因为 RSA 加密的代码都是比较通用的,所以没有特意去整合,这里参照着两位大神的代码重新写了一遍,做了一些简单的修改,符合本地运行环境
服务端代参照:
客户端代码参照:
JS加密依赖:jsencrypt.jsGithub地址:可客户端尽量依赖JAVA自带的Jar,只是Base64加密的时候额外依赖了apache的工具类commons-net-3.3.jar
服务端工RSA工具类
package com.wzh.config.utils; | |
import org.apache.commons.net.util.Base; | |
import org.apache.logj.Logger; | |
import javax.crypto.Cipher; | |
import java.io. byte ArrayOutputStream; | |
import java.security.*; | |
import java.security.interfaces.RSAPrivateKey; | |
import java.security.interfaces.RSAPublicKey; | |
import java.security.spec.PKCSEncodedKeySpec; | |
import java.security.spec.XEncodedKeySpec; | |
import java.util.HashMap; | |
import java.util.Map; | |
/** | |
* <RSA加密解密工具类> | |
* <额外依赖 commons-net-.3.jar,日志用的log4j,如果是其他的日志框架可以更改> | |
* @author wzh | |
* @version-12-16 18:20 | |
* @see [相关类/方法] (可选) | |
**/public class RsaUtils | |
{ | |
private static Logger log = Logger.getLogger(RsaUtils.class); | |
/** | |
* 块加密大小 | |
*/ private static final int cache _SIZE = 1024; | |
/** | |
* 加密算法RSA | |
*/ public static final String KEY_ALGORITHM = "RSA"; | |
/** | |
* 签名算法 | |
*/ public static final String SIGNATURE_ALGORITHM = "MDwithRSA"; | |
/** | |
* 获取公钥的key | |
*/ private static final String PUBLIC_KEY = "RsaPublicKey"; | |
/** | |
* 获取 私钥 的key | |
*/ private static final String PRIVATE_KEY = "RsaPrivateKey"; | |
/** | |
* RSA最大加密明文大小 | |
*/ private static final int MAX_ENCRYPT_BLOCK =; | |
/** | |
* RSA最大解密密文大小 | |
*/ private static final int MAX_DECRYPT_BLOCK =; | |
/** | |
* Base 字符串 解码为二进制数据 | |
* @param base | |
* @return 二进制数据 | |
* @throws Exception | |
*/ public static byte[] decodeBase(String base64) | |
throws Exception | |
{ | |
return Base.decodeBase64(base64.get bytes ()); | |
} | |
/** | |
* 二进制数据编码为Base字符串 | |
* @param bytes | |
* @return Base字符串 | |
* @throws Exception | |
*/ public static String encodeBase(byte[] bytes) | |
throws Exception | |
{ | |
return new String(Base.encodeBase64(bytes)); | |
} | |
/** | |
* 生成秘钥对 | |
* @return 返回公钥和私钥的Map集合 | |
* @throws Exception | |
*/ public static Map<String, Object> initKeyPair() | |
throws Exception | |
{ | |
KeyPairGenerator keyPairGen = KeyPairGenerator. getInstance (KEY_ALGORITHM); | |
keyPairGen.initialize(CACHE_SIZE); | |
KeyPair keyPair = keyPairGen.generateKeyPair(); | |
RSAPublicKey publicKey = (RSAPublicKey)keyPair.getPublic(); | |
RSAPrivateKey privateKey = (RSAPrivateKey)keyPair.getPrivate(); | |
Map<String, Object> keyMap = new HashMap<String, Object>(); | |
// 公钥 | |
keyMap.put(PUBLIC_KEY, publicKey); | |
// 私钥 | |
keyMap.put(PRIVATE_KEY, privateKey); | |
return keyMap; | |
} | |
/** | |
* 获取私钥 | |
* @param keyMap 秘钥对Map | |
* @return 私钥字符串 | |
* @throws Exception | |
*/ public static String getPrivateKey(Map<String, Object> keyMap) throws Exception { | |
Key key = (Key) keyMap.get(PRIVATE_KEY); | |
return encodeBase(key.getEncoded()); | |
} | |
/** | |
* 获取公钥字符串 | |
* @param keyMap 秘钥对Map | |
* @return 公钥字符串 | |
* @throws Exception | |
*/ public static String getPublicKey(Map<String, Object> keyMap) throws Exception { | |
Key key = (Key) keyMap.get(PUBLIC_KEY); | |
return encodeBase(key.getEncoded()); | |
} | |
/** | |
* 使用私钥生成数字签名 | |
* @param data 使用私钥加密的数据 | |
* @param privateKey 是哟啊字符串 | |
* @return 数字签名 | |
* @throws Exception | |
*/ public static String sign(byte[] data, String privateKey) throws Exception { | |
// 获取byte数组 | |
byte[] keyBytes = decodeBase(privateKey); | |
// 构造PKCSEncodedKeySpec对象 | |
PKCSEncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); | |
// 指定的加密算法 | |
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); | |
// 取私钥匙对象 | |
PrivateKey privateK = keyFactory.generatePrivate(pkcsKeySpec); | |
// 用私钥对信息生成数字签名 | |
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); | |
signature.initSign(privateK); | |
signature.update(data); | |
return encodeBase(signature.sign()); | |
} | |
/** | |
* 校验数字签名 | |
* @param data 私钥加密的数据 | |
* @param publicKey 公钥字符串 | |
* @param sign 私钥生成的签名 | |
* @return 校验成功返回true 失败返回false | |
* @throws Exception | |
*/ public static boolean verify(byte[] data, String publicKey, String sign) throws Exception { | |
// 获取byte数组 | |
byte[] keyBytes = decodeBase(publicKey); | |
XEncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); | |
// 构造XEncodedKeySpec对象 | |
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); | |
// 指定的加密算法 | |
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); | |
// 取公钥匙对象 | |
PublicKey publicK = keyFactory.generatePublic(keySpec); | |
signature.initVerify(publicK); | |
signature.update(data); | |
// 验证签名是否正常 | |
return signature.verify(decodeBase(sign)); | |
} | |
/** | |
* 私钥加密 | |
* @param data 需要加密的数据 | |
* @param privateKey 私钥 | |
* @return 加密后的数据 | |
* @throws Exception | |
*/ public static byte[] encryptByPrivateKey(byte[] data, String privateKey) throws Exception { | |
byte[] keyBytes = decodeBase(privateKey); | |
PKCSEncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); | |
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); | |
Key privateK = keyFactory.generatePrivate(pkcsKeySpec); | |
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); | |
cipher.init(Cipher.ENCRYPT_MODE, privateK); | |
int inputLen = data.length; | |
ByteArrayOutputStream out = new ByteArrayOutputStream(); | |
int offSet =; | |
byte[] cache; | |
int i =; | |
// 对数据分段加密 | |
while (inputLen - offSet >) { | |
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { | |
cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK); | |
} else { | |
cache = cipher.doFinal(data, offSet, inputLen - offSet); | |
} | |
out.write(cache,, cache.length); | |
i++; | |
offSet = i * MAX_ENCRYPT_BLOCK; | |
} | |
byte[] encryptedData = out.toByteArray(); | |
out.close(); | |
return encryptedData; | |
} | |
/** | |
* 公钥加密 | |
* @param data 需要加密的数据 | |
* @param publicKey 公钥字符串 | |
* @return 加密后的数据 | |
* @throws Exception | |
*/ public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception { | |
byte[] keyBytes = decodeBase(publicKey); | |
XEncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); | |
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); | |
Key publicK = keyFactory.generatePublic(xKeySpec); | |
// 对数据加密 | |
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); | |
cipher.init(Cipher.ENCRYPT_MODE, publicK); | |
int inputLen = data.length; | |
ByteArrayOutputStream out = new ByteArrayOutputStream(); | |
int offSet =; | |
byte[] cache; | |
int i =; | |
// 对数据分段加密 | |
while (inputLen - offSet >) { | |
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { | |
cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK); | |
} else { | |
cache = cipher.doFinal(data, offSet, inputLen - offSet); | |
} | |
out.write(cache,, cache.length); | |
i++; | |
offSet = i * MAX_ENCRYPT_BLOCK; | |
} | |
byte[] encryptedData = out.toByteArray(); | |
out.close(); | |
return encryptedData; | |
} | |
/** | |
* 私钥解密 | |
* @param encryptedData 公钥加密的数据 | |
* @param privateKey 私钥字符串 | |
* @return 私钥解密的数据 | |
* @throws Exception | |
*/ public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey) throws Exception { | |
byte[] keyBytes = decodeBase(privateKey); | |
PKCSEncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); | |
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); | |
Key privateK = keyFactory.generatePrivate(pkcsKeySpec); | |
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); | |
cipher.init(Cipher.DECRYPT_MODE, privateK); | |
int inputLen = encryptedData.length; | |
ByteArrayOutputStream out = new ByteArrayOutputStream(); | |
int offSet =; | |
byte[] cache; | |
int i =; | |
// 对数据分段解密 | |
while (inputLen - offSet >) { | |
if (inputLen - offSet > MAX_DECRYPT_BLOCK) { | |
cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK); | |
} else { | |
cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet); | |
} | |
out.write(cache,, cache.length); | |
i++; | |
offSet = i * MAX_DECRYPT_BLOCK; | |
} | |
byte[] decryptedData = out.toByteArray(); | |
out.close(); | |
return decryptedData; | |
} | |
/** | |
* 公钥解密 | |
* @param encryptedData 私钥加密的数据 | |
* @param publicKey 公钥字符串 | |
* @return 公钥解密的数据 | |
* @throws Exception | |
*/ public static byte[] decryptByPublicKey(byte[] encryptedData, String publicKey) throws Exception { | |
byte[] keyBytes = decodeBase(publicKey); | |
XEncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); | |
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); | |
Key publicK = keyFactory.generatePublic(xKeySpec); | |
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); | |
cipher.init(Cipher.DECRYPT_MODE, publicK); | |
int inputLen = encryptedData.length; | |
ByteArrayOutputStream out = new ByteArrayOutputStream(); | |
int offSet =; | |
byte[] cache; | |
int i =; | |
// 对数据分段解密 | |
while (inputLen - offSet >) { | |
if (inputLen - offSet > MAX_DECRYPT_BLOCK) { | |
cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK); | |
} else { | |
cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet); | |
} | |
out.write(cache,, cache.length); | |
i++; | |
offSet = i * MAX_DECRYPT_BLOCK; | |
} | |
byte[] decryptedData = out.toByteArray(); | |
out.close(); | |
return decryptedData; | |
} | |
/** | |
* 公钥加密方法 | |
* @param data 需加密的字符串 | |
* @param PUBLICKEY 公钥字符串 | |
* @return 加密后的字符串 | |
*/ public static String encryptedDataByPublic(String data, String PUBLICKEY) { | |
try { | |
data = encodeBase(encryptByPublicKey(data.getBytes(), PUBLICKEY)); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
log.error(e.getMessage(),e); | |
} | |
return data; | |
} | |
/** | |
* 私钥解密方法 | |
* @param data 公钥加密的字符串 | |
* @param PRIVATEKEY 私钥字符串 | |
* @return 私钥解密的字符串 | |
*/ public static String decryptDataByPrivate(String data, String PRIVATEKEY) { | |
String temp = ""; | |
try { | |
byte[] rs = decodeBase(data); | |
//以utf-的方式生成字符串 | |
temp = new String(decryptByPrivateKey(rs, PRIVATEKEY),"UTF-"); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
return temp; | |
} | |
public static void main(String[] args) { | |
try { | |
Map<String, Object> keyMap = RsaUtils.initKeyPair(); | |
String publicKey = RsaUtils.getPublicKey(keyMap); | |
String privateKey = RsaUtils.getPrivateKey(keyMap); | |
System.out.println("公钥:" + publicKey); | |
System.out.println("私钥:" + privateKey); | |
String source = "我是需要私钥加密的字符串!"; | |
System.out.println("签名验证逻辑,私钥加密--公钥解密,需要加密的字符串:" + source); | |
byte[] data = source.getBytes(); | |
byte[] encodedData = RsaUtils.encryptByPrivateKey(data, privateKey); | |
System.out.println("私钥加密后:" + new String(encodedData)); | |
String sign = RsaUtils.sign(encodedData, privateKey); | |
System.out.println("签名:" + sign); | |
boolean status = RsaUtils.verify(encodedData, publicKey, sign); | |
System.out.println("验证结果:" + status); | |
byte[] decodedData = RsaUtils.decryptByPublicKey(encodedData, publicKey); | |
String target = new String(decodedData); | |
System.out.println("公钥解密私钥加密的数据:" + target); | |
System.out.println("---------公钥加密----私钥解密----------"); | |
// 这里尽量长一点,复制了一段歌词 | |
String msg = "月溅星河,长路漫漫,风烟残尽,独影阑珊;谁叫我身手不凡,谁让我爱恨两难,到后来," + | |
"肝肠寸断。幻世当空,恩怨休怀,舍悟离迷,六尘不改;且怒且悲且狂哉,是人是鬼是妖怪,不过是," + | |
"心有魔债。叫一声佛祖,回头无岸,跪一人为师,生死无关;善恶浮世真假界,尘缘散聚不分明,难断!" + | |
"我要这铁棒有何用,我有这变化又如何;还是不安,还是氐惆,金箍当头,欲说还休。我要这铁棒醉舞魔," + | |
"我有这变化乱迷浊;踏碎灵霄,放肆桀骜,世恶道险,终究难逃。"; | |
String ecodeMsg = RsaUtils.encryptedDataByPublic(msg,publicKey); | |
System.out.println("加密后的歌词:" + ecodeMsg); | |
String decodeMsg = RsaUtils.decryptDataByPrivate(ecodeMsg,privateKey); | |
System.out.println("解密后的歌词:" + decodeMsg); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
} |
首先测试一下工具类,main函数跑一下,成功验证签名,加密,解密
image.png
客户端JS代码,需要JSEncrypt库,前文有给出github地址,这里对这个库做一个简单的扩展,因为RSA长文本超过秘钥长度要报错,所以需要扩展修改下
/** | |
* --------------------------- | |
* 此JS需加载JSEncrypt库的后面,加密解密调用着两个方法 | |
* --------------------------- | |
*/ | |
/** | |
* 长文本加密 | |
* @param {string} string 待加密长文本 | |
* @returns {string} 加密后的base编码 | |
* */JSEncrypt.prototype.encryptLong = function (string) { | |
var k = this.getKey(); | |
try { | |
var ct = ""; | |
//RSA每次加密bytes,需要辅助方法判断字符串截取位置 | |
//.获取字符串截取点 | |
var bytes = new Array(); | |
bytes.push(); | |
var byteNo =; | |
var len, c; | |
len = string.length; | |
var temp =; | |
for (var i =; i < len; i++) { | |
c = string.charCodeAt(i); | |
if (c >=x010000 && c <= 0x10FFFF) { //特殊字符,如Ř,Ţ | |
byteNo +=; | |
} else if (c >=x000800 && c <= 0x00FFFF) { //中文以及标点符号 | |
byteNo +=; | |
} else if (c >=x000080 && c <= 0x0007FF) { //特殊字符,如È,Ò | |
byteNo +=; | |
} else { // 英文以及标点符号 | |
byteNo +=; | |
} | |
if ((byteNo %) >= 114 || (byteNo % 117) == 0) { | |
if (byteNo - temp >=) { | |
bytes.push(i); | |
temp = byteNo; | |
} | |
} | |
} | |
//.截取字符串并分段加密 | |
if (bytes.length >) { | |
for (var i =; i < bytes.length - 1; i++) { | |
var str; | |
if (i ==) { | |
str = string.substring(, bytes[i + 1] + 1); | |
} else { | |
str = string.substring(bytes[i] +, bytes[i + 1] + 1); | |
} | |
var t = k.encrypt(str); | |
ct += t; | |
} | |
; | |
if (bytes[bytes.length -] != string.length - 1) { | |
var lastStr = string.substring(bytes[bytes.length -] + 1); | |
ct += k.encrypt(lastStr); | |
} | |
return hexb64(ct); | |
} | |
var t = k.encrypt(string); | |
var y = hexb64(t); | |
return y; | |
} catch (ex) { | |
console.log(ex); | |
return false; | |
} | |
}; | |
/** | |
* 长文本解密 | |
* @param {string} string 加密后的base编码 | |
* @returns {string} 解密后的原文 | |
* */JSEncrypt.prototype.decryptLong = function (string) { | |
var k = this.getKey(); | |
var maxLength =; | |
try { | |
var string = btohex(string); | |
var ct = ""; | |
if (string.length > maxLength *) { | |
var lt = string.match(/.{,256}/g); //128位解密。取256位 | |
lt.forEach(function (entry) { | |
var t = k.decrypt(entry); | |
ct += t; | |
}); | |
return ct; | |
} | |
var y = k.decrypt(string); | |
return y; | |
} catch (ex) { | |
return false; | |
} | |
}; | |
function hexb64(h) { | |
var bmap="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | |
var bpadchar="="; | |
var i; | |
var c; | |
var ret = ""; | |
for(i =; i+3 <= h.length; i+=3) { | |
c = parseInt(h.substring(i,i+),16); | |
ret += bmap.charAt(c >> 6) + b64map.charAt(c & 63); | |
} | |
if(i+ == h.length) { | |
c = parseInt(h.substring(i,i+),16); | |
ret += bmap.charAt(c << 2); | |
} | |
else if(i+ == h.length) { | |
c = parseInt(h.substring(i,i+),16); | |
ret += bmap.charAt(c >> 2) + b64map.charAt((c & 3) << 4); | |
} | |
while((ret.length &) > 0) ret += b64padchar; | |
return ret; | |
} |
一个简单的测试页面,就不做前后台衔接了,只是在前提用后台生成的公钥进行加密,然后后台main方法解密一下。
<html> | |
<head> | |
<title>MyHtml.html</title> | |
<meta name="keywords" content="keyword,keyword2,keyword3"> | |
<meta name="description" content="this is my page"> | |
<meta name="content-type" content="text/html; charset=UTF-"> | |
<script src="${request.contextPath}/js/jquery-.3.1.min.js"></script> | |
<script src="${request.contextPath}/js/jsencrypt.js?v="></script> | |
<script src="${request.contextPath}/js/RsaJs.js?v="></script> | |
<script type="text/javascript"> | |
$(function() { | |
$('#submit').click(function() { | |
var data = $('#msg').val(); | |
// 公钥 | |
var publickey = $('#publickey').val(); | |
// 使用jsencrypt库加密前端参数 | |
var jsencrypt = new JSEncrypt(); | |
jsencrypt.setPublicKey(publickey); | |
// 这里调用长文本的加密方法 | |
var ecodeMsg = jsencrypt.encryptLong(data); | |
$('#ecodeMsg').val(ecodeMsg); | |
}); | |
}); | |
</script> | |
</head> | |
<body> | |
需要加密的内容:</br><textarea id="msg" name="msg" rows="" cols="60"></textarea></br> | |
公钥:</br><textarea id="publickey" rows="" cols="60"></textarea></br> | |
密文:</br><textarea id="ecodeMsg" rows="" cols="60"></textarea> | |
<br/> | |
<br/> | |
<input id="submit" type="button" value="加密" /> | |
</body> | |
</body> | |
</html> |
简单的测试,页面获取密文
image.png
后台main解密一下
import com.wzh.config.utils.RsaUtils; | |
/** | |
* <一句话功能描述> | |
* <功能详细描述> | |
* @author wzh | |
* @version-12-16 23:31 | |
* @see [相关类/方法] (可选) | |
**/public class RsaManTest { | |
public static void main(String[] args) { | |
String msg = RsaUtils.decryptDataByPrivate("XsMCYaNhdx2pJXebCgl3g3pF7FX9KrPY+gtwgbQs0Q1mqJL4VHqQytxOJfUwXHLP/hLck80AWSctJ29/dB4IQ2mSbcO4OInAJMkPwqWsnh1E9bFlFP2KjQ5RBVngb//IiSgBSFo8NR00y1/h47CrNch6ljW1nCLG82Qk2olhfI=", | |
"MIICdgIBADANBgkqhkiGw0BAQEFAASCAmAwggJcAgEAAoGBAIlXVcewyLHiE9aidGpcR7hMT5opgXTGUfWX2idJnr2b++Edzg8Id6FOFgmzAWBkoSfAZodkTgZcOUjc/F/ZDQbuSDSBiakVb6T+WK53oVKb8a9Y9Ouq63Bwqihq9kqp9+aLSRcRqyLkF/tGg87dGMEMGeHq2djOI8rrbXGlyQNhAgMBAAECgYAHMCD3MJNwa+qp4xrArk+6n5PS97UkzXRgrC/ounuqZM2L/KlaNBE+yf1xSIMb7mhY0kMLdv52asE8xQQYaB28WVJHxExgcMDDdhtOp+4++WEe2xPWrJSUWSvLQWxrJ01yw9smezt1N0qrA4psJ+eq3JP366wZ3GLFhq0BOW8wGQJBANZyo2Dm1aP4aBHfLSa51YQJ+izFJyC53Hn43CPhXNDT08GO6tuZ9KiIJkk7rNdDrkAFnR4cDwEZT0C6Fk/VWA8CQQCj8+/fGGV+z3D0VCFkPf+VlZdNuITFD+wzlaalJZq5mYtzdHAZ3AGxpNs+Qbykq4TSb5XhfQoZuBeMD3brCv2PAkEA1Wuby4mP3zMOJ5MrtVnG9DSVxU6kxT4T/VO9ivvzSmU2XnDkrY7H3Z46NDHurwHNfivYFSopiJdut2U7ZVJW4wJAc/FsLq7IB9eXH5HnU0Zs2lHBgBr++YT7Gre3844WTy6AaZNsOz1UjVXyHaLLTwBkm5SBv8Z3QBzpugitpiZNjQJAR9SqcarmoE7xNxOJ0gqj2Pht26rAfcQogpoVvbvJTgGlpvEnnV2wEX47mzLFewzQ2nxjwAJPdzr+rOKXKnDCFA=="); | |
System.out.println(msg); | |
} | |
} |
前台加密,后台解密成功