java实现RSA接口参数加密
为什么使用RSA
对称加密:DES加密适用于一体项目、AES加密适用于前后端分离、Base64加密
非对称加密:RSA加密公钥发出去加密,私钥自己解密、Fiddler。
RSA算法的特点是产生一对密钥,用其中的一个密钥对文件加密后所产生的密文只能使用另一个密钥方能解密还原。把加密和解密的密钥分离对于互联网通信安全具有革命性的意义,由此,加密的密钥(公钥)就可以公开在网上传递而不用担心泄密,因为加密后的文件只能由另一个密钥(私钥)方可解密还原。
一、加入依赖
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
1、作用
添加org.apache.commons.codec.binary.Base64;
2、Base64编码说明
Base64 是一种编码的方式, 并不是安全领域的加密解密算法。
Base64 的目的是把一些二进制符转成普通字符, 方便在网络上传输。
因为有些系统只能使用ASCII字符,有些二进制字符是控制字符,不能直接传送, 需要转换。
Base64 作用将 非ASCII字符转换为ASCII字符。
3、sun.misc.BASE64加解密跟Apache的commons-codec加解密区别
由于Sun内部API sun.misc.BASE64在编译的时候会出现后期可能被删除的潜在风险的警告,然而让我们觉得以后真的删除了,那么程序就肯定报错了,潜在很大的风险。
二、常量
/**
* RSA最大加密明文大小
*/
private static final int MAX_ENCRYPT_BLOCK = 117;
/**
* RSA最大解密密文大小
*/
private static final int MAX_DECRYPT_BLOCK = 128;
/**
* 算法
*/
private static final String ALGORITHM_NAME = "RSA";
/**
* MD5_RSA
*/
private static final String MD5_RSA = "MD5withRSA";
三、引用的包
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
四、获取base64加密后密钥对
public static HashMap<String,String> getKeyPairMap() throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance(ALGORITHM_NAME);
generator.initialize(1024);
KeyPair keyPair = generator.generateKeyPair();
//将公钥私钥进行base64编码、使用encodeBase64进行编译编码,并返回一个byte字节数组
String privateKey = new String(Base64.encodeBase64(keyPair.getPrivate().getEncoded()));
String publicKey = new String(Base64.encodeBase64(keyPair.getPublic().getEncoded()));
HashMap<String,String> keyMap = new HashMap<>();
keyMap.put("privateKey",privateKey);
keyMap.put("publicKey",publicKey);
return keyMap;
}
五、RSA加密
1、获取公钥
/**
* RSA加密,获取公钥
* @param publicKey base64加密的公钥字符串
*/
public static PublicKey getPublicKey(String publicKey) throws Exception {
//使用decodeBase64进行破译编码,并返回一个byte字节数组
byte[] decodedKey = Base64.decodeBase64(publicKey.getBytes());
//使用X509标准作为密钥规范管理的编码格式,按照 X509 标准对其进行编码的密钥。复制数组的内容,以防随后的修改。
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey);
//创建一个KeyFactory对象。
//密钥工厂用于将 密钥 ( Key类型的不透明密码密钥)转换为 密钥规范 (底层密钥资料的透明表示)
//返回一个KeyFactory对象,用于转换指定算法的公钥/私钥。
//返回封装指定Provider对象的KeyFactorySpi实现的新KeyFactory对象。 请注意,指定的Provider对象不必在提供程序列表中注册。
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_NAME);
//根据提供的密钥规范(密钥材料)生成公钥对象。
return keyFactory.generatePublic(keySpec);
}
2、加密
/**
* RSA加密
* @param data 待加密数据
* @param publicKey 公钥
*/
public static String encrypt(String data,String publicKey) throws Exception {
//避免前端解密时,出现中文乱码情况,提前将数据进行中编码
//data = new String(data.getBytes("utf8"));
data = java.net.URLEncoder.encode(data, "UTF-8");
//Cipher此类为加密和解密提供密码功能
//创建 Cipher 对象,应用程序调用 Cipher 的 getInstance 方法并将所请求转换 的名称传递给它
Cipher cipher = Cipher.getInstance(ALGORITHM_NAME);
//RSA加密,获取公钥
PublicKey publicKeyP = getPublicKey(publicKey);
// Cipher对象需要初始化
// init(int opmode, Key key, AlgorithmParameterSpec params)
// (1)opmode :Cipher.ENCRYPT_MODE(加密模式)和 Cipher.DECRYPT_MODE(解密模式)
// (2)key :密匙,使用传入的盐构造出一个密匙,可以使用SecretKeySpec、KeyGenerator和KeyPairGenerator创建密匙,其中
// * SecretKeySpec和KeyGenerator支持AES,DES,DESede三种加密算法创建密匙
// * KeyPairGenerator支持RSA加密算法创建密匙
// (3)params :使用CBC模式时必须传入该参数,该项目使用IvParameterSpec创建iv 对象
cipher.init(Cipher.ENCRYPT_MODE, publicKeyP);
//获取加密内容的长度
int inputLen = data.getBytes().length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offset = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offset > 0) {
if (inputLen - offset > MAX_ENCRYPT_BLOCK) {
//加密或解密
cache = cipher.doFinal(data.getBytes(), offset, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data.getBytes(), offset, inputLen - offset);
}
out.write(cache, 0, cache.length);
i++;
offset = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
// 获取加密内容使用base64进行编码,并以UTF-8为标准转化成字符串
// 加密后的字符串
return new String(Base64.encodeBase64(encryptedData));
}
六、RSA解密
1、获取公钥
/**
* 获取私钥
*/
public static PrivateKey getPrivateKey(String privateKey) throws Exception {
byte[] decodedKey = Base64.decodeBase64(privateKey.getBytes());
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_NAME);
//根据提供的密钥规范(密钥材料)生成公钥对象。
return keyFactory.generatePrivate(keySpec);
}
2、加密
/**
* RSA解密
*
* @param data 待解密数据
* @param privateKey 私钥
*/
public static String decrypt(String data, String privateKey) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM_NAME);
PrivateKey privateKeyP = getPrivateKey(privateKey);
cipher.init(Cipher.DECRYPT_MODE, privateKeyP);
byte[] dataBytes = Base64.decodeBase64(data);
int inputLen = dataBytes.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offset = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offset > 0) {
if (inputLen - offset > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(dataBytes, offset, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(dataBytes, offset, inputLen - offset);
}
out.write(cache, 0, cache.length);
i++;
offset = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
// 解密后的内容
return new String(decryptedData, StandardCharsets.UTF_8);
}
七、RSA签名
1、签名
/**
* RSA签名,签名
* @param data 待签名数据
* @param privateKey 私钥
*/
public static String sign(String data,PrivateKey privateKey) throws Exception {
//以主编码格式返回密钥,如果此密钥不支持编码,则返回null。
byte[] keyBytes = privateKey.getEncoded();
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_NAME);
//根据提供的密钥规范(密钥材料)生成私钥对象。
PrivateKey key = keyFactory.generatePrivate(keySpec);
//Signature类用于提供数字签名,用于保证数据的完整性,用非对称密钥中的私钥签名,公钥验签。
//与 Java Security 中其他基于算法的类一样,Signature 提供了与实现无关的算法,因此,调用方(应用程序代码)
// 会请求特定的签名算法并将它传回给已被正确初始化的 Signature 对象。如果需要,还可以通过特定的提供程序请求特定的算法
//getInstance指定签名算法
Signature signature = Signature.getInstance(MD5_RSA);
//初始化签署签名的私钥
signature.initSign(key);
//根据初始化类型,这可更新要签名或验证的字节
signature.update(data.getBytes());
//signature.sign()签署或验证所有更新字节的签名
return new String(Base64.encodeBase64(signature.sign()));
}
2、验签
/**
* 验签
*
* @param srcData 原始字符串
* @param publicKey 公钥
* @param sign 签名
*/
public static boolean verify(String srcData,PublicKey publicKey,String sign) throws Exception {
byte[] keyBytes = publicKey.getEncoded();
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_NAME);
PublicKey key = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(MD5_RSA);
signature.initVerify(key);
signature.update(srcData.getBytes());
//signature.verify签署或验证所有更新字节的签名
return signature.verify(Base64.decodeBase64(sign.getBytes()));
}
八、测试
// 公钥
private static String publicKeyStrTestYan = "";
// 私钥
private static String privateKeyStrTestYan = "";
// 加密后串
private static String publicKeyStrTestYanjie = "";
@PostMapping("/testRSAYan")
public SystemResult testRSAYan(@RequestBody String data) {
try {
//生成密钥对
// HashMap<String,String> keyPairMap = RSAUtilYan.getKeyPairMap();
// String privateKey = keyPairMap.get("privateKey");
// String publicKey = keyPairMap.get("publicKey");
// System.out.println("私钥 => " + privateKey + "\n");
// System.out.println("公钥 =>" + publicKey + "\n");
// return SystemResult.ok(keyPairMap.toString());
//RSA加密
// String encryptData = RSAUtilYan.encrypt(data,publicKeyStrTestYan);
// System.out.println("加密后内容 => " + encryptData + "\n");
// return SystemResult.ok(encryptData);
//
// // RSA解密
// String decryptData = RSAUtilYan.decrypt(publicKeyStrTestYanjie, privateKeyStrTestYan);
// decryptData = java.net.URLDecoder.decode(decryptData, "UTF-8");
//转json
// params = com.alibaba.fastjson.JSONObject.parseObject(decryptData);
// System.out.println("解密后内容 => " + decryptData + "\n");
// return SystemResult.ok(decryptData);
// RSA签名
String sign = RSAUtilYan.sign(data, RSAUtilYan.getPrivateKey(privateKeyStrTestYan));
// RSA验签
boolean result = RSAUtilYan.verify(data, RSAUtilYan.getPublicKey(publicKeyStrTestYan), sign);
System.out.println("验签结果 => " + result + "\n");
return SystemResult.ok();
} catch (Exception e) {
e.printStackTrace();
System.err.println("RSA加解密异常");
return SystemResult.error("RSA加解密异常");
}
}
九、前端加解密
跟后端要私钥
拿到后端穿过来的密文之后,直接解密,如果解密之后和预期的数据不一样,要和后端沟通是什么原因
下面展示了一个:后端加密的时候是先将字符串转成json对象 再加密的;所以前端解密之后要再将json对象转化为字符串
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入jsencrypt.js文件 -->
<script src="https://cdn.bootcss.com/jsencrypt/3.0.0-beta.1/jsencrypt.js"></script>
</head>
<body>
<script>
//公钥
var PUBLIC_KEY = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3+2kBT+EJq8PqSrleCgsddWtwuIDaNtSwWunuyaRuSGWhBJxFApWXv8xfdchhbw5ti/2z3w5ipAufXJ/5R9FwBGSGEivbpUUCpVuk7iFY8vE+yfZTobEkfJ0vsO9FllG9AofYDAP5dlKYfTnXpNv7HrPrZfleB5HOtXS1OMt02wIDAQAB';
//私钥
var PRIVATE_KEY = 'MIICXAIBAAKBgQC3+2kBT+EJq8PqSrleCgsddWtwuIDaNtSwWunuyaRuSGWhBJxFApWXv8xfdchhbw5ti/2z3w5ipAufXJ/5R9FwBGSGEivbpUUCpVuk7iFY8vE+yfZTobEkfJ0vsO9FllG9AofYDAP5dlKYfTnXpNv7HrPrZfleB5HOtXS1OMt02wIDAQABAoGBALb3QIL8hGjQt+JNc7EwvcMLHdEYu5PHiZbkotZFE9BVETSjWCFNI6WpKT8Z7C/xeOC6opB52F2ClMhkHfeTU5SpQx2YoRXjQGxCigTaRvlMo2q8q05sy4cxKpE+7c4yMaZSPldrx5HjLLlJzso7xngtHqnP/wQsbNnARhQ31tIhAkEA58D7SxU0i8UWSR1hPrXC4iPfDXTwtFOVyyHWCCId5fNFH6omlyHzleH4WLSYe/ZBAJRpPUqUeLlSKtkzonIgsQJBAMs693OXOeQpZdWUFlEl0hvcQFclaSID2TPbcL/doMslYd7tegyhBIrQRIzUQooZF5lbcsBLwb1hFPx0N7E6MUsCQH6pIC5W9pL1MkrffchyOoKxrDSElxFXJWURpxJm4wRMQpkdu12aNrDSAZMpkLUfT0Nr2WCgJCkez+OPNgMwcOECQGlMMBHv9MrGuECk7bs1wVs2DxAYDhTVsaAwvoZqqnkW8VKz8FzCwLu7lQjyoVc0EQogW6BOoB8jQeBbxs9Iu2kCQAFzAhRnjj1Rn2uvMqRCva+ez3RPaLHK4BzC5UdgF/qTu58+K29UqKSLenMB8ZZ1VQIJ83VnKSiIyHqeHBjl6LE=';
//使用公钥加密
var encrypt = new JSEncrypt();
encrypt.setPublicKey('-----BEGIN PUBLIC KEY-----' + PUBLIC_KEY + '-----END PUBLIC KEY-----');
var encrypted = encrypt.encrypt("张三");
console.log('加密后数据:%o', encrypted);
//使用私钥解密
var decrypt = new JSEncrypt();
decrypt.setPrivateKey('-----BEGIN RSA PRIVATE KEY-----'+PRIVATE_KEY+'-----END RSA PRIVATE KEY-----');
var uncrypted = decrypt.decrypt(encrypted);
console.log('解密后数据:%o', uncrypted); // 张三
// 下面这段密文就是后端传过来的json
var uncrypted1 = decrypt.decrypt("evHC57KB+RRQdSZmFWoAR7KcViMW+P0GGjhIvOociFS7QWJwE63A+8XqGJuVs+WOVNO/NTuYEd78QJu8gHcVJvt3QumYqt7EPVNekQ2OTRuyi+bTSF8Xy9tj+GXs8wF+3jbJYDdOwgEciXVFxbWEXwH6/pu44FIQEjL376RetZg=");
console.log('解密后数据:%o', JSON.parse(uncrypted1) ); // 安杰
// 如果没转成字符串的话
console.log('解密后数据:%o', uncrypted1); // \\u5b89\\u6770\
</script>
</body>
</html>
或者使用:
//安装
npm install jsencrypt
//引入
import JSEncrypt from 'jsencrypt'
//rsa加密
const encryptor = new JSEncrypt() // 创建加密对象实例
const pubKey = ''
encryptor.setPublicKey(pubKey) //设置公钥
const rsaPassWord = encryptor.encrypt('要加密的内容') // 对内容进行加密
十、遇到的问题:RSA解密中文乱码解决,前端加密后端解密;
解决:
1、如果后端加密(需加密前对字符串进行操作):
data = java.net.URLEncoder.encode(data, "UTF-8");
如果前端加密:
var name = '中文测试'
var nameen = encodeURIComponent(name);
var name = encrypt.encrypt(nameen);
先用encodeURIComponent() 加密,再用rsa加密
2、后端解密(解密后对接过字符串进行操作)
decryptData = java.net.URLDecoder.decode(decryptData, "UTF-8");
前端解密
dncodeURIComponent()