RSA 算法是一种非对称加解密算法。服务方生成一对 RSA 密钥,即公钥 + 私钥,将公钥提供给调用方,调用方使用公钥对数据进行加密后,服务方根据私钥进行解密。

一、基础工具类

  下方工具类涵盖了生成 RSA 密钥对、加密、解密的方法,并附上了测试过程。

package com.test.utils;import lombok.extern.slf4j.Slf4j;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.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;@Slf4j
public class RSAUtil {public static final String KEY_ALGORITHM = "RSA";private static final String PUBLIC_KEY = "RSAPublicKey";private static final String PRIVATE_KEY = "RSAPrivateKey";// 1024 bits 的 RSA 密钥对,最大加密明文大小private static final int MAX_ENCRYPT_BLOCK = 117;// 1024 bits 的 RSA 密钥对,最大解密密文大小private static final int MAX_DECRYPT_BLOCK = 128;// 生成密钥对public static Map<String, Object> initKey(int keysize) throws Exception {KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);// 设置密钥对的 bit 数,越大越安全keyPairGen.initialize(keysize);KeyPair keyPair = keyPairGen.generateKeyPair();// 获取公钥RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();// 获取私钥RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();Map<String, Object> keyMap = new HashMap<>(2);keyMap.put(PUBLIC_KEY, publicKey);keyMap.put(PRIVATE_KEY, privateKey);return keyMap;}// 获取公钥字符串public static String getPublicKeyStr(Map<String, Object> keyMap) {// 获得 map 中的公钥对象,转为 key 对象Key key = (Key) keyMap.get(PUBLIC_KEY);// 编码返回字符串return encryptBASE64(key.getEncoded());}// 获取私钥字符串public static String getPrivateKeyStr(Map<String, Object> keyMap) {// 获得 map 中的私钥对象,转为 key 对象Key key = (Key) keyMap.get(PRIVATE_KEY);// 编码返回字符串return encryptBASE64(key.getEncoded());}// 获取公钥public static PublicKey getPublicKey(String publicKeyString) throws NoSuchAlgorithmException, InvalidKeySpecException {byte[] publicKeyByte = Base64.getDecoder().decode(publicKeyString);X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyByte);KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);return keyFactory.generatePublic(keySpec);}// 获取私钥public static PrivateKey getPrivateKey(String privateKeyString) throws Exception {byte[] privateKeyByte = Base64.getDecoder().decode(privateKeyString);PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyByte);KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);return keyFactory.generatePrivate(keySpec);}/*** BASE64 编码返回加密字符串** @param key 需要编码的字节数组* @return 编码后的字符串*/public static String encryptBASE64(byte[] key) {return new String(Base64.getEncoder().encode(key));}/*** BASE64 解码,返回字节数组** @param key 待解码的字符串* @return 解码后的字节数组*/public static byte[] decryptBASE64(String key) {return Base64.getDecoder().decode(key);}/*** 公钥加密** @param text         待加密的明文字符串* @param publicKeyStr 公钥* @return 加密后的密文*/public static String encrypt1(String text, String publicKeyStr) {try {log.info("明文字符串为:[{}]", text);Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(publicKeyStr));byte[] tempBytes = cipher.doFinal(text.getBytes("UTF-8"));return Base64.getEncoder().encodeToString(tempBytes);} catch (Exception e) {throw new RuntimeException("加密字符串[" + text + "]时遇到异常", e);}}/*** 私钥解密** @param secretText    待解密的密文字符串* @param privateKeyStr 私钥* @return 解密后的明文*/public static String decrypt1(String secretText, String privateKeyStr) {try {// 生成私钥Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);cipher.init(Cipher.DECRYPT_MODE, getPrivateKey(privateKeyStr));// 密文解码byte[] secretTextDecoded = Base64.getDecoder().decode(secretText.getBytes("UTF-8"));byte[] tempBytes = cipher.doFinal(secretTextDecoded);return new String(tempBytes);} catch (Exception e) {throw new RuntimeException("解密字符串[" + secretText + "]时遇到异常", e);}}public static void main(String[] args) throws Exception {Map<String, Object> keyMap;String cipherText;// 原始明文String content = "春江潮水连海平,海上明月共潮生。滟滟随波千万里,何处春江无月明。";// 生成密钥对keyMap = initKey(1024);String publicKey = getPublicKeyStr(keyMap);log.info("公钥:[{}],长度:[{}]", publicKey, publicKey.length());String privateKey = getPrivateKeyStr(keyMap);log.info("私钥:[{}],长度:[{}]", privateKey, privateKey.length());// 加密cipherText = encrypto(content, publicKey);log.info("加密后的密文:[{}],长度:[{}]", cipherText, cipherText.length());// 解密String plainText = decrypt1(cipherText, privateKey);log.info("解密后明文:[{}]", plainText);}
}

  运行上述 main 函数,输出如下所示,可见加解密成功。

