相关文章推荐
精明的领带  ·  《一年級》陳學冬遭家長發難道歉 ...·  1 年前    · 
干练的火锅  ·  文化和旅游部人力资源社会保障部国家乡村振兴局 ...·  1 年前    · 
难过的领结  ·  健身房-高雄日航酒店·  1 年前    · 
果断的风衣  ·  超值特惠組【郭正宏旅行速寫本+旅圖速寫套書】 ...·  1 年前    · 
严肃的烤土司  ·  北京房山十渡风景区游玩攻略·  1 年前    · 
小百科  ›  小课堂 | RSA加密数据太长报错解决开发者社区
string rsa 腾讯课堂 云课堂
痴情的可乐
3 年前
作者头像
孟君
0 篇文章

小课堂 | RSA加密数据太长报错解决

前往专栏
腾讯云
开发者社区
文档 意见反馈 控制台
首页
学习
活动
专区
工具
TVP
文章/答案/技术大牛
发布
首页
学习
活动
专区
工具
TVP
返回腾讯云官网
社区首页 > 专栏 > 孟君的编程札记 > 小课堂 | RSA加密数据太长报错解决

小课堂 | RSA加密数据太长报错解决

作者头像
孟君
发布 于 2022-04-19 09:30:26
2K 0
发布 于 2022-04-19 09:30:26
举报

很多时候,我们需要在开发中对某些 数据加密 ,比如登录对密码进行RSA加密。 RSA加密算法是一种非对称加密算法,公钥加密私钥解密。RSA是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的。 但是,如果加密数据比较长的话,可能会出现问题,如: javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes

1、问题重现

我们可以编写一个RSA的简单实现,如:

