RSA 加密解密,客戶端JS加密,服務端JAVA解密

因為Rsa加密的代碼都是比較通用的,所以沒有特意去整合,這裡參照著兩位大神的代碼重新寫了一遍,做了一些簡單的修改,符合本地運行環境


服務端代參照:http://www.cnblogs.com/zhujiabin/p/7118126.html
客戶端代碼參照:https://jackiedark.github.io/2018/02/05/JSEncrypt%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E6%AE%B5%E5%8A%A0%E8%A7%A3%E5%AF%86/

JS加密依賴:jsencrypt.jsGithub地址:https://github.com/travist/jsencrypt可客戶端儘量依賴JAVA自帶的Jar,只是Base64加密的時候額外依賴了apache的工具類commons-net-3.3.jar

服務端工RSA工具類

<code>package com.wzh.config.utils; import org.apache.commons.net.util.Base64; import org.apache.log4j.Logger; import javax.crypto.Cipher; import java.io.ByteArrayOutputStream; import java.security.*; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.HashMap; import java.util.Map; /** * * * @author wzh * @version 2018-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 = "MD5withRSA"; /** * 獲取公鑰的key */ private static final String PUBLIC_KEY = "RsaPublicKey"; /** * 獲取私鑰的key */ private static final String PRIVATE_KEY = "RsaPrivateKey"; /** * RSA最大加密明文大小 */ private static final int MAX_ENCRYPT_BLOCK = 117; /** * RSA最大解密密文大小 */ private static final int MAX_DECRYPT_BLOCK = 128; /** * Base64字符串解碼為二進制數據 * @param base64 * @return 二進制數據 * @throws Exception */ public static byte[] decodeBase64(String base64) throws Exception { return Base64.decodeBase64(base64.getBytes()); } /** * 二進制數據編碼為Base64字符串 * @param bytes * @return Base64字符串 * @throws Exception */ public static String encodeBase64(byte[] bytes) throws Exception { return new String(Base64.encodeBase64(bytes)); } /** * 生成秘鑰對 * @return 返回公鑰和私鑰的Map集合 * @throws Exception */ public static Map 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 keyMap = new HashMap(2); // 公鑰 keyMap.put(PUBLIC_KEY, publicKey); // 私鑰 keyMap.put(PRIVATE_KEY, privateKey); return keyMap; } /** * 獲取私鑰 * @param keyMap 秘鑰對Map * @return 私鑰字符串 * @throws Exception */ public static String getPrivateKey(Map keyMap) throws Exception { Key key = (Key) keyMap.get(PRIVATE_KEY); return encodeBase64(key.getEncoded()); } /** * 獲取公鑰字符串 * @param keyMap 秘鑰對Map * @return 公鑰字符串 * @throws Exception */ public static String getPublicKey(Map keyMap) throws Exception { Key key = (Key) keyMap.get(PUBLIC_KEY); return encodeBase64(key.getEncoded()); } /** * 使用私鑰生成數字簽名 * @param data 使用私鑰加密的數據 * @param privateKey 是喲啊字符串 * @return 數字簽名 * @throws Exception */ public static String sign(byte[] data, String privateKey) throws Exception { // 獲取byte數組 byte[] keyBytes = decodeBase64(privateKey); // 構造PKCS8EncodedKeySpec對象 PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); // 指定的加密算法 KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); // 取私鑰匙對象 PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec); // 用私鑰對信息生成數字簽名 Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); signature.initSign(privateK); signature.update(data); return encodeBase64(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 = decodeBase64(publicKey); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); // 構造X509EncodedKeySpec對象 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(decodeBase64(sign)); } /** * 私鑰加密 * @param data 需要加密的數據 * @param privateKey 私鑰 * @return 加密後的數據 * @throws Exception */ public static byte[] encryptByPrivateKey(byte[] data, String privateKey) throws Exception { byte[] keyBytes = decodeBase64(privateKey); PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); Key privateK = keyFactory.generatePrivate(pkcs8KeySpec); Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); cipher.init(Cipher.ENCRYPT_MODE, privateK); int inputLen = data.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, offSet, MAX_ENCRYPT_BLOCK); } else { cache = cipher.doFinal(data, offSet, inputLen - offSet); } out.write(cache, 0, 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 = decodeBase64(publicKey); X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); Key publicK = keyFactory.generatePublic(x509KeySpec); // 對數據加密 Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); cipher.init(Cipher.ENCRYPT_MODE, publicK); int inputLen = data.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, offSet, MAX_ENCRYPT_BLOCK); } else { cache = cipher.doFinal(data, offSet, inputLen - offSet); } out.write(cache, 0, 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 = decodeBase64(privateKey); PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); Key privateK = keyFactory.generatePrivate(pkcs8KeySpec); Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); cipher.init(Cipher.DECRYPT_MODE, privateK); int inputLen = encryptedData.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(encryptedData, offSet, MAX_DECRYPT_BLOCK); } else { cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet); } out.write(cache, 0, 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 = decodeBase64(publicKey); X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); Key publicK = keyFactory.generatePublic(x509KeySpec); Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); cipher.init(Cipher.DECRYPT_MODE, publicK); int inputLen = encryptedData.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(encryptedData, offSet, MAX_DECRYPT_BLOCK); } else { cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet); } out.write(cache, 0, 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 = encodeBase64(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 = decodeBase64(data); //以utf-8的方式生成字符串 temp = new String(decryptByPrivateKey(rs, PRIVATEKEY),"UTF-8"); } catch (Exception e) { e.printStackTrace(); } return temp; } public static void main(String[] args) { try { Map 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(); } } } /<code>