公钥:[MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVk5GORH7kMgZBI7xN6dvlQ6NXO8/emNe/KOxFJRI1r49ecxSb41IBV7rcf+5z7hihbhXqUm65d3O0xcqDOqRYI0LgBxERsX0cPcAl7zSGDgynUC8jlTxBWlQjwb758vvmDRX3g5v0btUPObcyB1IgM3cpVDmsb5Obcl0Wxk2gGwIDAQAB],长度:[216]
私钥:[MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJWTkY5EfuQyBkEjvE3p2+VDo1c7z96Y178o7EUlEjWvj15zFJvjUgFXutx/7nPuGKFuFepSbrl3c7TFyoM6pFgjQuAHERGxfRw9wCXvNIYODKdQLyOVPEFaVCPBvvny++YNFfeDm/Ru1Q85tzIHUiAzdylUOaxvk5tyXRbGTaAbAgMBAAECgYBFLe5JNYbWtghMgmGqS3onrEPUqdY3ZsuKHzw/sqicaelOTr1+aAHamx/Ssdywg7Oing7GxH9vij7aogxK64lsf/OD9Eq2DCvpbNValV/GVABRcCQLO6neHWujen3ex13ky1qtqCS6oZgbqDZyFAj5EGydJdbAgGTZ/rU0Mqbn+QJBANzi3i7a14KXRJYXx7r1UcTVbs30P0jk2F5SaPYsM0UW4Ec5qmqCv6mCYn6GHCJnlJiHvYt9QOUvWB9xuwHtXg0CQQCtWrITaT7P/jHqxzVc02nM420sNtOCrkSy60gZtfNSWFtTl/ddz2ACrN+fbfolUJhHaus3+ekyFoqpIdfNkpTHAkEAlrwO6SSYWtrFiDOULiZI9ay837klEqZwbPWKASwqlKRGyvQ0MlklWBTNCBCW1Heg9PH0zGLeTUggt9yRxH/qZQJAGaGhVtFm4iX5h3cw4qq3p/2wdKseluHhcnrrTDHk6jX6Ot/rSUmSLpMU9WOzarUB7v1WDg67dhZzJhLE77ZOnQJAAe/5aqiYn2SvAxMislav4PcNnbqnHVH+mmRlRP0xeuOKQQzAEn2nqYBY7vCUw3+SYGxe+EbNMLnrcHvTpQKPsw==],长度:[848]
明文字符串为:[春江潮水连海平,海上明月共潮生。滟滟随波千万里,何处春江无月明。]
加密后的密文:[iQIlICQHZJUUxslhyUJSjEdTWm+/B/OWhWXZHhX+ji5Zf7FjaNHr2BCb92ZOmA9BxsAPsS3bg1SajGyBP8fYIkKJpiqHuQ70Jt9xgwdTnaazJEw3BXWRIJIGwniKcEHZZQe+PjjZ4XNGaNSqCa8NtbuJJ6dfFYnv/T/6ANdBwyA=],长度:[172]
解密后明文:[春江潮水连海平,海上明月共潮生。滟滟随波千万里,何处春江无月明。]
二、异常及原因分析

  当原始明文长度较小时,使用上述工具类加解密,没有问题。但是当明文长度过长时,会出现加密异常,如下所示。

Exception in thread "main" java.lang.RuntimeException: 加密字符串[春江潮水连海平,海上明月共潮生。滟滟随波千万里,何处春江无月明。江流宛转绕芳甸,月照花林皆似霰。空里流霜不觉飞,汀上白沙看不见。江天一色无纤尘,皎皎空中孤月轮。江畔何人初见月?江月何年初照人?人生代代无穷已,江月年年望相似。]时遇到异常at com.test.utils.RSAUtil.encrypto(RSAUtil.java:100)at com.test.utils.RSAUtil.main(RSAUtil.java:146)
Caused by: javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytesat com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:344)at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:389)at javax.crypto.Cipher.doFinal(Cipher.java:2165)at com.test.utils.RSAUtil.encrypto(RSAUtil.java:97)... 1 more

  原因分析如下。
  RSA 算法一次能加密的明文长度与密钥长度成正比,如 RSA 1024 实际可加密的明文长度最大是 1024 bits。如果小于这个长度怎么办?就需要进行数据补齐(padding),因为如果没有 padding,用户则无法确定解密后内容的真实长度。字符串之类的内容问题还不大,以 0 作为结束符,但对二进制数据就很难理解,因为不确定后面的 0 是内容还是内容结束符。
  只要用到 padding,那么就要占用实际的明文长度。对于 1024 bits(1024 / 8 = 128 byte[] ) 密钥对而言,如果 padding 方式使用默认的 OPENSSL_PKCS1_PADDING(需要占用 11 字节用于填充),则明文长度最多为 128 - 11 = 117 bytes,于是才有 117 字节的说法,即下面这种常见的说法:len_in_byte(原始明文数据)= len_in_bit(key)/ 8 - 11。
  我们一般使用的 padding 标准有 NoPadding、OAEPPadding、PKCS1Padding 等,其中PKCS1 建议的 padding 就占用了 11 个字节。对于 RSA 加密来讲,padding 也是要参与加密的,所以实际的明文只有 117 字节了。
  我们在把明文送给 RSA 加密器前,要确认这个值是不是大于位长,也就是如果接近位长,那么需要先 padding 再分段加密。除非我们是 “定长定量自己可控可理解” 的加密,不需要 padding。

  各种 padding 对输入数据长度的要求总结如下。

  • 私钥加密时的要求