import java.io.UnsupportedEncodingException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
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 org.apache.commons.codec.binary.Base64;
public final class RsaUtils {
  private RsaUtils() {
   * 获取公钥和私钥对,key为公钥,value为私钥
   * @return
   * @throws NoSuchAlgorithmException
  public static Map<String, String> genKeyPair() throws NoSuchAlgorithmException {
    KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
    keyPairGen.initialize(1024, new SecureRandom());
    KeyPair keyPair = keyPairGen.generateKeyPair();
    RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
    RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
    String publicKeyString = null;
    String privateKeyString = null;
    try {
      publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()), "UTF-8");
      privateKeyString = new String(Base64.encodeBase64(privateKey.getEncoded()), "UTF-8");
    } catch (UnsupportedEncodingException var7) {
      var7.printStackTrace();
    Map<String, String> keyPairMap = new HashMap<>();
    keyPairMap.put("publicKey", publicKeyString);
    keyPairMap.put("privateKey", privateKeyString);
    return keyPairMap;
  public static String encrypt(String str, String publicKey) throws Exception {
    byte[] decoded = Base64.decodeBase64(publicKey);
    RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA")
        .generatePublic(new X509EncodedKeySpec(decoded));
    Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(1, pubKey);
    String outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes("UTF-8")));
    return outStr;
  public static String decrypt(String str, String privateKey) throws Exception {
    byte[] inputByte = Base64.decodeBase64(str.getBytes("UTF-8"));
    byte[] decoded = Base64.decodeBase64(privateKey);
    RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA")
        .generatePrivate(new PKCS8EncodedKeySpec(decoded));
    Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(2, priKey);
    String outStr = new String(cipher.doFinal(inputByte), "UTF-8");
    return outStr;

写一个简单的测试:

public class Main {
  public static void main(String[] args) throws Exception {
    Map<String, String> keyMap = RsaUtils.genKeyPair();
    String publicKey = keyMap.get("publicKey");
    String privateKey = keyMap.get("privateKey");
    System.out.println("publicKey = " + publicKey);
    System.out.println("privateKey = " + privateKey);
    String originValue = "原数据123456,测试一下1111111111111111111111111111111111111222222222222222222221111111111111111111111111111111111111111111111111";
    // 加密前:原数据123456,测试一下
    System.out.println("加密前:" + originValue);
    String encrypt = RsaUtils.encrypt(originValue, publicKey);
    System.out.println("加密后:" + encrypt);
    String decrypt = RsaUtils.decrypt(encrypt, privateKey);
    System.out.println("解密后:" + decrypt);
}

错误重现:

Exception in thread "main" javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes
  at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:346)
  at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:391)
  at javax.crypto.Cipher.doFinal(Cipher.java:2168)
  at com.********.crypto.rsa.RsaUtils.encrypt(RsaUtils.java:58)
  at Main.main(Main.java:19)

2、原因

Cipher rsa = Cipher.getInstance("RSA/ECB/PKCS1Padding");

表示,使用RSA算法,并且加PAD的方式按照PKCS1的标准。

即 输入数据长度小于等于密钥的位数/8-11 ,例如:1024位密钥, 1024/8-11 =117 。不足的部分,程序会自动补齐。加密后的数据还是等于密钥的位数/8。

Cipher提供加解密API,其中RSA非对称加密解密内容长度是有限制的,加密长度不超过117Byte,解密长度不超过128Byte,报错如下: javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes 。

3、解决

既然Cipher加解密有长度限制,那么如果超过 117 bytes ,我们可以 采用分段加密、分段解密的方式进行 。

import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
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 org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.ArrayUtils;
public final class RsaUtils {
  // RSA最大加密明文大小
  private static final int MAX_ENCRYPT_BLOCK = 117;
  // RSA最大解密密文大小
  private static final int MAX_DECRYPT_BLOCK = 128;
  private RsaUtils() {
   * 获取公钥和私钥对,key为公钥,value为私钥
   * @return
   * @throws NoSuchAlgorithmException
  public static Map<String, String> genKeyPair() throws NoSuchAlgorithmException {
    KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
    keyPairGen.initialize(1024, new SecureRandom());
    KeyPair keyPair = keyPairGen.generateKeyPair();
    RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
    RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
    String publicKeyString = null;
    String privateKeyString = null;
    try {
      publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()), "UTF-8");
      privateKeyString = new String(Base64.encodeBase64(privateKey.getEncoded()), "UTF-8");
    } catch (UnsupportedEncodingException var7) {
      var7.printStackTrace();
    Map<String, String> keyPairMap = new HashMap<>();
    keyPairMap.put("publicKey", publicKeyString);
    keyPairMap.put("privateKey", privateKeyString);
    return keyPairMap;
  public static String encrypt(String str, String publicKey) throws Exception {
    byte[] decoded = Base64.decodeBase64(publicKey);
    RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA")
        .generatePublic(new X509EncodedKeySpec(decoded));
    Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
    cipher.init(1, pubKey);
    // 分段加密
    // URLEncoder编码解决中文乱码问题
    byte[] data = URLEncoder.encode(str, "UTF-8").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 = Base64.encodeBase64String(enBytes);
    return outStr;
   * 公钥分段解密
   * @param str
   * @param privateKey
   * @return
   * @throws Exception
  public static String decrypt(String str, String privateKey) throws Exception {
    // 获取公钥
    byte[] decoded = Base64.decodeBase64(privateKey);
    RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA")
        .generatePrivate(new PKCS8EncodedKeySpec(decoded));
    Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
    cipher.init(2, priKey);
    byte[] data = Base64.decodeBase64(str.getBytes("UTF-8"));
    // 返回UTF-8编码的解密信息
    int inputLen = data.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(data, offSet, MAX_DECRYPT_BLOCK);
      } else {
        cache = cipher.doFinal(data, offSet, inputLen - offSet);
      out.write(cache, 0, cache.length);
      offSet = i * 128;
    byte[] decryptedData = out.toByteArray();
    out.close();
    return URLDecoder.decode(new String(decryptedData, "UTF-8"), "UTF-8");

再次测试一下,把测试数据长度加大很多: public class Main { public static void main(String[] args) throws Exception { Map<String, String> keyMap = RsaUtils.genKeyPair(); String publicKey = keyMap.get("publicKey"); String privateKey = keyMap.get("privateKey"); System.out.println("publicKey = " + publicKey); System.out.println("privateKey = " + privateKey); String originValue = "原数据123456,测试一下1111111111111111111111111111111111111222222222222222222221111111111111111111111111111111111111111111111111原数据123456,测试一下1111111111111111111111111111111111111222222222222222222221111111111111111111111111111111111111111111111111原数据123456,测试一下1111111111111111111111111111111111111222222222222222222221111111111111111111111111111111111111111111111111原数据123456,测试一下1111111111111111111111111111111111111222222222222222222221111111111111111111111111111111111111111111111111原数据123456,测试一下1111111111111111111111111111111111111222222222222222222221111111111111111111111111111111111111111111111111原数据123456,测试一下1111111111111111111111111111111111111222222222222222222221111111111111111111111111111111111111111111111111原数据123456,测试一下1111111111111111111111111111111111111222222222222222222221111111111111111111111111111111111111111111111111原数据123456,测试一下1111111111111111111111111111111111111222222222222222222221111111111111111111111111111111111111111111111111原数据123456,测试一下1111111111111111111111111111111111111222222222222222222221111111111111111111111111111111111111111111111111原数据123456,测试一下1111111111111111111111111111111111111222222222222222222221111111111111111111111111111111111111111111111111原数据123456,测试一下1111111111111111111111111111111111111222222222222222222221111111111111111111111111111111111111111111111111原数据123456,测试一下1111111111111111111111111111111111111222222222222222222221111111111111111111111111111111111111111111111111原数据123456,测试一下1111111111111111111111111111111111111222222222222222222221111111111111111111111111111111111111111111111111原数据123456,测试一下1111111111111111111111111111111111111222222222222222222221111111111111111111111111111111111111111111111111原数据123456,测试一下1111111111111111111111111111111111111222222222222222222221111111111111111111111111111111111111111111111111原数据123456,测试一下1111111111111111111111111111111111111222222222222222222221111111111111111111111111111111111111111111111111原数据123456,测试一下1111111111111111111111111111111111111222222222222222222221111111111111111111111111111111111111111111111111"; // 加密前:原数据123456,测试一下 System.out.println("加密前:" + originValue); String encrypt = RsaUtils.encrypt(originValue, publicKey); System.out.println("加密后:" + encrypt);

 
推荐文章
精明的领带  ·  《一年級》陳學冬遭家長發難道歉 郭敬明微博聲援--傳媒--人民網
1 年前
干练的火锅  ·  文化和旅游部人力资源社会保障部国家乡村振兴局关于公布2022年“非 ...
1 年前
难过的领结  ·  健身房-高雄日航酒店
1 年前
果断的风衣  ·  超值特惠組【郭正宏旅行速寫本+旅圖速寫套書】(限量獨家) | 九歌 ...
1 年前
严肃的烤土司  ·  北京房山十渡风景区游玩攻略
1 年前
今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
删除内容请联系邮箱 2879853325@qq.com
小百科 - 百科知识指南
© 2024 ~ 沪ICP备11025650号