返回

RSA加密问题解决:精准分块处理方案

java

RSA 实现问题解析与方案

RSA 加密算法,虽说原理简单,但在实际实现中,针对不同文件类型的加解密处理常常会遇到各种问题。尤其是涉及二进制文件,比如 .png, .pdf,处理不当容易出现数据损坏。文中的情况, .txt 文件正常, .pdf 文件部分损坏, .png 文件则出现大面积错乱,揭示了加密过程中字节处理的一些普遍问题。接下来深入分析其原因,并提供相应的解决方案。

字节处理和分块问题

主要问题在于字节分块的处理。RSA算法是基于大整数的模幂运算,但实际的文件是字节流。当直接把文件字节转换成 BigInteger 进行加密时,需要特别关注:

  • 块大小 : RSA的块大小取决于密钥中的 n 值,正确的块大小是加密和解密的关键。log2(n)的计算和字节的映射过程稍有偏差会导致加密数据大小和原数据对不上。文中实现中计算byteChunkSize采用向下取整方法导致最终文件大小异常。
  • 字节数组长度 : Java 中 BigInteger 构造函数如果传入的 byte array 的最高位是1则被视为负数,因此,要增加一个表示正数的signum位。
  • 尾部处理 : 加密解密中,需要考虑最后的数据块不足一个完整分块长度时如何填充或者直接读取,代码中对尾部长度为考虑会导致解密错位。

解决方案一:精准分块与字节填充

  1. 计算准确的字节分块大小: 使用 n.bitLength() 获取 n 的位长度,并将位长度转换为字节长度。向上取整计算 byteChunkSize (long)(n.bitLength() + 7) / 8.,确保后续分块字节的合理性,在byteChunkSize字节中,最大的BigInteger为n-1。
  2. 读取数据: 使用与 byteChunkSize 长度相同的 byte 数组读取文件数据。
  3. 数据长度的保存和读取 : 为了确保解密时的正确数据读取,需要在加密的时候,增加实际数据长度记录。在加密后的输出文件中,每段加密后的字节数组前加入长度记录信息。解密时,首先读取长度信息,再根据长度读取实际的数据内容。
  4. 末块处理 必须考虑最后一块读取数据长度不足 byteChunkSize 的情况,读取多少数据就处理多少数据。解密数据时候必须判断最终长度是否为 byteChunkSize。若长度小于 byteChunkSize, 需要进行移位处理或者保留原字节进行拼接。

代码示例(修改加密和解密部分):

public static void encrypt(String inputFile, String outputFile) throws IOException {
    ArrayList<BigInteger> key = readKey("pubkey.txt");
    BigInteger e = key.get(0);
    BigInteger n = key.get(1);

    try (FileInputStream fis = new FileInputStream(inputFile);
         DataOutputStream dos = new DataOutputStream(new FileOutputStream(outputFile))) {

        // Calculate chunk size based on n 
       int byteChunkSize = (int) Math.ceil((double)n.bitLength() / 8.0);

        byte[] buffer = new byte[byteChunkSize];
        int bytesRead;

        while ((bytesRead = fis.read(buffer)) != -1) {
            
           byte[] dataChunk = new byte[bytesRead];
           System.arraycopy(buffer, 0, dataChunk, 0, bytesRead);

          
           BigInteger messageChunk = new BigInteger(1, dataChunk);
            
           BigInteger encryptedChunk = messageChunk.modPow(e, n);
            
          byte[] encryptedBytes = encryptedChunk.toByteArray();

           dos.writeInt(bytesRead); // save length
           dos.writeInt(encryptedBytes.length);
           dos.write(encryptedBytes);
        }
    }

    System.out.println("Encryption completed. Encrypted data saved to " + outputFile);
}



public static void decrypt(String inputFile, String outputFile) throws IOException {
    ArrayList<BigInteger> key = readKey("privkey.txt");
    BigInteger d = key.get(0);
    BigInteger n = key.get(1);

   try (DataInputStream dis = new DataInputStream(new FileInputStream(inputFile));
        FileOutputStream fos = new FileOutputStream(outputFile)) {


         int byteChunkSize = (int) Math.ceil((double)n.bitLength() / 8.0);

       while(dis.available()>0){
            int originalDataLength = dis.readInt();
            int encryptedLength = dis.readInt();
            byte[] encryptedBuffer = new byte[encryptedLength];
            dis.readFully(encryptedBuffer);

            BigInteger encryptedChunk = new BigInteger(1, encryptedBuffer);

            BigInteger decryptedChunk = encryptedChunk.modPow(d, n);
            byte[] decryptedBytes = decryptedChunk.toByteArray();

            byte[] result;
              if (decryptedBytes.length == byteChunkSize+1 && decryptedBytes[0] == 0 )
                {
                    result = Arrays.copyOfRange(decryptedBytes, 1, decryptedBytes.length);
                   fos.write(result, 0 , originalDataLength); 
                  }
                else
                {
                  fos.write(decryptedBytes,0, originalDataLength); 
                }

       }
  
    }
    System.out.println("Decryption completed. Decrypted data saved to " + outputFile);
}

操作步骤:

  1. 编译修改后的 Java 代码。
  2. 执行 generateKeys 方法生成公钥 pubkey.txt 和私钥 privkey.txt
  3. 调用 encrypt 方法加密文件。
  4. 调用 decrypt 方法解密文件。

安全建议

  • 密钥强度 : 选择足够大的素数 p 和 q 生成密钥。 建议使用 2048 位或更高的密钥长度。可以使用 Miller-Rabin 素性测试生成高质量的素数。
  • 填充方案 : 对于 RSA 加密,建议采用合适的填充方案,比如 OAEP(Optimal Asymmetric Encryption Padding),以避免攻击。文中的代码示例不包括填充。
  • 随机数生成 : 确保随机数生成器的安全性,避免密钥被预测。

通过以上调整和实践,可以有效地解决 RSA 加解密在处理二进制文件时遇到的数据损坏问题。关键在于精准的分块大小计算、数据长度记录和字节处理,以及合理的填充方案。理解这些细节对于建立一个安全的 RSA 实现至关重要。