【发布时间】:2021-05-12 00:32:24
【问题描述】:
我正在尝试编写一个程序来加密任何类型的文件。我已经完成了我的加密课程,当我注意到(起初它有效)每当我尝试解密我的任何文件时都会收到 AEADBadTagException。
这是我的加密/解密类:
class Encryptor {
private static final String algorithm = "AES/GCM/NoPadding";
private final int tagLengthBit = 128; // must be one of {128, 120, 112, 104, 96}
private final int ivLengthByte = 12;
private final int saltLengthByte = 64;
protected final Charset UTF_8 = StandardCharsets.UTF_8;
private CryptoUtils crypto = new CryptoUtils();
// return a base64 encoded AES encrypted text
/**
*
* @param pText to encrypt
* @param password password for encryption
* @return encoded pText
* @throws Exception
*/
protected byte[] encrypt(byte[] pText, char[] password) throws Exception {
// 64 bytes salt
byte[] salt = crypto.getRandomNonce(saltLengthByte);
// GCM recommended 12 bytes iv?
byte[] iv = crypto.getRandomNonce(ivLengthByte);
// secret key from password
SecretKey aesKeyFromPassword = crypto.getAESKeyFromPassword(password, salt);
Cipher cipher = Cipher.getInstance(algorithm);
// ASE-GCM needs GCMParameterSpec
cipher.init(Cipher.ENCRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(tagLengthBit, iv));
byte[] cipherText = cipher.doFinal(pText);
// prefix IV and Salt to cipher text
byte[] cipherTextWithIvSalt = ByteBuffer.allocate(iv.length + salt.length + cipherText.length).put(iv).put(salt)
.put(cipherText).array();
Main.clearArray(password, null);
Main.clearArray(null, salt);
Main.clearArray(null, iv);
Main.clearArray(null, cipherText);
aesKeyFromPassword = null;
cipher = null;
try {
return cipherTextWithIvSalt;
} finally {
Main.clearArray(null, cipherTextWithIvSalt);
}
}
// für Files
protected byte[] decrypt(byte[] encryptedText, char[] password)
throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException {
// get back the iv and salt from the cipher text
ByteBuffer bb = ByteBuffer.wrap(encryptedText);
byte[] iv = new byte[ivLengthByte];
bb.get(iv);
byte[] salt = new byte[saltLengthByte];
bb.get(salt);
byte[] cipherText = new byte[bb.remaining()];
bb.get(cipherText);
// get back the aes key from the same password and salt
SecretKey aesKeyFromPassword;
aesKeyFromPassword = crypto.getAESKeyFromPassword(password, salt);
Cipher cipher;
cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(tagLengthBit, iv));
byte[] plainText = cipher.doFinal(cipherText);
Main.clearArray(password, null);
Main.clearArray(null, iv);
Main.clearArray(null, salt);
Main.clearArray(null, cipherText);
aesKeyFromPassword = null;
cipher = null;
bb = null;
try {
return plainText;
} finally {
Main.clearArray(null, plainText);
}
}
protected void encryptFile(String file, char[] pw) throws Exception {
Path pathToFile = Paths.get(file);
byte[] fileCont = Files.readAllBytes(pathToFile);
byte[] encrypted = encrypt(fileCont, pw);
Files.write(pathToFile, encrypted);
Main.clearArray(pw, null);
Main.clearArray(null, fileCont);
Main.clearArray(null, encrypted);
}
protected void decryptFile(String file, char[] pw)
throws IOException, InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException {
Path pathToFile = Paths.get(file);
byte[] fileCont = Files.readAllBytes(pathToFile);
byte[] decrypted = decrypt(fileCont, pw);
Files.write(pathToFile, decrypted);
Main.clearArray(pw, null);
Main.clearArray(null, fileCont);
Main.clearArray(null, decrypted);
}
}
对应的 CryptoUtils 类:
class CryptoUtils {
protected byte[] getRandomNonce(int numBytes) {
byte[] nonce = new byte[numBytes];
new SecureRandom().nextBytes(nonce);
try {
return nonce;
} finally {
Main.clearArray(null, nonce);
}
}
// Password derived AES 256 bits secret key
protected SecretKey getAESKeyFromPassword(char[] password, byte[] salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
// iterationCount = 65536
// keyLength = 256
KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
try {
return secret;
} finally {
secret = null;
}
}
// hex representation
protected String hex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(String.format("%02x", b));
}
try {
return result.toString();
} finally {
result.delete(0, result.length() - 1);
}
}
// print hex with block size split
protected String hexWithBlockSize(byte[] bytes, int blockSize) {
String hex = hex(bytes);
// one hex = 2 chars
blockSize = blockSize * 2;
// better idea how to print this?
List<String> result = new ArrayList<>();
int index = 0;
while (index < hex.length()) {
result.add(hex.substring(index, Math.min(index + blockSize, hex.length())));
index += blockSize;
}
try {
return result.toString();
} finally {
result.clear();
}
}
}
异常发生在解密方法中的byte[] plainText = cipher.doFinal(cipherText);。
我不确定 tagLenthBit 是否必须是 ivLengthByte * 8,但我确实尝试过,但没有任何区别。
【问题讨论】:
-
我无法重现这个。您是否可能混淆了文件,因为您将所有内容都保存在同一个文件中?
-
@Topaco 不可能,因为对于每个文件,都会读取内容,然后立即写回。仅选择一个文件也会发生这种情况
-
正如我所说,在我的机器上代码可以工作(使用 14 MB pdf 测试文件)。我会尝试隔离问题,例如您可以先测试
encrypt()和decrypt()没有 文件的问题是否也出现。 -
您似乎清除了
finally语句中的结果(finally在return之前执行),s。encrypt()、decrypt()和getRandomNonce()。我已经注释掉了这些行(因为你没有发布Main.clearArray()),所以它从一开始就在我的机器上运行。
标签: java encryption buffer bytebuffer aes-gcm