一、RSA加密簡介
RSA加密是一種非對稱加密。可以在不直接傳遞密鑰的情況下,完成解密。這能夠確保信息的安全性,避免了直接傳遞密鑰所造成的被破解的風險。是由一對密鑰來進行加解密的過程,分別稱為公鑰和私鑰。兩者之間有數學相關,該加密算法的原理就是對一極大整數做因數分解的困難性來保證安全性。通常個人保存私鑰,公鑰是公開的(可能同時多人持有)。
二、RSA加密、簽名區別
加密和簽名都是為了安全性考慮,但略有不同。常有人問加密和簽名是用私鑰還是公鑰?其實都是對加密和簽名的作用有所混淆。簡單的說,加密是為了防止信息被泄露,而簽名是為了防止信息被篡改。
總結:公鑰加密、私鑰解密、私鑰簽名、公鑰驗簽。
補充一下js版的RSA加解密和簽名:加密解密最好用的是jsencrypt.js ,簽名驗簽最好用的是jsrsasign.js 。曾經我也用痛苦地用過RSA.js ,但是它很難用,首先是它的參數對我是一種考驗,一開始都不知道那些參數怎么填,才來才慢慢明白,還有就是RSA.js加密是沒有padding的,它每次加密出來的東西只要公鑰和數據一樣,加密出來就是一樣的密文,這個還必須得用 bcprov-jdk15-143.jar
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); final Cipher cipher = Cipher.getInstance("RSA/None/NoPadding", "BC");
這個來解,但是一般的boss系統都不這么干,所以RSA.js並不好用,果斷放棄吧。
1) jsencrypt.js
可以加密解密, 也有簽名驗簽的API, 但是經測試, 貌似簽名驗簽時Java不兼容。
2) jsrsasign.js
雖然名字標識sign, 但是遠不止簽名功能, 也有加密解密的功能. 但是經測試, 貌似加密解密時Java也不太好,報各種錯。
綜上考慮。本人決定兩個js文件共同使用。加密解密使用jsencrypt.js,簽名驗簽使用jsrsasign.js。
三、RSA加密、簽名的方法,代碼例子如下:
前段代碼:
引入js文件
<script th:src="@{public/js/jsencrypt.min.js}" type="text/javascript" charset="utf-8"></script> <script th:src="@{public/js/jsrsasign-all-min.js}" type="text/javascript" charset="utf-8"></script>
隨機生成的公鑰私鑰
// 公鑰 let publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD3XSdz1MnzazBEN5KOfTx0IyVJ\n" + "Z5wb57isrCuHDhnYXwtmdhQalgII0fozeeFpMpAvlnmHC1kpW7XVGvZnLx3bWbCE\n" + "bf+pMSW4kmQuI+5cxRUJbCl7sdaODBrINgERHPICVC18AJLThEVMHyjuR6Jn4zQm\n" + "yYNbReSktY/BrFTvMQIDAQAB\n";
公鑰--pem標准格式的秘鑰字符串, 解析生成秘鑰實例:RSAKey
. 標准的pem格式秘鑰含有開始標記
和結束標記
, 如本文使用的秘鑰:-----BEGIN xxx-----
,-----END xxx-----
.
至於xxx的具體內容不是太重要, 代碼里自動通過正則清洗掉頭和尾標記, 所以真的寫成-----BEGIN xxx-----
也沒有關系.
// 公鑰 --- pem標准格式的秘鑰字符串 let pk = "-----BEGIN PUBLIC KEY-----\n" + publicKey + "-----END PUBLIC KEY-----";
私鑰
//私鑰 let privateKey = "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAPddJ3PUyfNrMEQ3\n" + "ko59PHQjJUlnnBvnuKysK4cOGdhfC2Z2FBqWAgjR+jN54WkykC+WeYcLWSlbtdUa\n" + "9mcvHdtZsIRt/6kxJbiSZC4j7lzFFQlsKXux1o4MGsg2AREc8gJULXwAktOERUwf\n" + "KO5HomfjNCbJg1tF5KS1j8GsVO8xAgMBAAECgYEA6eG1JMrj63jEmStmMb1txG1a\n" + "mu4Q5z2QGgtr2HVXsIIlGEq6tWxyHf7TL4qkuz9onuYKn8n2Eqm44fZtVaBx+5ES\n" + "zRpIvlTvaxmVu0HZ1hYAzUw1XyRnXNMKpL5tT4GCjm8+QGPzlGxgXI1sNg8r9Jaw\n" + "9zRUYeA6LQR9RIMkHWUCQQD8QojjVoGjtiunoh/N8iplhUszZIavAEvmDIE+kVy+\n" + "pA7hvlukLw6JMc7cfTcnHyxDo9iHVIzrWlTuKRq9KWVLAkEA+wgJS2sgtldnCVn6\n" + "tJKFVwsHrWhMIU29msPPbNuWUD23BcKE/vehIyFu1ahNA/TiM40PEnzprQ5JfPxU\n" + "16S78wJANTfMLTnYy7Lo7sqTLx2BuD0wqjzw9QZ4/KVytsJv8IAn65P/PVn4FRV+\n" + "8KEx+3zmF7b/PT2nJRe/hycAzxtmlQJBAMrFwQxEqpXfoAEzx4lY2ZBn/nmaR/SW\n" + "4VNEXCbocVC7qT1j1R5HVMgV13uKiTtq8dUGWmhqsi7x3XayNK5ECPUCQQDZaAN6\n" + "tvIHApz9OLsXSw0jZirQ6KEYdharXbIVDy1W1sVE3lzLbqLdFp1bxAHQIvsYS5PM\n" + "A9veSJh372RLJKkj\n";
私鑰--pem標准格式的秘鑰字符串
//私鑰 --- pem標准格式的秘鑰字符串 let priK = "-----BEGIN PRIVATE KEY-----\n" + privateKey + "-----END PRIVATE KEY-----";
使用jsencrypt.js加密解密
加密:
var encrypt = new JSEncrypt(); encrypt.setPublicKey(publicKey); var encryptMsg = encrypt.encrypt(message); console.log("encryptMsg:" + encryptMsg);
解密:
var encrypt = new JSEncrypt(); encrypt.setPrivateKey(privateKey); var decryptMsg = encrypt .decrypt(message); console.log("decryptMsg:" + decryptMsg);
使用jsrsasign.js 簽名驗簽
簽名:
// 創建 Signature 對象 let signature=new KJUR.crypto.Signature({alg:"MD5withRSA",prvkeypem:priK}); //!這里指定 私鑰 pem格式! signature.updateString(data); let a = signature.sign(); let sign = hextob64(a); console.log(sign);
驗簽:
// 驗證Java的簽名 // 構建Signature實例 // 這里 prvkeypem 放公鑰pem看起來有點怪, 但是這是可行的, 內部還是使用的上文經常出現的 KEYUTIL.getKey(pk) 來生成公鑰實例的 const signature = new KJUR.crypto.Signature({'alg': "MD5withRSA", 'prvkeypem': pk}); //!這里指定 公鑰 pem格式! signature.updateString(data); // 傳入待簽明文 var ab = signature.verify(b64tohex(sign)); console.log("jsrsasign verify java: " + ab);
后端代碼:
package com.qt.xxx.util; import java.io.ByteArrayOutputStream; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Signature; 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; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESKeySpec; import com.alibaba.fastjson.JSONObject; //加密解密工具 public class EncryptUtil { /** * RSA最大加密明文大小 */ private static final int MAX_ENCRYPT_BLOCK = 256; /** * RSA最大解密密文大小 */ private static final int MAX_DECRYPT_BLOCK = 256; //公鑰 public static String PUB_KEY = "xxx"; //私鑰 public static String PRIV_KEY ="xxx"; //------------------------RSA----start--------------------------------- //初始化密鑰對 public static Map<Integer, String> genKeyPairByRSA() { Map<Integer, String> keyMap = new HashMap<Integer, String>(); // 用於封裝隨機產生的公鑰與私鑰 try { // KeyPairGenerator類用於生成公鑰和私鑰對,基於RSA算法生成對象 KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); // 初始化密鑰對生成器,密鑰大小為96-1024位 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.encode(publicKey.getEncoded())); // 得到私鑰字符串 String privateKeyString = new String(Base64.encode((privateKey.getEncoded()))); // 將公鑰和私鑰保存到Map keyMap.put(0, publicKeyString); // 0表示公鑰 keyMap.put(1, privateKeyString); // 1表示私鑰 PUB_KEY = publicKeyString; PRIV_KEY = privateKeyString; } catch (Exception e) { return null; } return keyMap; } /** * RSA公鑰加密 * * @param str 需要加密的字符串 * @param publicKey 公鑰 * @return 公鑰加密后的內容 */ public static String encryptByRSA(String str) { String outStr = null; try { // base64編碼的公鑰 byte[] decoded = Base64.decode(DataUtil.PUB_KEY); RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded)); // RSA加密 Cipher cipher = Cipher.getInstance("RSA"); // android的rsa加密方式是RSA/ECB/NoPadding,而標准jdk是RSA/ECB/PKCS1Padding,如果需要加密時要設置標准jdk的加密方式 //Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding"); cipher.init(Cipher.ENCRYPT_MODE, pubKey); outStr = Base64.encode(cipher.doFinal(str.getBytes("UTF-8"))); } catch (Exception e) { } return outStr; } /** * RSA私鑰解密 * * @param str 加密字符串 * @param privateKey 私鑰 * @return 私鑰解密后的內容 */ public static String decryptByRSA(String str) { String outStr = null; try { // 64位解碼加密后的字符串 byte[] inputByte = Base64.decode(str); // base64編碼的私鑰 byte[] decoded = Base64.decode(DataUtil.PRIV_KEY); RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded)); // RSA解密 Cipher cipher = Cipher.getInstance("RSA"); // android的rsa加密方式是RSA/ECB/NoPadding,而標准jdk是RSA/ECB/PKCS1Padding,如果需要加密時要設置標准jdk的加密方式 //Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding"); cipher.init(Cipher.DECRYPT_MODE, priKey); outStr = new String(cipher.doFinal(inputByte)); } catch (Exception e) { } return outStr; } /** * RSA私鑰簽名 * * @param data 待簽名數據 * @param privateKey 私鑰 * @return 簽名 */ public static String signByRSA(String data, PrivateKey privateKey) throws Exception { byte[] keyBytes = privateKey.getEncoded(); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey key = keyFactory.generatePrivate(keySpec); Signature signature = Signature.getInstance("MD5withRSA"); signature.initSign(key); signature.update(data.getBytes()); return new String(Base64.encode(signature.sign())); } /** * RSA公鑰驗簽 * * @param srcData 原始字符串 * @param publicKey 公鑰 * @param sign 簽名 * @return 是否驗簽通過 */ public static boolean verifyByRSA(String srcData, PublicKey publicKey, String sign) throws Exception { byte[] keyBytes = publicKey.getEncoded(); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PublicKey key = keyFactory.generatePublic(keySpec); Signature signature = Signature.getInstance("MD5withRSA"); signature.initVerify(key); signature.update(srcData.getBytes()); return signature.verify(Base64.decode(sign)); } /** * 獲取公鑰 * * @param publicKey 公鑰字符串 * @return */ public static PublicKey getPublicKeyByRSA(String publicKey) throws Exception { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); byte[] decodedKey = Base64.decode(publicKey); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey); return keyFactory.generatePublic(keySpec); } /** * 獲取私鑰 * * @param privateKey 私鑰字符串 * @return */ public static PrivateKey getPrivateKeyByRSA(String privateKey) throws Exception { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); byte[] decodedKey = Base64.decode(privateKey); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey); return keyFactory.generatePrivate(keySpec); } //------------------------RSA----start--------------------------------- }
注意:
一、android加密的數據服務器上無法解密?
android的rsa加密方式是RSA/ECB/NoPadding,而標准jdk是RSA/ECB/PKCS1Padding,所以加密時要設置標准jdk的加密方式
二、base64編碼。因為不同的設備對字符的處理方式不同,字符有可能處理出錯,不利於傳輸。所以先把數據做base64編碼,變成可見字符,減少出錯
官方提供的base64類,Base64.encode編碼,Base64.decode解碼。用這個會有換行符,需要自定義
三、rsa是非對稱加密算法。依賴於大數計算,加密速度比des慢,通常只用於加密少量數據或密鑰
四、公鑰加密比私鑰加密塊,公鑰解密比私鑰解密慢。加密后的數據大概是加密前的1.5倍
參考:
https://www.cnblogs.com/pcheng/p/9629621.html
https://www.jianshu.com/p/b32fc387d8ad
https://www.cnblogs.com/angelshelter/p/4842729.html
https://www.cnblogs.com/kgdxpr/p/12651650.html
https://www.cnblogs.com/yadongliang/p/11783047.html