RSA加密图片后解密失败:字节存储与读取异常排查
RSA加密图片后解密失效的问题分析与解决
问题现象
RSA公私钥验证正常,但加密图片字节数组后解密失败:
- 单字节明文(如255)经RSA加密得到大整数
1397715838 - 将加密结果转成字节数组写入文件后,再次读取时只能得到单字节
255,而非原加密大整数 - 解密时因输入值错误,无法还原明文
相关代码如下:
RSA方法代码
public static void RSA() throws Exception { Random random = new Random(); var fileData = ReadFileToBinary("C:\\Users\\User\\IdeaProjects\\CryptoLab1\\src\\crypto\\dino.png"); FileOutputStream encryptFile = new FileOutputStream("C:\\Users\\User\\IdeaProjects\\CryptoLab1\\src\\crypto\\endino.png"); FileOutputStream decryptFile = new FileOutputStream("C:\\Users\\User\\IdeaProjects\\CryptoLab1\\src\\crypto\\dedino.png"); BigInteger P = BigInteger.probablePrime(16, random); BigInteger Q = BigInteger.probablePrime(16, random); BigInteger N = P.multiply(Q); BigInteger f = (P.subtract(BigInteger.ONE)).multiply(Q.subtract(BigInteger.ONE)); BigInteger d; BigInteger c; BigInteger encrypt; BigInteger decrypt; do { d = new BigInteger(16, random); } while (!(d.gcd(f).equals(BigInteger.ONE)) && d.compareTo(f) < 0); c = d.modInverse(f); for (var mess : fileData) { BigInteger message = BigInteger.valueOf(mess); encrypt = cryptoLab1.ModPow(message,d, N); //ecnrypt 1397715838 encryptFile.write(encrypt.toByteArray()); // encrypt byte here 255 } var fileData2 = ReadFileToBinary("C:\\Users\\User\\IdeaProjects\\CryptoLab1\\src\\crypto\\endino.png"); for(var mess: fileData2) { BigInteger message = BigInteger.valueOf(mess); //Need value here 1397715838, not 255 decrypt = cryptoLab1.ModPow(message, c, N); if(decrypt.intValue() > 127) { decrypt = decrypt.subtract(BigInteger.valueOf(256)); decryptFile.write(decrypt.toByteArray()); } else { out2.write(decrypt.toByteArray()); } } }
ReadFileToBinary方法代码
public static int[] ReadFileToBinary(String path) throws IOException { File file = new File(path); byte[] fileData = new byte[(int)file.length()]; FileInputStream in = new FileInputStream(file); in.read(fileData); int[] arrayBytes= new int[(int) file.length()]; for(int i = 0; i < fileData.length; i++) { arrayBytes[i] = Byte.toUnsignedInt(fileData[i]); } in.close(); return arrayBytes; }
问题根源
核心错误是加密数据的写入与读取逻辑完全破坏了RSA加密结果的结构:
- RSA加密后的
BigInteger(如1397715838)对应的字节数组是4字节长度,但encryptFile.write(encrypt.toByteArray())只是把这些字节直接写入文件,没有做固定长度标记。 - 读取时用
ReadFileToBinary把文件的每个字节单独转成int,相当于把4字节的加密大整数拆成了4个独立的单字节int值,而非还原成原加密大整数。 - 后续解密时用这些单字节int值作为输入,自然无法得到正确的明文。
另外代码还有两处次要错误:
- 私钥
d的生成条件逻辑错误:!(d.gcd(f).equals(BigInteger.ONE)) && d.compareTo(f) < 0应该改为!d.gcd(f).equals(BigInteger.ONE) || d.compareTo(f) >= 0,否则会生成不符合要求的d。 - 解密代码中
out2未定义,属于语法错误。
解决方案
要保证加密后的每个BigInteger作为完整的块写入和读取,具体步骤:
1. 确定加密块的固定长度
RSA加密结果一定小于模数N,因此可以根据N的位长计算出每个加密块的字节长度:
int blockSize = (N.bitLength() + 7) / 8;
这里用16位素数生成N,位长是32位,所以blockSize=4(4字节)。
2. 修改加密写入逻辑
将每个加密后的BigInteger转成固定长度的字节数组(大端序)后写入文件,避免因数值大小导致字节长度变化:
byte[] encryptBytes = new byte[blockSize]; byte[] temp = encrypt.toByteArray(); // 将加密结果复制到固定长度数组的末尾,保证大端对齐 System.arraycopy(temp, 0, encryptBytes, blockSize - temp.length, temp.length); encryptFile.write(encryptBytes);
3. 修改解密读取逻辑
按固定blockSize读取字节,将每组字节还原成BigInteger后再解密:
FileInputStream encryptIn = new FileInputStream("加密文件路径"); byte[] buffer = new byte[blockSize]; int readLen; while ((readLen = encryptIn.read(buffer)) != -1) { // 用正号标记解析字节数组,避免符号问题 BigInteger message = new BigInteger(1, buffer); decrypt = cryptoLab1.ModPow(message, c, N); // 解密结果是0-255的无符号字节,直接写入 decryptFile.write(decrypt.intValue()); }
修正后的完整RSA方法代码
public static void RSA() throws Exception { Random random = new Random(); var fileData = ReadFileToBinary("C:\\Users\\User\\IdeaProjects\\CryptoLab1\\src\\crypto\\dino.png"); FileOutputStream encryptFile = new FileOutputStream("C:\\Users\\User\\IdeaProjects\\CryptoLab1\\src\\crypto\\endino.png"); FileOutputStream decryptFile = new FileOutputStream("C:\\Users\\User\\IdeaProjects\\CryptoLab1\\src\\crypto\\dedino.png"); BigInteger P = BigInteger.probablePrime(16, random); BigInteger Q = BigInteger.probablePrime(16, random); BigInteger N = P.multiply(Q); int blockSize = (N.bitLength() + 7) / 8; // 计算固定加密块长度 BigInteger f = (P.subtract(BigInteger.ONE)).multiply(Q.subtract(BigInteger.ONE)); BigInteger d; BigInteger c; BigInteger encrypt; BigInteger decrypt; // 修正私钥d的生成条件:确保d与f互质且d < f do { d = new BigInteger(16, random); } while (!d.gcd(f).equals(BigInteger.ONE) || d.compareTo(f) >= 0); c = d.modInverse(f); // 加密阶段 for (var mess : fileData) { BigInteger message = BigInteger.valueOf(mess); encrypt = cryptoLab1.ModPow(message, d, N); // 转成固定长度字节数组写入 byte[] encryptBytes = new byte[blockSize]; byte[] temp = encrypt.toByteArray(); System.arraycopy(temp, 0, encryptBytes, blockSize - temp.length, temp.length); encryptFile.write(encryptBytes); } encryptFile.close(); // 解密阶段 FileInputStream encryptIn = new FileInputStream("C:\\Users\\User\\IdeaProjects\\CryptoLab1\\src\\crypto\\endino.png"); byte[] buffer = new byte[blockSize]; int readLen; while ((readLen = encryptIn.read(buffer)) != -1) { BigInteger message = new BigInteger(1, buffer); decrypt = cryptoLab1.ModPow(message, c, N); decryptFile.write(decrypt.intValue()); } encryptIn.close(); decryptFile.close(); }
说明
ReadFileToBinary方法无需修改,它负责将明文图片的每个字节转成无符号int,适合作为RSA的明文输入(因为RSA明文需要小于N,这里单字节0-255远小于32位的N)。- 固定块长度的处理是RSA批量加密的基础逻辑,保证每个加密结果的字节长度一致,才能正确拆分还原。
内容的提问来源于stack exchange,提问作者John Smith
相关产品推荐
相关产品推荐

