RSA加密解密,签名验签简述 + 完美工具类RSAUtils
原创博文,欢迎转载,转载时请务必附上博文链接,感谢您的尊重。
系列文章目录
RSA+AES数据传输的加密解密【篇】,项目实战(专题汇总):
- AES 加密解密简述 + 完美工具类 AESUtils
- RSA 加密解密,签名验签简述 + 完美工具类 RSAUtils
- RSA + AES 加密原理,一线大厂主流的加密手段
- RSA 与 AES 加密效率对比,用调研事实说明问题,用算法原理解决疑惑
- RSA + AES 混合加密策略,真实项目案例,一线大厂的主流HTTP加密交互方式(正在赶稿)
一、RSA简介
RSA是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的。
RSA是被研究得最广泛的公钥算法,从提出到现在已近三十年,经历了各种攻击的考验,逐渐为人们接受,普遍认为是目前最优秀的公钥方案之一。该能够抵抗到目前为止已知的绝大多数密码攻击,已被ISO推荐为公钥数据加密标准。
RSA算法属于非对称加密算法,非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。简单的说是“公钥加密,私钥解密;私钥加密,公钥解密”。
二、参数解释
下面几行是生成 RSA 秘钥对的标准核心代码,我们通过它分析下参数对结果的影响。
// keygen类
KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA");
// 随机种子
keygen.initialize(2048, new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keys = keygen.genKeyPair();
PublicKey publicKey = keys.getPublic();
PrivateKey privateKey = keys.getPrivate();
1. 秘钥长度
RSA密钥是(公钥+模值)、(私钥+模值)分组分发的,单独给对方一个公钥或私钥是没有任何用处,所以我们说的“密钥”其实是它们两者中的其中一组。
而“密钥长度”一般只是指模值的位长度,目前主流可选值:1024、2048、3072、4096… 没有上限,多大都可以使用。但出于安全问题的考虑,低于1024bit的密钥不建议使用。
2. 秘钥种子
初始化keygen时:
- 如果不指定seed(如上范例所示),而是直接 New 一个 SecureRandom 的随机对象,那生成的公私钥也是随机的;
如果指定seed(如下引用所示),那么 secureRandom 结果是一样的,所以生成的公私钥也永远不会变。
String seed = “$%^*%^()(ED47d784sde78”;
SecureRandom secureRandom = new SecureRandom();
secureRandom.setSeed(seed.getBytes());
keygen.initialize(2048, secureRandom);
种子 seed 干嘛用?答:非常有用,而且生产中建议使用。
做测试每次都随机无所谓,但是在生产中很必要。
设想我们 new SecureRandom() 随机生成一对RSA秘钥,公钥都已经发给调用方了,结果我的私钥弄丢了,怎么办?只能再生成一对,然后逐一通知调用方进行更换,很尴尬吧。但是,如果我们使用种子seed生成,然后安全的保存了种子,以后就再不怕把公钥、私钥弄丢了。
二、RSAUtils
工具类 RSAUtils,包含:
- 生成 RSA 密钥对,包含随机、固定的方法;
- RSA 加密与解密,普通方法;
- RSA 加密与解密,限制明文大小的方法;
- SIGN 签名与验签的方法;
代码:RSAUtils.java
import java.nio.charset.StandardCharsets;
import java.security.Key;
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 com.alibaba.fastjson.JSON;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 功能:SHA256withRSA 工具类
* 说明:
* @author Mr.tjm
* @date 2020-5-20 11:25
*/
@SuppressWarnings("all")
public class RSAUtils {
private static final Logger logger = LoggerFactory.getLogger(RSAUtils.class);
// MAX_DECRYPT_BLOCK应等于密钥长度/8(1byte=8bit),所以当密钥位数为2048时,最大解密长度应为256.
// 128 对应 1024,256对应2048
private static final int KEYSIZE = 2048;
// RSA最大加密明文大小
private static final int MAX_ENCRYPT_BLOCK = 117;
// RSA最大解密密文大小
private static final int MAX_DECRYPT_BLOCK = 128;
// 不仅可以使用DSA算法,同样也可以使用RSA算法做数字签名
private static final String KEY_ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
// 默认种子
public static final String DEFAULT_SEED = "$%^*%^()(ED47d784sde78";
// 编码格式
private static final String CODE_FORMATE_UTF8 = "UTF-8";
// - - - - - - - - - - - - - - - - - - - - RSA 生成秘钥对 - - - - - - - - - - - - - - - - - - - - //
/**
* 生成密钥对:Base64 转码的字符串
* @param key
* @return
*/
public static Map<String, String> initKeyBase64Str() throws Exception {
Map<String, String> map = new HashMap<>(2);
Map<String, Key> keyMap = initKey();
PublicKey publicKey = (PublicKey) keyMap.get("PublicKey");
PrivateKey privateKey = (PrivateKey) keyMap.get("PrivateKey");
map.put("PublicKey", new String(Base64.encodeBase64(publicKey.getEncoded())));
map.put("PrivateKey", new String(Base64.encodeBase64(privateKey.getEncoded())));
logger.info("生成密钥 = " + JSON.toJSONString(map));
return map;
}
/**
* 生成默认密钥
*
* @return 密钥对象
*/
public static Map<String, Key> initKey() throws Exception {
return initKey(DEFAULT_SEED);
}
/**
* 生成密钥对:若seed为null,那么结果是随机的;若seed不为null且固定,那么结果也是固定的;
*
* @param seed 种子
* @return 密钥对象
*/
public static Map<String, Key> initKey(String seed) throws Exception {
KeyPairGenerator keygen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
// 如果指定seed,那么secureRandom结果是一样的,所以生成的公私钥也永远不会变
SecureRandom secureRandom = new SecureRandom();
secureRandom.setSeed(seed.getBytes());
// Modulus size must range from 512 to 1024 and be a multiple of 64
keygen.initialize(KEYSIZE, secureRandom);
// 生成一个密钥对,保存在keyPair中
KeyPair keys = keygen.genKeyPair();
PublicKey publicKey = keys.getPublic();
PrivateKey privateKey = keys.getPrivate();
// 将公钥和私钥保存到Map
Map<String, Key> map = new HashMap<>(2);
map.put("PublicKey", publicKey);
map.put("PrivateKey", privateKey);
logger.info("生成密钥 = " + JSON.toJSONString(map));
return map;
}
// - - - - - - - - - - - - - - - - - - - - RSA 加密、解密 - - - - - - - - - - - - - - - - - - - - //
/**
* 获取公钥 PublicKey 信息
* @param 公钥
* @return
*/
public static PublicKey getPublicKey(String pubKeyStr) throws Exception{
byte[] publicKeys = Base64.decodeBase64(pubKeyStr);
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeys);
KeyFactory mykeyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey publicKey = mykeyFactory.generatePublic(publicKeySpec);
logger.info("传入的公钥为:【" + pubKeyStr + "】,转义后的公钥为:【" + publicKey + "】");
return publicKey;
}
/**
* 公钥加密,指定 RSA 方式的 PublicKey 对象
*
* @param str 加密字符串
* @param publicKey 公钥
* @return
*/
public static String encrypt(String str, String publicKey) throws Exception{
// base64编码的公钥
byte[] decoded = Base64.decodeBase64(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(decoded));
// RSA加密
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
String outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes(CODE_FORMATE_UTF8)));
return outStr;
}
/**
* 公钥加密,任意 PublicKey 对象
*
* @param publicKey
* @param encryptData
* @param encode
*/
public static String encrypt(PublicKey publicKey, String encryptData, String encode) throws Exception {
if (publicKey == null) {
throw new Exception("加密公钥为空,请设置。");
}
try {
final Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] output = cipher.doFinal(encryptData.getBytes(encode));
return Base64.encodeBase64String(output);
} catch (Exception e) {
logger.info("加密异常:"+e.getMessage());
return null;
}
}
/**
* 私钥解密,指定 RSA 方式的 PrivateKey 对象
*
* @param str 加密字符串
* @param privateKey 私钥
* @return
*/
public static String decrypt(String str, String privateKey) throws Exception{
// 64位解码加密后的字符串
byte[] inputByte = Base64.decodeBase64(str.getBytes(CODE_FORMATE_UTF8));
// base64编码的私钥
byte[] decoded = Base64.decodeBase64(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(decoded));
// RSA解密
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, priKey);
String outStr = new String(cipher.doFinal(inputByte));
return outStr;
}
/**
* RSA 公钥加密,【限制长度】
*
* @param str 加密字符串
* @param publicKey 公钥
* @return 密文
*/
public static String encryptByPublicKey(String str, String publicKey) throws Exception {
// base64编码的公钥
byte[] keyBytes = decryptBASE64(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(keyBytes));
// RSA加密
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] data = str.getBytes("UTF-8");
// 加密时超过117字节就报错。为此采用分段加密的办法来加密
byte[] enBytes = null;
for (int i = 0; i < data.length; i += MAX_ENCRYPT_BLOCK) {
// 注意要使用2的倍数,否则会出现加密后的内容再解密时为乱码
byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(data, i, i + MAX_ENCRYPT_BLOCK));
enBytes = ArrayUtils.addAll(enBytes, doFinal);
}
String outStr = encryptBASE64(enBytes);
return outStr;
}
/**
* RSA 私钥解密,【限制长度】
*
* @param encryStr 加密字符串
* @param privateKey 私钥
* @return 明文
*/
public static String decryptByPrivateKey(String encryStr, String privateKey) throws Exception {
// base64编码的私钥
byte[] decoded = decryptBASE64(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(decoded));
// RSA解密
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, priKey);
// 64位解码加密后的字符串
byte[] data = decryptBASE64(encryStr);
// 解密时超过128字节报错。为此采用分段解密的办法来解密
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.length; i += MAX_DECRYPT_BLOCK) {
byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(data, i, i + MAX_DECRYPT_BLOCK));
sb.append(new String(doFinal));
}
return sb.toString();
}
/**
* BASE64Encoder 加密
*
* @param data 要加密的数据
* @return 加密后的字符串
*/
private static String encryptBASE64(byte[] data) {
return new String(Base64.encodeBase64(data));
}
/**
* BASE64Encoder 解密
*
* @param data 要解密的数据
* @return 解密后的字节
*/
private static byte[] decryptBASE64(String data) {
return Base64.decodeBase64(data);
}
// - - - - - - - - - - - - - - - - - - - - SIGN 签名,验签 - - - - - - - - - - - - - - - - - - - - //
/**
* 加签:生成报文签名
*
* @param content 报文内容
* @param privateKey 私钥
* @param encode 编码
* @return
*/
public static String doSign(String content, String privateKey, String encode) {
try {
String unsign = Base64.encodeBase64String(content.getBytes(StandardCharsets.UTF_8));
byte[] privateKeys = Base64.decodeBase64(privateKey.getBytes());
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeys);
KeyFactory mykeyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PrivateKey psbcPrivateKey = mykeyFactory.generatePrivate(privateKeySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(psbcPrivateKey);
signature.update(unsign.getBytes(encode));
byte[] signed = signature.sign();
return Base64.encodeBase64String(signed);
} catch (Exception e) {
logger.error("生成报文签名出现异常");
}
return null;
}
/**
* 验证:验证签名信息
*
* @param content 签名报文
* @param signed 签名信息
* @param publicKey 公钥
* @param encode 编码格式
* @return
*/
public static boolean doCheck(String content, String signed, PublicKey publicKey, String encode) {
try {
// 解密之前先把content明文,进行base64转码
String unsigned = Base64.encodeBase64String(content.getBytes(encode));
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(publicKey);
signature.update(unsigned.getBytes(encode));
boolean bverify = signature.verify(Base64.decodeBase64(signed));
return bverify;
} catch (Exception e) {
logger.error("报文验证签名出现异常");
}
return false;
}
}
三、秘钥生成网站
如果只是想随机生成一对秘钥用于测试,也可以使用现有网站,笔者推荐一个:https://www.bejson.com/enc/rsa/
《BEJSON —— RSA 公钥私钥,加密解密在线测试》:
我是「IT无知君」,您的点赞、评论和关注,是我创作的动力源泉。
学无止境,气有浩然,让我们一起加油,天涯未远,江湖有缘再见!!
还没有评论,来说两句吧...