javax.crypto.BadPaddingException 在 RSA 解密期间
javax.crypto.BadPaddingException during RSA Decryption
在我的 Java 代码中,我尝试使用 RSA 和 public 密钥加密字符串。字符串是表示图像的 Base64 编码字符串(图像已转换为字符串)。它将使用私钥解密。
在加密过程中,我首先得到一个异常"javax.crypto.IllegalBlockSizeException: Data must not be longer than 190 bytes"。因此,我以 189 块为单位处理字符串(明文),然后解析它。
在解密过程中,我又遇到了一个异常"javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes"。因此,我处理了 byte[](密文),首先将其转换为 String,以 256 块为单位,然后也对其进行解析。
同样,在我的解密过程中,我最终得到一个 "javax.crypto.BadPaddingException: Decryption error" 异常,我一直无法解决。
根据本站高手的推荐,我使用了"OAEPWithSHA-256AndMGF1Padding"。在其他填充方法之后,我什至尝试使用 No Padding 来查看异常是否会消失,但它没有用。我做错了什么?
我能够确定异常是在 - decryptedImagePartial = t.rsaDecrypt(cipherTextTrimmed.getBytes(), privateKey) 行抛出的;
- 在主要方法的解密部分。
如果我的编码习惯不好,请多多包涵。我真的更愿意暂时找出异常背后的错误。
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
public class Tester
{
public KeyPair buildKeyPair() throws NoSuchAlgorithmException
{
final int keySize = 2048;
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(keySize);
return keyPairGenerator.genKeyPair();
}
public byte[] encrypt(PublicKey publicKey, String message) throws Exception
{
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(message.getBytes());
}
public String decrypt(PrivateKey privateKey, byte [] encrypted) throws Exception
{
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(cipher.doFinal(encrypted));
}
public byte[] rsaEncrypt(String watermarkMsg, PublicKey publicKey) throws Exception
{
byte[] cipherText = encrypt(publicKey, watermarkMsg);
return cipherText;
}
public String rsaDecrypt(byte[] cipherText, PrivateKey privateKey) throws Exception
{
String plainText = decrypt(privateKey, cipherText);
return plainText;
}
public static void main(String args[]) throws NoSuchAlgorithmException
{
Tester t = new Tester();
String inputImageFilePath = "<file_path_here";
String stringOfImage = null;
byte[] encryptedImage = null;
byte[] encryptedImagePartial = null;
KeyPair keyPair = t.buildKeyPair();
PublicKey pubKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate()
//-----------IMAGE TO STRING CONVERSION----------------
//The imagetostring() function retrieves the image at the file path and converts it into a Base64 encoded String
try
{
stringOfImage = t.imagetostring(inputImageFilePath);
}
catch(Exception e)
{
System.out.println(e.toString());
}
//-----------ENCRYPTION OF STRING----------------
//The encryption is done in blocks of 189, because earlier I got an exception - "javax.crypto.IllegalBlockSizeException: Data must not be longer than 190 bytes"
try
{
String plaintext = stringOfImage;
String plaintextTrimmed = "";
System.out.println(stringOfImage);
encryptedImage = new byte[15512]; //The size is given as 15512 because the length of the particular string was found to be 15512
while(plaintext!="")
{
if(plaintext.length()>189)
{
plaintextTrimmed = plaintext.substring(0, 189);
plaintext = plaintext.substring(189);
}
else
{
plaintextTrimmed = plaintext;
plaintext = "";
}
encryptedImagePartial = t.rsaEncrypt(plaintextTrimmed, pubKey);
encryptedImage = t.concatenate(encryptedImage, encryptedImagePartial);
System.out.println(encryptedImage.length);
}
}
catch(Exception e)
{
System.out.println(e.toString());
}
t.byteDigest(encryptedImage);
//-----------DECRYPTION OF STRING--------------
//The decryption is done in blocks of 189, because earlier I got an exception - "javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes"
try
{
// The ciphertext is located in the variable encryptedImage which is a byte[]
String stringRepOfCipherText = new String(encryptedImage); String cipherTextTrimmed = "";
String decryptedImagePartial;
String decryptedImage = "";
while(stringRepOfCipherText!="")
{
if(stringRepOfCipherText.length()>189)
{
cipherTextTrimmed = stringRepOfCipherText.substring(0, 189);
stringRepOfCipherText = stringRepOfCipherText.substring(189);
}
else
{
cipherTextTrimmed = stringRepOfCipherText;
stringRepOfCipherText = "";
}
decryptedImagePartial = t.rsaDecrypt(cipherTextTrimmed.getBytes(), privateKey);
decryptedImage = decryptedImage + decryptedImagePartial;
}
}
catch(BadPaddingException e)
{
System.out.println(e.toString());
}
catch(Exception e)
{
System.out.println(e.toString());
}
}
}
此外,我注意到其他一些使用 KeyFactory 生成密钥的示例。谁也能告诉我使用 KeyFactory 和我用过的有什么区别吗?
你不能将密文切割成任意块!
由于您特别要求使用不涉及对称算法的纯 RSA(我强烈建议您不要这样做!),因此您需要做的是:
- 找出适合您的 RSA 配置的 maximum payload size。
- 将你的明文拆分成这个大小的块
- 单独加密每个块,不要简单地连接它们并丢弃块边界!
解密过程中:
- 使用加密后的原始大小将每个密文块传递给解密函数。 不附加任何数据,不创建 "substrings"。
- 连接生成的明文。
理想情况下,您应该使用混合加密方案:
- 生成加密密钥(
encKey
)
- 使用
encKey
的对称算法加密您的图像
- 使用
pubKey
和 RSA 加密 encKey
对称密码可用于不同的操作模式,从而避免此类长度限制。
首先,先把图片编码成base 64是完全没有意义的,现代密码的输入都是字节,图片本身就是字节。如果你想存储一个字符串,你可能需要对密文进行 base 64 编码。
输入块大小确实是190字节。您可以看到 RSA / OAEP here 的 table(不要忘记投票!)。我不确定在那种情况下您为什么要使用 189;然而,我的代码是通用的。输出块大小只是 RSA 的密钥大小,因为它被显式转换为以字节为单位的密钥大小(即使它可能更小)。
在解密过程中,您将密文转换为字符串。但是,Java 中的字符串解码是有损的;如果解码器发现一个不代表字符的字节,那么它将被 静默 丢弃。所以这不会(总是有效),例如导致 BadPaddingException
。不过没关系,我们可以保留二进制密文。
废话不多说,先上一段代码给大家看看。请注意每块 66 字节的密文扩展以及(主要是)解密性能不佳。强烈建议在混合密码系统中使用 AES 和 RSA(对于这个问题不是第一次)。
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;
import javax.crypto.Cipher;
public class Tester {
private static final int KEY_SIZE = 2048;
private static final int OAEP_MGF1_SHA256_OVERHEAD = 66;
public static KeyPair buildKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(KEY_SIZE);
return keyPairGenerator.generateKeyPair();
}
public static void main(String args[]) throws Exception {
KeyPair keyPair = Tester.buildKeyPair();
RSAPublicKey pubKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// assumes the bitLength is a multiple of 8 (check first!)
int keySizeBytes = pubKey.getModulus().bitLength() / Byte.SIZE;
byte[] image = new byte[1000];
Arrays.fill(image, (byte) 'm');
// --- encryption
final Cipher enc;
try {
enc = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("OAEP with MGF-1 using SHA-256 not available in this runtime", e);
}
enc.init(Cipher.ENCRYPT_MODE, pubKey);
int fragmentsize = keySizeBytes - OAEP_MGF1_SHA256_OVERHEAD;
ByteArrayOutputStream ctStream = new ByteArrayOutputStream();
int off = 0;
while (off < image.length) {
int toCrypt = Math.min(fragmentsize, image.length - off);
byte[] partialCT = enc.doFinal(image, off, toCrypt);
ctStream.write(partialCT);
off += toCrypt;
}
byte[] ct = ctStream.toByteArray();
// --- decryption
Cipher dec = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
dec.init(Cipher.DECRYPT_MODE, privateKey);
ByteArrayOutputStream ptStream = new ByteArrayOutputStream();
off = 0;
while (off < ct.length) {
int toCrypt = Math.min(keySizeBytes, ct.length - off);
byte[] partialPT = dec.doFinal(ct, off, toCrypt);
ptStream.write(partialPT);
off += toCrypt;
}
byte[] pt = ptStream.toByteArray();
// mmmm...
System.out.println(new String(pt, StandardCharsets.US_ASCII));
}
}
在我的 Java 代码中,我尝试使用 RSA 和 public 密钥加密字符串。字符串是表示图像的 Base64 编码字符串(图像已转换为字符串)。它将使用私钥解密。
在加密过程中,我首先得到一个异常"javax.crypto.IllegalBlockSizeException: Data must not be longer than 190 bytes"。因此,我以 189 块为单位处理字符串(明文),然后解析它。
在解密过程中,我又遇到了一个异常"javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes"。因此,我处理了 byte[](密文),首先将其转换为 String,以 256 块为单位,然后也对其进行解析。
同样,在我的解密过程中,我最终得到一个 "javax.crypto.BadPaddingException: Decryption error" 异常,我一直无法解决。
根据本站高手的推荐,我使用了"OAEPWithSHA-256AndMGF1Padding"。在其他填充方法之后,我什至尝试使用 No Padding 来查看异常是否会消失,但它没有用。我做错了什么?
我能够确定异常是在 - decryptedImagePartial = t.rsaDecrypt(cipherTextTrimmed.getBytes(), privateKey) 行抛出的; - 在主要方法的解密部分。
如果我的编码习惯不好,请多多包涵。我真的更愿意暂时找出异常背后的错误。
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
public class Tester
{
public KeyPair buildKeyPair() throws NoSuchAlgorithmException
{
final int keySize = 2048;
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(keySize);
return keyPairGenerator.genKeyPair();
}
public byte[] encrypt(PublicKey publicKey, String message) throws Exception
{
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(message.getBytes());
}
public String decrypt(PrivateKey privateKey, byte [] encrypted) throws Exception
{
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(cipher.doFinal(encrypted));
}
public byte[] rsaEncrypt(String watermarkMsg, PublicKey publicKey) throws Exception
{
byte[] cipherText = encrypt(publicKey, watermarkMsg);
return cipherText;
}
public String rsaDecrypt(byte[] cipherText, PrivateKey privateKey) throws Exception
{
String plainText = decrypt(privateKey, cipherText);
return plainText;
}
public static void main(String args[]) throws NoSuchAlgorithmException
{
Tester t = new Tester();
String inputImageFilePath = "<file_path_here";
String stringOfImage = null;
byte[] encryptedImage = null;
byte[] encryptedImagePartial = null;
KeyPair keyPair = t.buildKeyPair();
PublicKey pubKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate()
//-----------IMAGE TO STRING CONVERSION----------------
//The imagetostring() function retrieves the image at the file path and converts it into a Base64 encoded String
try
{
stringOfImage = t.imagetostring(inputImageFilePath);
}
catch(Exception e)
{
System.out.println(e.toString());
}
//-----------ENCRYPTION OF STRING----------------
//The encryption is done in blocks of 189, because earlier I got an exception - "javax.crypto.IllegalBlockSizeException: Data must not be longer than 190 bytes"
try
{
String plaintext = stringOfImage;
String plaintextTrimmed = "";
System.out.println(stringOfImage);
encryptedImage = new byte[15512]; //The size is given as 15512 because the length of the particular string was found to be 15512
while(plaintext!="")
{
if(plaintext.length()>189)
{
plaintextTrimmed = plaintext.substring(0, 189);
plaintext = plaintext.substring(189);
}
else
{
plaintextTrimmed = plaintext;
plaintext = "";
}
encryptedImagePartial = t.rsaEncrypt(plaintextTrimmed, pubKey);
encryptedImage = t.concatenate(encryptedImage, encryptedImagePartial);
System.out.println(encryptedImage.length);
}
}
catch(Exception e)
{
System.out.println(e.toString());
}
t.byteDigest(encryptedImage);
//-----------DECRYPTION OF STRING--------------
//The decryption is done in blocks of 189, because earlier I got an exception - "javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes"
try
{
// The ciphertext is located in the variable encryptedImage which is a byte[]
String stringRepOfCipherText = new String(encryptedImage); String cipherTextTrimmed = "";
String decryptedImagePartial;
String decryptedImage = "";
while(stringRepOfCipherText!="")
{
if(stringRepOfCipherText.length()>189)
{
cipherTextTrimmed = stringRepOfCipherText.substring(0, 189);
stringRepOfCipherText = stringRepOfCipherText.substring(189);
}
else
{
cipherTextTrimmed = stringRepOfCipherText;
stringRepOfCipherText = "";
}
decryptedImagePartial = t.rsaDecrypt(cipherTextTrimmed.getBytes(), privateKey);
decryptedImage = decryptedImage + decryptedImagePartial;
}
}
catch(BadPaddingException e)
{
System.out.println(e.toString());
}
catch(Exception e)
{
System.out.println(e.toString());
}
}
}
此外,我注意到其他一些使用 KeyFactory 生成密钥的示例。谁也能告诉我使用 KeyFactory 和我用过的有什么区别吗?
你不能将密文切割成任意块!
由于您特别要求使用不涉及对称算法的纯 RSA(我强烈建议您不要这样做!),因此您需要做的是:
- 找出适合您的 RSA 配置的 maximum payload size。
- 将你的明文拆分成这个大小的块
- 单独加密每个块,不要简单地连接它们并丢弃块边界!
解密过程中:
- 使用加密后的原始大小将每个密文块传递给解密函数。 不附加任何数据,不创建 "substrings"。
- 连接生成的明文。
理想情况下,您应该使用混合加密方案:
- 生成加密密钥(
encKey
) - 使用
encKey
的对称算法加密您的图像
- 使用
pubKey
和 RSA 加密
encKey
对称密码可用于不同的操作模式,从而避免此类长度限制。
首先,先把图片编码成base 64是完全没有意义的,现代密码的输入都是字节,图片本身就是字节。如果你想存储一个字符串,你可能需要对密文进行 base 64 编码。
输入块大小确实是190字节。您可以看到 RSA / OAEP here 的 table(不要忘记投票!)。我不确定在那种情况下您为什么要使用 189;然而,我的代码是通用的。输出块大小只是 RSA 的密钥大小,因为它被显式转换为以字节为单位的密钥大小(即使它可能更小)。
在解密过程中,您将密文转换为字符串。但是,Java 中的字符串解码是有损的;如果解码器发现一个不代表字符的字节,那么它将被 静默 丢弃。所以这不会(总是有效),例如导致 BadPaddingException
。不过没关系,我们可以保留二进制密文。
废话不多说,先上一段代码给大家看看。请注意每块 66 字节的密文扩展以及(主要是)解密性能不佳。强烈建议在混合密码系统中使用 AES 和 RSA(对于这个问题不是第一次)。
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;
import javax.crypto.Cipher;
public class Tester {
private static final int KEY_SIZE = 2048;
private static final int OAEP_MGF1_SHA256_OVERHEAD = 66;
public static KeyPair buildKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(KEY_SIZE);
return keyPairGenerator.generateKeyPair();
}
public static void main(String args[]) throws Exception {
KeyPair keyPair = Tester.buildKeyPair();
RSAPublicKey pubKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// assumes the bitLength is a multiple of 8 (check first!)
int keySizeBytes = pubKey.getModulus().bitLength() / Byte.SIZE;
byte[] image = new byte[1000];
Arrays.fill(image, (byte) 'm');
// --- encryption
final Cipher enc;
try {
enc = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("OAEP with MGF-1 using SHA-256 not available in this runtime", e);
}
enc.init(Cipher.ENCRYPT_MODE, pubKey);
int fragmentsize = keySizeBytes - OAEP_MGF1_SHA256_OVERHEAD;
ByteArrayOutputStream ctStream = new ByteArrayOutputStream();
int off = 0;
while (off < image.length) {
int toCrypt = Math.min(fragmentsize, image.length - off);
byte[] partialCT = enc.doFinal(image, off, toCrypt);
ctStream.write(partialCT);
off += toCrypt;
}
byte[] ct = ctStream.toByteArray();
// --- decryption
Cipher dec = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
dec.init(Cipher.DECRYPT_MODE, privateKey);
ByteArrayOutputStream ptStream = new ByteArrayOutputStream();
off = 0;
while (off < ct.length) {
int toCrypt = Math.min(keySizeBytes, ct.length - off);
byte[] partialPT = dec.doFinal(ct, off, toCrypt);
ptStream.write(partialPT);
off += toCrypt;
}
byte[] pt = ptStream.toByteArray();
// mmmm...
System.out.println(new String(pt, StandardCharsets.US_ASCII));
}
}