RSA加密解密
一、RSA的原理
1.1、RSA的介绍
RSA加密算法是一种可逆的非对称加密算法,即RSA加密时候用的密钥(公钥)和RSA解密时用的密钥(私钥)不是同一把。基本原理是将两个很大的质数相乘很容易得到乘积,但是该乘积分解质因数却很困难。RSA算法被广泛的用于加密解密和RSA签名/验证等领域。
1.2、RSA算法的速度与安全性
比起AES等其它对称算法来说,RSA运算更为复杂,所以要慢得多。
从安全角度来讲,一般建议RSA密钥长度至少为2048位。世界上还没有任何可靠的攻击RSA算法的方式,如果密钥足够长或者没有密钥,想要RSA解密或者破解RSA解密基本是不可能的。RSA从提出到现在已近二十年,经历了各种攻击的考验,逐渐为人们接受,普遍认为是目前最优秀的公钥方案之一。
1.3、RSA存储格式
DER是RSA密钥的二进制格式,PEM是DER转码为Base64的字符格式,由于DER是二进制格式,不便于阅读和理解。一般而言,密钥都是通过PEM的格式进行存储的,本工具所选择的RSA密钥格式也就是PEM编码存储的格式。
二、项目中使用
因为网站的代码和js对用户是可见的,如果采用对称加密的话那么用户可以通过看js就知道怎么解密数据了。所以为了保证传输重要数据的安全性,需要非对称加密。js给后台提交的数据用服务端给js提供的公钥加密数据。提交到后台用自己私钥解密数据。服务器给js端返回的数据用js给服务器提供的公钥加密,js用自己持有的私钥解密。这样用户在网页端只能看到一个秘钥。就能保证传输的安全性。
1、Java后端使用
1.1、RSA加密工具类–RSAUtils.Class
import org.apache.commons.codec.binary.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: AhNorth
* @Desc: RSA工具类
* @DateTime: 2023/6/15 14:51
*/
public class RSAUtils {
//随机生成密钥对
public static Map<String, String> genKeyPair() {
// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
KeyPairGenerator keyPairGen = null;
try {
keyPairGen = KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
// 初始化密钥对生成器,密钥大小为96-1024位
assert keyPairGen != null;
keyPairGen.initialize(1024, new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); // 得到私钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 得到公钥
String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
// 得到私钥字符串
String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
// 将公钥和私钥保存到Map
Map<String, String> keyMap = new HashMap<>();
keyMap.put("publicKey", publicKeyString); //公钥
keyMap.put("privateKey", privateKeyString); //私钥
return keyMap;
}
/** RSA公钥加密
* @param str 加密字符串
* @param publicKey 公钥
* @return 密文
*/
public static String encrypt(String str, String publicKey) {
//base64编码的公钥
byte[] decoded = Base64.decodeBase64(publicKey);
RSAPublicKey pubKey = null;
String outStr = null;
try {
pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)));
} catch (InvalidKeySpecException | BadPaddingException | IllegalBlockSizeException | InvalidKeyException | NoSuchPaddingException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
//RSA加密
return outStr;
}
/** RSA私钥解密
* @param str 加密字符串
* @param privateKey 私钥
* @return 铭文
*/
public static String decrypt(String str, String privateKey) {
//64位解码加密后的字符串
byte[] inputByte = Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8));
//base64编码的私钥
byte[] decoded = Base64.decodeBase64(privateKey);
RSAPrivateKey priKey = null;
//RSA解密
Cipher cipher = null;
String outStr = null;
try {
priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
outStr = new String(cipher.doFinal(inputByte));
} catch (InvalidKeySpecException | NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | InvalidKeyException e) {
e.printStackTrace();
}
return outStr;
}
}
2.2、初始化秘钥
编写获取秘钥接口,前端随机生成uuid并传递给后端,后端初始化密钥对,公钥传递给前端用于加密,私钥保存到redis或本地缓存中方便解密。
/**
* Desc: 获取公钥
* @param uuid
* @return: java.lang.String
*/
@GetMapping("/key")
public Result<String> publicKey(@RequestParam String uuid){
try {
//初始化公钥私钥对
Map<String, String> keyMap = RSAUtils.genKeyPair();
String publicKey = keyMap.get("publicKey");
String privateKey = keyMap.get("privateKey");
//保存私钥到缓存
if(open){
String key = RedisKeys.getPrivateKey(uuid);
redisUtils.set(key, privateKey, 300);
}else {
localCache.put("privateKey" + uuid, privateKey);
}
return publicKey;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
2.3、解密
根据获取公钥的uuid从缓存中获取对应的私钥后使用工具类中的方法解密即可。
/**
* Desc: 解密
* @param uuid
* @param data 加密后的数据
* @return: java.lang.String
*/
public static String getDecryptStr(uuid, data){
String privateKey = null;
if(open){
uuid = RedisKeys.getPrivateKey(uuid);
privateKey = (String)redisUtils.get(uuid);
//删除Redis中的私钥
if(privateKey != null){
redisUtils.delete(uuid);
}
}else {
privateKey = localCache.getIfPresent("privateKey" + uuid);
//删除缓存中的私钥
if(privateKey != null){
localCache.invalidate("privateKey" + uuid);
}
}
//解密
String str = null;
try {
str = RSAUtils.decrypt(data, privateKey);
} catch (Exception e) {
System.out.println("解密错误");
e.printStackTrace();
}
return str;
}
2、Vue前端使用
jsencrypt和encryptlong都是rsa加密,加密的对象一定要是字符串。 简单数据用前者,如果加密的是对象并且数据还挺多的,比如含有token 用后者。
2.1、引入依赖
npm install jsencrypt --save
npm install encryptlong --save
2.2、RSA加密工具类–rsa.js
/* 产引入jsencrypt实现数据RSA加密 */
import JSEncrypt from 'jsencrypt' // 处理长文本数据时报错 jsencrypt.js Message too long for RSA
/* 产引入encryptlong实现数据RSA加密 */
import Encrypt from 'encryptlong' // encryptlong是基于jsencrypt扩展的长文本分段加解密功能。
/**
* JSEncrypt加密
* @param data 数据
* @param publicKey 公钥
* @returns {string}
*/
export function rsaPublicData(data, publicKey) {
const jsencrypt = new JSEncrypt()
jsencrypt.setPublicKey(publicKey)
// 如果是对象/数组的话,需要先JSON.stringify转换成字符串
return jsencrypt.encrypt(data)
}
/**
* JSEncrypt解密
* @param data 数据
* @param privateKey 私钥
* @returns {string}
*/
export function rsaPrivateData(data, privateKey) {
const jsencrypt = new JSEncrypt()
jsencrypt.setPrivateKey(privateKey)
// 如果是对象/数组的话,需要先JSON.stringify转换成字符串
return jsencrypt.encrypt(data)
}
/**
* encryptlong加密
* @param data 数据
* @param publicKey 公钥
* @returns {string}
*/
export function encrypt(data, publicKey) {
const encryptor = new Encrypt()
encryptor.setPublicKey(publicKey)
// 如果是对象/数组的话,需要先JSON.stringify转换成字符串
return encryptor.encryptLong(data)
}
/**
* encryptlong解密
* @param data 数据
* @param privateKey 私钥
* @returns {string}
*/
export function decrypt(data, privateKey) {
const encryptor = new Encrypt()
encryptor.setPrivateKey(privateKey)
// 如果是对象/数组的话,需要先JSON.stringify转换成字符串
return encryptor.decryptLong(data)
}
2.3、生成uuid
前端生成uuid传给后端,在初始化密钥对的时候使用该uuid作为key将私钥保存到缓存中,登录时也需要上传该uuid使后端可以从缓存中获取到对应的秘钥。
/**
* 获取uuid
* @returns {string}
*/
export function getUUID () {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
return (c === 'x' ? (Math.random() * 16 | 0) : ('r&0x3' | '0x8')).toString(16)
})
}
2.4、加密
获取到公钥后根据公钥加密后传输给后端。
const password = encrypt(this.password.toString(), publicKey)
感谢大家的观看。