首先測試一下工具類,main函數跑一下,成功驗證簽名,加密,解密


image.png

客戶端JS代碼,需要JSEncrypt庫,前文有給出github地址,這裡對這個庫做一個簡單的擴展,因為RSA長文本超過秘鑰長度要報錯,所以需要擴展修改下

<code>/** * --------------------------- * 此JS需加載JSEncrypt庫的後面,加密解密調用著兩個方法 * --------------------------- */ /** * 長文本加密 * @param {string} string 待加密長文本 * @returns {string} 加密後的base64編碼 * */ JSEncrypt.prototype.encryptLong = function (string) { var k = this.getKey(); try { var ct = ""; //RSA每次加密117bytes,需要輔助方法判斷字符串截取位置 //1.獲取字符串截取點 var bytes = new Array(); bytes.push(0); var byteNo = 0; var len, c; len = string.length; var temp = 0; for (var i = 0; i < len; i++) { c = string.charCodeAt(i); if (c >= 0x010000 && c <= 0x10FFFF) { //特殊字符,如Ř,Ţ byteNo += 4; } else if (c >= 0x000800 && c <= 0x00FFFF) { //中文以及標點符號 byteNo += 3; } else if (c >= 0x000080 && c <= 0x0007FF) { //特殊字符,如È,Ò byteNo += 2; } else { // 英文以及標點符號 byteNo += 1; } if ((byteNo % 117) >= 114 || (byteNo % 117) == 0) { if (byteNo - temp >= 114) { bytes.push(i); temp = byteNo; } } } //2.截取字符串並分段加密 if (bytes.length > 1) { for (var i = 0; i < bytes.length - 1; i++) { var str; if (i == 0) { str = string.substring(0, bytes[i + 1] + 1); } else { str = string.substring(bytes[i] + 1, bytes[i + 1] + 1); } var t1 = k.encrypt(str); ct += t1; } ; if (bytes[bytes.length - 1] != string.length - 1) { var lastStr = string.substring(bytes[bytes.length - 1] + 1); ct += k.encrypt(lastStr); } return hex2b64(ct); } var t = k.encrypt(string); var y = hex2b64(t); return y; } catch (ex) { console.log(ex); return false; } }; /** * 長文本解密 * @param {string} string 加密後的base64編碼 * @returns {string} 解密後的原文 * */ JSEncrypt.prototype.decryptLong = function (string) { var k = this.getKey(); var maxLength = 128; try { var string = b64tohex(string); var ct = ""; if (string.length > maxLength * 2) { var lt = string.match(/.{1,256}/g); //128位解密。取256位 lt.forEach(function (entry) { var t1 = k.decrypt(entry); ct += t1; }); return ct; } var y = k.decrypt(string); return y; } catch (ex) { return false; } }; function hex2b64(h) { var b64map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var b64padchar="="; var i; var c; var ret = ""; for(i = 0; i+3 <= h.length; i+=3) { c = parseInt(h.substring(i,i+3),16); ret += b64map.charAt(c >> 6) + b64map.charAt(c & 63); } if(i+1 == h.length) { c = parseInt(h.substring(i,i+1),16); ret += b64map.charAt(c << 2); } else if(i+2 == h.length) { c = parseInt(h.substring(i,i+2),16); ret += b64map.charAt(c >> 2) + b64map.charAt((c & 3) << 4); } while((ret.length & 3) > 0) ret += b64padchar; return ret; } /<code>

一個簡單的測試頁面,就不做前後臺銜接了,只是在前提用後臺生成的公鑰進行加密,然後後臺main方法解密一下。

<code> MyHtml.html 需要加密的內容: 公鑰: 密文:

/<code>