如何解决Java AES GCM AEAD 标签不匹配
我正在尝试编写一个程序来加密任何类型的文件。我已经完成了我的加密课程,当我注意到(起初它起作用)每当我尝试解密我的任何文件时都会收到 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,iv));
byte[] plainText = cipher.doFinal(cipherText);
Main.cleararray(password,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,fileCont);
Main.cleararray(null,encrypted);
}
protected void decryptFile(String file,char[] pw)
throws IOException,InvalidKeyException,BadPaddingException {
Path pathToFile = Paths.get(file);
byte[] fileCont = Files.readAllBytes(pathToFile);
byte[] decrypted = decrypt(fileCont,decrypted);
Main.cleararray(pw,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,但我确实尝试过,但没有任何区别。
解决方法
我提供了我自己的示例代码,用于使用 PBKDF2 密钥派生的 AES 256 GCM 文件加密,因为我懒得检查代码的所有部分:-)
加密是通过 CipherInput-/Outputstreams 完成的,因为这样可以避免在加密较大文件时出现“内存不足错误”(您的代码正在读取字节数组中的完整明文/密文)。
请注意,代码没有异常处理,没有清除敏感数据/变量,加密/解密结果是一个简单的“文件存在”例程,但我相信您可以将其用作程序的良好基础.
这是一个示例输出:
AES 256 GCM-mode PBKDF2 with SHA512 key derivation file encryption
result encryption: true
result decryption: true
代码:
import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
public class AesGcmEncryptionInlineIvPbkdf2BufferedCipherInputStreamSoExample {
public static void main(String[] args) throws NoSuchPaddingException,NoSuchAlgorithmException,IOException,InvalidKeyException,InvalidKeySpecException,InvalidAlgorithmParameterException {
System.out.println("AES 256 GCM-mode PBKDF2 with SHA512 key derivation file encryption");
char[] password = "123456".toCharArray();
int iterations = 65536;
String uncryptedFilename = "uncrypted.txt";
String encryptedFilename = "encrypted.enc";
String decryptedFilename = "decrypted.txt";
boolean result;
result = encryptGcmFileBufferedCipherOutputStream(uncryptedFilename,encryptedFilename,password,iterations);
System.out.println("result encryption: " + result);
result = decryptGcmFileBufferedCipherInputStream(encryptedFilename,decryptedFilename,iterations);
System.out.println("result decryption: " + result);
}
public static boolean encryptGcmFileBufferedCipherOutputStream(String inputFilename,String outputFilename,char[] password,int iterations) throws
IOException,NoSuchPaddingException,InvalidAlgorithmParameterException {
SecureRandom secureRandom = new SecureRandom();
byte[] salt = new byte[32];
secureRandom.nextBytes(salt);
byte[] nonce = new byte[12];
secureRandom.nextBytes(nonce);
Cipher cipher = Cipher.getInstance("AES/GCM/NOPadding");
try (FileInputStream in = new FileInputStream(inputFilename);
FileOutputStream out = new FileOutputStream(outputFilename);
CipherOutputStream encryptedOutputStream = new CipherOutputStream(out,cipher);) {
out.write(nonce);
out.write(salt);
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
KeySpec keySpec = new PBEKeySpec(password,salt,iterations,32 * 8); // 128 - 192 - 256
byte[] key = secretKeyFactory.generateSecret(keySpec).getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(key,"AES");
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(16 * 8,nonce);
cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec,gcmParameterSpec);
byte[] buffer = new byte[8096];
int nread;
while ((nread = in.read(buffer)) > 0) {
encryptedOutputStream.write(buffer,nread);
}
encryptedOutputStream.flush();
}
if (new File(outputFilename).exists()) {
return true;
} else {
return false;
}
}
public static boolean decryptGcmFileBufferedCipherInputStream(String inputFilename,InvalidAlgorithmParameterException {
byte[] salt = new byte[32];
byte[] nonce = new byte[12];
Cipher cipher = Cipher.getInstance("AES/GCM/NOPadding");
try (FileInputStream in = new FileInputStream(inputFilename); // i don't care about the path as all is lokal
CipherInputStream cipherInputStream = new CipherInputStream(in,cipher);
FileOutputStream out = new FileOutputStream(outputFilename)) // i don't care about the path as all is lokal
{
byte[] buffer = new byte[8192];
in.read(nonce);
in.read(salt);
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
KeySpec keySpec = new PBEKeySpec(password,nonce);
cipher.init(Cipher.DECRYPT_MODE,gcmParameterSpec);
int nread;
while ((nread = cipherInputStream.read(buffer)) > 0) {
out.write(buffer,nread);
}
out.flush();
}
if (new File(outputFilename).exists()) {
return true;
} else {
return false;
}
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。