主要问题是:
- IV 和 Salt 不得写在
for
loop.
- IV 存储在
encrypt
不是 Base64 编码的,但是是 Base64 解码的decrypt
.
- 16字节salt存储在
encrypt
(不必要)Base64 编码,即存储 24 个字节。在decrypt
但是只加载了 16 个字节。
Also:
- 编码/解码时,有时没有指定编码,因此使用默认编码。
-
encrypt
and decrypt
对 key 和 IV 使用不同的参数类型。
- 代码中存在许多复制/粘贴错误。
注意:与您的代码相比,linked https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html#PBEEx除了密钥之外,代码还确定来自密码和盐的 IV。
在您的代码中,IV 已通过。因此,您必须确保密钥/IV 对只能使用一次。通常每次加密都会生成一个随机 IV。
在以下代码中(基于您的代码,但为了简单起见,没有异常处理)这些问题已得到修复/优化。此外,该代码适用FileInputStream
and FileOutputStream
而不是你的课程(但这不是必需的):
private static void encrypt(String key, byte[] initVector, String inputFile, String outputFile) throws Exception {
// Key
PBEKeySpec pbeKeySpec = new PBEKeySpec(key.toCharArray());
SecretKeyFactory keyFac = SecretKeyFactory.getInstance("PBEWithHmacSHA256AndAES_256");
SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);
// IV
IvParameterSpec iv = new IvParameterSpec(initVector);
// Salt
SecureRandom rand = new SecureRandom();
byte[] salt = new byte[16];
rand.nextBytes(salt);
// ParameterSpec
int count = 1000; // should be larger, see Michael Fehr's comment
PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, count, iv);
// Cipher
Cipher cipher = Cipher.getInstance("PBEWithHmacSHA256AndAES_256");
cipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);
try (FileInputStream fin = new FileInputStream(inputFile);
FileOutputStream fout = new FileOutputStream(outputFile);
CipherOutputStream cipherOut = new CipherOutputStream(fout, cipher)) {
// Write IV, Salt
fout.write(initVector);
fout.write(salt);
// Encrypt
final byte[] bytes = new byte[1024];
for (int length = fin.read(bytes); length != -1; length = fin.read(bytes)) {
cipherOut.write(bytes, 0, length);
}
}
}
private static void decrypt(String key, byte[] initVect, String inputFile, String outputFile) throws Exception {
try (FileInputStream encryptedData = new FileInputStream(inputFile);
FileOutputStream decryptedOut = new FileOutputStream(outputFile)) {
// Key
PBEKeySpec pbeKeySpec = new PBEKeySpec(key.toCharArray());
SecretKeyFactory keyFac = SecretKeyFactory.getInstance("PBEWithHmacSHA256AndAES_256");
SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);
// Read IV
if (initVect == null) {
initVect = encryptedData.readNBytes(16);
}
IvParameterSpec iv = new IvParameterSpec(initVect);
// Read salt
byte[] salt = encryptedData.readNBytes(16);
// ParameterSpec
int count = 1000;
PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, count, iv);
// Cipher
Cipher cipher = Cipher.getInstance("PBEWithHmacSHA256AndAES_256");
cipher.init(Cipher.DECRYPT_MODE, pbeKey, pbeParamSpec);
try (CipherInputStream decryptStream = new CipherInputStream(encryptedData, cipher)) {
// Decrypt
final byte[] bytes = new byte[1024];
for (int length = decryptStream.read(bytes); length != -1; length = decryptStream.read(bytes)) {
decryptedOut.write(bytes, 0, length);
}
}
}
}
EDIT- 关于盐和 IV 的读取decrypt
:
As GPI他们在评论中指出,FileInputStream.read(byte[] b) https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/io/FileInputStream.html#read(byte%5B%5D)一般读作b.length
字节,但这是不保证。更稳健的是确定读取数据的长度并循环调用该方法,直到数据完成。另一种选择是使用InputStream.readNBytes(int len) https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/io/InputStream.html#readNBytes(int),即保证读书len
bytes(除非遇到流末尾或抛出异常),如Zabuzard已建议。在代码中,现在使用后者,即read
被替换为readNBytes
.