padding 最大数据长度
RSA_PKCS1_PADDING RSA_size - 11
RSA_NO_PADDING RSA_size - 0
RSA_X931_PADDING RSA_size - 2
  • 公钥加密时的要求
padding 最大数据长度
RSA_PKCS1_PADDING RSA_size - 11
RSA_SSLV23_PADDING RSA_size - 11
RSA_X931_PADDING RSA_size - 2
RSA_NO_PADDING RSA_size - 0
RSA_PKCS1_OAEP_PADDING RSA_size - 2 * SHA_DIGEST_LENGTH - 2
三、分段加解密
 // 分段加密public static String encrypt2(String plainText, String publicKeyStr) throws Exception {log.info("明文:[{}],长度:[{}]", plainText, plainText.length());byte[] plainTextArray = plainText.getBytes("UTF-8");PublicKey publicKey = getPublicKey(publicKeyStr);Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);cipher.init(Cipher.ENCRYPT_MODE, publicKey);int inputLen = plainTextArray.length;ByteArrayOutputStream out = new ByteArrayOutputStream();int offSet = 0;int i = 0;byte[] cache;while (inputLen - offSet > 0) {if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {cache = cipher.doFinal(plainTextArray, offSet, MAX_ENCRYPT_BLOCK);} else {cache = cipher.doFinal(plainTextArray, offSet, inputLen - offSet);}out.write(cache, 0, cache.length);i++;offSet = i * MAX_ENCRYPT_BLOCK;}byte[] encryptText = out.toByteArray();out.close();return Base64.getEncoder().encodeToString(encryptText);}// 分段解密public static String decrypt2(String encryptTextHex, String privateKeyStr) throws Exception {byte[] encryptText = Base64.getDecoder().decode(encryptTextHex);PrivateKey privateKey = getPrivateKey(privateKeyStr);Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);cipher.init(Cipher.DECRYPT_MODE, privateKey);int inputLen = encryptText.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(encryptText, offSet, MAX_DECRYPT_BLOCK);} else {cache = cipher.doFinal(encryptText, offSet, inputLen - offSet);}out.write(cache, 0, cache.length);i++;offSet = i * MAX_DECRYPT_BLOCK;}byte[] plainText = out.toByteArray();out.close();return new String(plainText);}

  备注:当密钥对改为 2048 bits 时,最大加密明文大小为 2048 (bits) / 8 - 11(byte) = 245 byte,上述代码中的两个常量就变更为:

 // 2048 bits 的 RSA 密钥对,最大加密明文大小private static final int MAX_ENCRYPT_BLOCK = 245;// 2048 bits 的 RSA 密钥对,最大解密密文大小private static final int MAX_DECRYPT_BLOCK = 256;

  PKCS1 和 PKCS8 的区别如下。

  • 以 “-----BEGIN RSA PRIVATE KEY-----” 开头,以 “-----END RSA PRIVATE KEY-----” 结束的就是 PKCS1 格式;
  • 以 “-----BEGIN PRIVATE KEY-----” 开头,以 “-----END PRIVATE KEY-----” 结束的就是 PKCS8 格式。

  通常 JAVA 中需要 PKCS8 格式的密钥。

  在线 RSA 加解密网站:RSA 加解密。

文章参考:
  • RSA算法明文长度介绍
  • RSA-Java加解密

