非对称加密-RSA公钥加密,私钥解密,私钥加签,公钥验签
一、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