You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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加密结果的结构:

  1. RSA加密后的BigInteger(如1397715838)对应的字节数组是4字节长度,但encryptFile.write(encrypt.toByteArray())只是把这些字节直接写入文件,没有做固定长度标记。
  2. 读取时用ReadFileToBinary把文件的每个字节单独转成int,相当于把4字节的加密大整数拆成了4个独立的单字节int值,而非还原成原加密大整数。
  3. 后续解密时用这些单字节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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 02:25:53