RSA 加解密(Java 实现)相关推荐

  1. RSA加解密用途简介及java示例

    在公司当前版本的中间件通信框架中,为了防止非授权第三方和到期客户端的连接,我们通过AES和RSA两种方式的加解密策略进行认证.对于非对称RSA加解密,因为其性能耗费较大,一般仅用于认证连接,不会用于每 ...

  2. openresty 与 java RSA加解密

    上一篇搞定了openresty与java之间的aes加解密.这一篇就来说说openresty与java之间RSA的加解密.在测试的过程中.发现了与aes同样的问题.就是openresty支持的填充模式 ...

  3. Java中的RSA加解密工具类:RSAUtils

    本人手写已测试,大家可以参考使用 package com.mirana.frame.utils.encrypt;import com.mirana.frame.utils.log.LogUtils; ...

  4. 与非java语言使用RSA加解密遇到的问题:algid parse error, not a sequence

    遇到的问题 在一个与Ruby语言对接的项目中,决定使用RSA算法来作为数据传输的加密与签名算法.但是,在使用Ruby生成后给我的私钥时,却发生了异常:IOException: algid parse ...

  5. 前后端java+vue 实现rsa 加解密与摘要签名算法

    RSA有两个密钥,一个是公开的,称为公开密钥:一个是私密的,称为私密密钥. 特点: 公开密钥是对大众公开的,私密密钥是服务器私有的,两者不能互推得出. 用公开密钥对数据进行加密,私密密钥可解密:私密密 ...

  6. RSA加解密,.net公钥/私钥兼容java

    背景介绍 之前老程序使用.net进行数据的RSA加解密,现在用JAVA重写,但是.net的公钥和私钥是xml格式,跟java的不一样,需要手动转换一下.目前网上的大部分都是java转.net.我这里来 ...

  7. RSA加解密的OAEP MGF1 填充解析

    RSA加解密的OAEP MGF1 填充解析 加密时的填充 PKCS#1 v2.1: RSA密码学规范中关于 OAEP的模式的讲解如下: RSAES-OAEP-ENCRYPT (( n, e), M, ...

  8. rsa加解密 --- jsencrypt.min.js --- 支持长字符串分段加解密

    前端 + rsa加解密 + jsencrypt.min.js–(新增超长字符分段加解密) 分享2种,分段 和 不分段 加解密 --话不多说,直接上代码!~ 最终效果: 首先引入2个js <scr ...

  9. openssl在多平台和多语言之间进行RSA加解密注意事项

    首先说一下平台和语言: 系统平台为CentOS6.3,RSA加解密时使用NOPADDING进行填充 1)使用C/C++调用系统自带的openssl 2)Android4.2模拟器,第三方openssl ...

  10. SHA256withRSA签名,RSA加解密

    SHA256withRSA签名,RSA加解密 简介 加密.签名流程图 具体代码实现 简介 项目用到了加密相关知识,主要包括加解密和签名过程.文章用java简单实现这一过程的工具类.(不包括公私钥的生成 ...

最新文章

  1. 译 | 缓存穿透问题导致Facebook史上最严重事故之一
  2. mysql Integer Types (Exact Value) - INTEGER, INT, SMALLINT, TINYINT, MEDIUMINT, BIGINT
  3. gson格式化参数 对象转Map
  4. 我珍藏的内容分享给大家
  5. 《例说51单片机(C语言版)(第3版)》一1.7 实时练习
  6. java.sql.SQLException: Value'0000-00-00'异常解决办法
  7. 为什么模板函数的声明和实现都放在.h文件中
  8. Java设计模式之单例模式(Singleton Pattern)
  9. 模糊综合评价(清风建模学习笔记)
  10. 小米游戏本8代加装固态硬盘系统迁移
  11. 解决EPSON R230打印机驱动程序安装错误
  12. 【LoadBalancer】SpringCloud微服务组件LoadBalancer
  13. 读书笔记 - 《格鲁夫给经理人的第一课》
  14. oracle面试题答案,Oracle面试题笔试题及参考答案
  15. 【Spring教程】框架体系介绍
  16. 《Java SE实战指南》13-07:final修饰符
  17. 解读72篇DeepMind深度强化学习论文
  18. 线性稳压电源和开关电源到底有什么区别
  19. 360浏览器下载文件成功但文件异常问题
  20. 如何用代理解决api接口跨域问题

热门文章

  1. CAD二次开发(VB)代码整理
  2. 实用工具篇--华为eNSP下载及安装
  3. 【python】爬取元素周期表的元素信息
  4. python中function是什么意思_Python中的Function定义方法
  5. JAVA实现简单电话簿功能
  6. 吉大java是选修课么,吉大校园网客户端 Java 版
  7. uygurqa输入法android,uygurqa键盘输入法
  8. c语言数组回文数编写字符串,回文数C语言(示例代码)
  9. UML工具Visual Paradigm入门:业务流程建模 (BPM) 教程
  10. lol一直显示服务器满,LOL服务器爆满!排队两个小时你敢信?网友感慨原来大家都在啊...