
rsa是一种非对称加密算法,其核心操作是基于大整数的幂运算和模运算。在实际应用中,文件或图片等数据通常以字节流的形式存在。为了使用rsa对这些字节数据进行加密,我们需要将字节数据转换为大整数进行处理。一个常见的策略是将原始数据块(例如,单个字节或几个字节组成的块)视为一个大整数,然后对其进行加密。
例如,一个字节(值范围0-255)可以被转换为一个BigInteger对象。经过RSA加密后,这个BigInteger会变成另一个大得多的BigInteger(密文)。为了将这个密文写入文件,我们需要将其转换回字节数组。Java的BigInteger类提供了toByteArray()方法来完成这个转换。
问题的核心在于BigInteger.toByteArray()方法的行为。它将一个BigInteger表示为一个最小长度的二进制补码形式的字节数组。这意味着:
当您将每个加密后的BigInteger的字节数组直接连接写入文件时,由于每个数组的长度不固定,并且没有明确的分隔符或长度信息,导致在读取文件时无法确定每个加密块的边界。例如,当您读取到字节序列[54, -2, 43, 4, 13, -140, ...]时,您无法判断第一个加密的BigInteger是[54]、[54, -2]还是[54, -2, 43]。因此,您无法正确地将这些字节重新组合成原始的BigInteger密文进行解密。
原始代码中,加密时将encrypt(一个BigInteger)转换为encrypt.toByteArray()写入文件,但解密时却试图从文件中读取单个字节并将其转换为BigInteger.valueOf(mess)。这导致解密时传入的BigInteger根本不是加密时的那个大整数密文,而是密文字节数组中的一个片段,从而解密失败。
解决这个问题的关键在于,在将每个加密后的BigInteger的字节数组写入文件时,同时记录其长度信息。这样,在解密时,我们可以先读取长度,然后根据长度准确地读取出对应的字节数组,再将其转换回BigInteger进行解密。
文件结构可以设计为: [加密块1长度 (N1)] [加密块1字节数组] [加密块2长度 (N2)] [加密块2字节数组] ...
我们将使用Java的DataOutputStream和DataInputStream来简化长度信息的写入和读取。
1. 加密过程修改
在加密循环中,对于每个加密后的BigInteger:
import java.io.*;
import java.math.BigInteger;
import java.util.Random;
public class RSACryptographer {
// 假设 ModPow 和 ReadFileToBinary 方法已定义
// public static BigInteger ModPow(BigInteger base, BigInteger exponent, BigInteger modulus) { ... }
// public static int[] ReadFileToBinary(String path) throws IOException { ... }
public static void RSAEncryptDecrypt(String inputFilePath, String encryptedFilePath, String decryptedFilePath) throws Exception {
Random random = new Random();
// 1. 生成RSA密钥对
BigInteger P = BigInteger.probablePrime(16, random); // 示例:16位素数
BigInteger Q = BigInteger.probablePrime(16, random); // 示例:16位素数
BigInteger N = P.multiply(Q); // 模数 N
BigInteger f = (P.subtract(BigInteger.ONE)).multiply(Q.subtract(BigInteger.ONE)); // 欧拉函数 f(N)
BigInteger d; // 私钥指数
BigInteger c; // 公钥指数 (通常是e)
// 寻找与f互质的d
do {
d = new BigInteger(16, random); // 示例:16位随机数
} while (!(d.gcd(f).equals(BigInteger.ONE)) || d.compareTo(f) >= 0 || d.compareTo(BigInteger.ONE) <= 0);
// 计算c (d的模逆元)
c = d.modInverse(f);
System.out.println("RSA Keys Generated:");
System.out.println("P: " + P);
System.out.println("Q: " + Q);
System.out.println("N (Public Modulus): " + N);
System.out.println("f (Phi(N)): " + f);
System.out.println("d (Private Exponent): " + d);
System.out.println("c (Public Exponent): " + c);
System.out.println("------------------------------------");
// 2. 读取原始文件数据
int[] fileData = ReadFileToBinary(inputFilePath);
// 3. 加密过程
try (DataOutputStream encryptFileStream = new DataOutputStream(new FileOutputStream(encryptedFilePath))) {
System.out.println("Starting encryption...");
for (int messInt : fileData) {
BigInteger message = BigInteger.valueOf(messInt); // 将单个字节转换为BigInteger
// 确保消息小于N (对于单字节消息,通常总是小于N)
if (message.compareTo(N) >= 0) {
throw new IllegalArgumentException("Message value " + message + " is too large for modulus N=" + N);
}
// 加密:encrypt = message^d mod N
BigInteger encryptedBigInt = ModPow(message, d, N);
// 将加密后的BigInteger转换为字节数组
byte[] encryptedBytes = encryptedBigInt.toByteArray();
// 写入字节数组的长度,然后写入字节数组本身
encryptFileStream.writeInt(encryptedBytes.length); // 写入长度 (int占4字节)
encryptFileStream.write(encryptedBytes); // 写入加密数据
}
System.out.println("Encryption complete. Encrypted file saved to: " + encryptedFilePath);
}
// 4. 解密过程
try (DataInputStream decryptFileStream = new DataInputStream(new FileInputStream(encryptedFilePath));
FileOutputStream decryptedFileOutputStream = new FileOutputStream(decryptedFilePath)) {
System.out.println("Starting decryption...");
while (decryptFileStream.available() > 0) {
// 读取加密块的长度
int length = decryptFileStream.readInt();
// 根据长度读取加密块的字节数组
byte[] encryptedBytesToRead = new byte[length];
decryptFileStream.readFully(encryptedBytesToRead); // 确保读取所有字节
// 将字节数组转换回BigInteger密文
BigInteger encryptedBigInt = new BigInteger(encryptedBytesToRead);
// 解密:decrypted = encryptedBigInt^c mod N
BigInteger decryptedBigInt = ModPow(encryptedBigInt, c, N);
// 将解密后的BigInteger转换回原始字节
// 由于原始消息是单个字节 (0-255),这里取其低8位
byte decryptedByte = (byte) (decryptedBigInt.intValue() & 0xFF);
decryptedFileOutputStream.write(decryptedByte);
}
System.out.println("Decryption complete. Decrypted file saved to: " + decryptedFilePath);
}
}
// 辅助方法:ModPow (模幂运算)
// 假设这是您自己的实现,或者使用BigInteger自带的modPow
public static BigInteger ModPow(BigInteger base, BigInteger exponent, BigInteger modulus) {
return base.modPow(exponent, modulus);
}
// 辅助方法:ReadFileToBinary
public static int[] ReadFileToBinary(String path) throws IOException {
File file = new File(path);
byte[] fileData = new byte[(int)file.length()];
try (FileInputStream in = new FileInputStream(file)) {
in.read(fileData);
}
int[] arrayBytes= new int[fileData.length];
for(int i = 0; i < fileData.length; i++) {
arrayBytes[i] = Byte.toUnsignedInt(fileData[i]);
}
return arrayBytes;
}
public static void main(String[] args) {
try {
// 替换为您的实际文件路径
String input = "C:\Users\User\IdeaProjects\CryptoLab1\src\crypto\dino.png";
String encrypted = "C:\Users\User\IdeaProjects\CryptoLab1\src\crypto\endino.png";
String decrypted = "C:\Users\User\IdeaProjects\CryptoLab1\src\crypto\dedino.png";
RSAEncryptDecrypt(input, encrypted, decrypted);
} catch (Exception e) {
e.printStackTrace();
}
}
}代码解释:
通过在每个加密后的BigInteger字节数组前添加其长度信息,我们成功解决了由于BigInteger.toByteArray()可变长度输出导致的文件解析问题,确保了加密数据的完整性和可恢复性。然而,此解决方案仅修复了数据流的完整性问题,并未解决原始RSA算法在实际应用中的安全性和效率问题。在构建生产级别的加密系统时,务必考虑使用标准的加密库、适当的填充方案以及混合加密策略,以确保数据安全和系统性能。
以上就是解决RSA加密中BigInteger字节数组可变长度导致的解密失败问题的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号