在当今的互联网应用开发中,用户数据的安全性至关重要。登录功能作为用户进入系统的第一道防线,其安全性更是不容忽视。本文将介绍一种基于 RSA 加密的登录方案,前端使用 Node.js 的 node-forge
库对密码进行公钥加密,后端使用 Spring Boot 的 RSUtils
工具类进行私钥解密,从而确保用户密码在传输过程中的安全性。
一、RSA 加密原理简介
RSA 是一种非对称加密算法,它使用一对密钥:公钥和私钥。公钥用于加密数据,私钥用于解密数据。在登录场景中,前端使用公钥对用户密码进行加密,然后将加密后的密码发送到后端。后端使用私钥对加密的密码进行解密,从而获取原始密码,再进行后续的验证操作。
二、前端实现:使用 Node.js 的 node-forge
加密密码
(一)引入 node-forge
库
在前端项目中,首先需要引入 node-forge
库。可以通过 npm 安装:
bash复制
npm install node-forge
然后在代码中引入:
JavaScript复制
import forge from 'node-forge';
(二)获取公钥
前端登录界面
<template>
<div class="about">
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="活动名称" prop="name">
<el-input v-model="ruleForm.name"/>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="ruleForm.password" show-password/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">立即创建</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
</div>
</template>
公钥需要从后端获取。后端可以通过某种方式(如 API)将公钥发送到前端。前端将公钥存储起来,以便后续使用。
<script>
//导入node-forge
import forge from 'node-forge'
import JSEncrypt from 'jsencrypt';
export default {
data() {
return {
publicKey: '',
ruleForm: {
name: 'chj',
password: 'gb123456'
}
};
},
created() {
this.initPublicKey();
},
methods: {
initPublicKey() {
this.axios.get('http://localhost:9999/api/getrsa').then(res => {
this.publicKey = res.data.publicKey;
console.log(this.publicKey);
});
}
}
}
</script>
(三)使用公钥加密密码
当用户输入密码并点击登录按钮时,前端使用存储的公钥对密码进行加密,将加密后的密码作为登录请求的一部分发送到后端
submitForm(formName) {
//获取publicKeyObj对象
var publicKeyObj = forge.pki.publicKeyFromPem(this.publicKey);
console.log("原始数据",this.ruleForm.password);
//对URI组件统一编码
var encode = encodeURIComponent(this.ruleForm.password);
console.log("编码后数据",encode)
//对字符串加密处理
var encrypted = publicKeyObj.encrypt(encode, 'RSA-OAEP', {
md: forge.md.sha256.create(),
mgf1:{
md: forge.md.sha1.create()
}
});
console.log("加密后数据",encrypted);
//工具类转码
var encryptedBase64 = forge.util.encode64(encrypted);
console.log("base64后得编码",encryptedBase64);
//发送请求携带加密的串
this.axios.post('http://localhost:9999/api/login', {
name: this.ruleForm.name,
password: encryptedBase64
}).then(res => {
console.log(res);
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
三、后端实现:Spring Boot 结合 RSUtils
工具类解密密码
工具类RSUtils
package org.example.util;
import com.sun.crypto.provider.SunJCE;
import java.nio.charset.StandardCharsets;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.io.ByteArrayOutputStream;
import javax.crypto.Cipher;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import java.security.*;
import java.util.*;
public class RSAUtils {
public static final String KEY_ALGORITHM = "RSA";
private static final int MAX_DECRYPT_BLOCK = 256;
//Base64解码
private static final Base64.Decoder decoder64 = Base64.getDecoder();
//Base64编码
private static final Base64.Encoder encoder64 = Base64.getEncoder();
/**
* 生成公私钥
*
* @param keySize 密钥长度(如2048)
* @return 密钥对
* @throws NoSuchAlgorithmException
*/
public static SecretKey generateSecretKey(int keySize) throws NoSuchAlgorithmException {
SunJCE provider=new com.sun.crypto.provider.SunJCE();
Security.addProvider(provider);
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(keySize, new SecureRandom());
KeyPair pair = keyGen.generateKeyPair();
PrivateKey privateKey = pair.getPrivate();
PublicKey publicKey = pair.getPublic();
return new SecretKey(encoder64.encodeToString(publicKey.getEncoded()), encoder64.encodeToString(privateKey.getEncoded()));
}
/**
* 私钥解密
*/
public static String decryptByPrivateKey(String str, String key) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
OAEPParameterSpec oaepParams = new OAEPParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-1"), PSource.PSpecified.DEFAULT);
PrivateKey privateKey = strToPrivateKey(key);
cipher.init(Cipher.DECRYPT_MODE, privateKey, oaepParams);
byte[] data = decryptBASE64(str);
byte[] bytes = dataSegment(data, cipher, MAX_DECRYPT_BLOCK);
return new String(bytes, StandardCharsets.UTF_8);
}
/**
* 对数据进行分段加密或解密
*/
private static byte[] dataSegment(byte[] data, Cipher cipher, int maxBlock) throws Exception{
byte[] toByteArray;
int inputLen = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
int i = 0;
byte[] cache;
// 对数据分段加密解密
while (inputLen - offSet > 0) {
int var = inputLen - offSet > maxBlock ? maxBlock : inputLen - offSet;
cache = cipher.doFinal(data, offSet, var);
out.write(cache, 0, cache.length);
i++;
offSet = i * maxBlock;
}
toByteArray = out.toByteArray();
out.close();
return toByteArray;
}
/**
* 获取私钥
*/
private static PrivateKey strToPrivateKey(String str) throws Exception {
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(decryptBASE64(str));
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
return privateKey;
}
/**
* 字符串转字节数组
*/
private static byte[] decryptBASE64(String str) {
return Base64.getDecoder().decode(str);
}
public static class SecretKey {
private String publicKey;
private String privateKey;
public SecretKey(String publicKey, String privateKey) {
this.publicKey = publicKey;
this.privateKey = privateKey;
}
public String getPublicKey() {
return publicKey;
}
public String getPrivateKey() {
return privateKey;
}
@Override
public String toString() {
return "SecretKey{" +
"publicKey='" + publicKey + '\'' +
", privateKey='" + privateKey + '\'' +
'}';
}
}
}
后端控制器类代码:
package org.example.controller;
import org.example.model.Users;
import org.example.util.RSAUtils;
import org.example.util.RSAtils;
import org.springframework.web.bind.annotation.*;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import static java.util.regex.Pattern.compile;
@CrossOrigin
@RequestMapping("/api")
@RestController
public class RSAController {
//密钥对象
private RSAUtils.SecretKey secretKey;
//通过构造函数创建对象
public RSAController() throws NoSuchAlgorithmException {
System.out.println("----------------");
secretKey = RSAUtils.generateSecretKey(2048);
}
@GetMapping("/getrsa")
public Map<String,Object> getrsa() throws NoSuchAlgorithmException {
Map<String,Object> map = new HashMap<>();
String temp="-----BEGIN PUBLIC KEY-----\n" + secretKey.getPublicKey() + "\n-----END PUBLIC KEY-----";
map.put("publicKey",temp);
// map.put("privateKey",secretKey.getPrivateKey());
return map;
}
@PostMapping("/login")
public Map<String,Object> login(@RequestBody Users users) throws Exception {
//工具类解密
String decryptedData = RSAUtils.decryptByPrivateKey(users.getPassword(),secretKey.getPrivateKey());
//创建对象
Map<String, Object> map = new HashMap<>();
//设置解密成功响应的结果
map.put("pass", decryptedData);
return map;
}
}
model实体类
package org.example.model;
import java.io.Serializable;
public class Users implements Serializable {
private String name;
private String password;
//空的构造函数
public Users() {
}
//构造函数
public Users(String name, String password) {
this.name = name;
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
四、安全性考虑
密钥管理:密钥对的生成和存储需要严格管理。公钥可以公开,但私钥必须保密,不能泄露给任何未经授权的人员或系统。
传输安全:虽然密码在传输过程中被加密,但仍然需要使用 HTTPS 协议来确保整个通信过程的安全性,防止中间人攻击。
前端安全:前端代码可能会被篡改,因此需要确保前端代码的完整性和安全性。可以通过代码签名等方式来防止恶意代码注入。
五、测试结果
六、总结
本文介绍了一种基于 RSA 加密的登录方案,前端使用 Node.js 的 node-forge
库对密码进行公钥加密,后端使用 Spring Boot 的 RSUtils
工具类进行私钥解密。通过这种方式,可以有效保护用户密码在传输过程中的安全性。在实际应用中,还需要结合其他安全措施,如 HTTPS、密钥管理等,以确保系统的整体安全性。