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

很多时候,我们需要在开发中对某些数据加密,比如登录对密码进行RSA加密。

RSA加密算法是一种非对称加密算法,公钥加密私钥解密。RSA是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的。

但是,如果加密数据比较长的话,可能会出现问题,如:

javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes

1、问题重现我们可以编写一个RSA的简单实现,如:

代码语言:javascript代码运行次数:0运行复制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 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 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;

}

}

写一个简单的测试:

代码语言:javascript代码运行次数:0运行复制public class Main {

public static void main(String[] args) throws Exception {

Map 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);

}

}错误重现:

代码语言:javascript代码运行次数:0运行复制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、原因代码语言:javascript代码运行次数:0运行复制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,我们可以采用分段加密、分段解密的方式进行。

代码语言:javascript代码运行次数:0运行复制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 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 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);

i++;

offSet = i * 128;

}

byte[] decryptedData = out.toByteArray();

out.close();

return URLDecoder.decode(new String(decryptedData, "UTF-8"), "UTF-8");

}

}

再次测试一下,把测试数据长度加大很多:

代码语言:javascript代码运行次数:0运行复制

public class Main {

public static void main(String[] args) throws Exception {

Map 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);

String decrypt = RsaUtils.decrypt(encrypt, privateKey);

System.out.println("解密后:" + decrypt);

}

}

可以发现,能够正常加解密,至此问题解决。

与您同乐时刻,世界杯历届四强“黑马”盘点:克罗地亚领衔,摩洛哥入围
中国各省区面积所对应的国家,看看你家乡和哪个国家一样大!