java - javax.crypto.AEADBadTagException
问题描述
我已经围绕此处指定的加密和解密函数实现了一些包装器。
private static final Charset UTF = StandardCharsets.UTF_8;
private static String encrypt(String plainText, String aesKey) {
byte[] cipher = encryptWithPrefixIV(plainText.getBytes(UTF),
ConversionUtil.hexStringToByteArray(aesKey));
return new String(cipher, UTF);
}
public static String decrypt(String cipher, String aesKey) {
byte[] plainText = decryptWithPrefixIV(cipher.getBytes(UTF),
ConversionUtil.hexStringToByteArray(aesKey));
return new String(plainText, UTF);
}
函数 hexStringToByteArray 将十六进制格式的字符串转换"ff"
为11111111
. 此功能有效且经过良好测试!为简洁起见,我不包括该功能。问题是我遇到了javax.crypto.AEADBadTagException: Tag mismatch!
异常,并且错误是不可重现的。该代码在一段时间内运行良好,并且时不时地引发异常。我用于测试的代码是:
public static void main(String[] args) {
String plainText = "This is something";
String cipher = encrypt(plainText);
System.out.println(plainText.equals(decrypt(cipher)));
}
指定链接中提到的功能是:
private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding";
private static final int TAG_LENGTH_BIT = 128;
private static final int IV_LENGTH_BYTE = 12;
public static byte[] encryptWithPrefixIV(byte[] pText, byte[] aesKey) {
byte[] iv = getRandomNonce(IV_LENGTH_BYTE);
byte[] cipherText = encryptWithIV(pText, aesKey, iv);
return ByteBuffer.allocate(iv.length + cipherText.length)
.put(iv)
.put(cipherText)
.array();
}
public static byte[] encryptWithIV(byte[] pText, byte[] aesKey, byte[] iv) {
try {
Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
SecretKey secret = new SecretKeySpec(aesKey, "AES");
cipher.init(Cipher.ENCRYPT_MODE, secret, new GCMParameterSpec(TAG_LENGTH_BIT, iv));
return cipher.doFinal(pText);
} catch (Exception e) {
throw new RuntimeException(e.toString());
}
}
public static byte[] decryptWithPrefixIV(byte[] cText, byte[] aesKey) {
ByteBuffer bb = ByteBuffer.wrap(cText);
byte[] iv = new byte[IV_LENGTH_BYTE];
bb.get(iv);
// bb.get(iv, 0, iv.length);
byte[] cipherText = new byte[bb.remaining()];
bb.get(cipherText);
return decryptWithIV(cipherText, aesKey, iv);
}
public static byte[] decryptWithIV(byte[] cText, byte[] aesKey, byte[] iv) {
try {
Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
SecretKey secret = new SecretKeySpec(aesKey, "AES");
cipher.init(Cipher.DECRYPT_MODE, secret, new GCMParameterSpec(TAG_LENGTH_BIT, iv));
return cipher.doFinal(cText);
} catch (Exception e) {
throw new RuntimeException(e.toString());
}
}
public static byte[] getRandomNonce(int numBytes) {
byte[] nonce = new byte[numBytes];
new SecureRandom().nextBytes(nonce);
return nonce;
}
解决方案
通过呈现最小的运行代码而不是碎片,我们所有人都会节省大量时间......
首先:您的主要功能不完整,因为它没有为加密和解密功能提供 AES 密钥。
第二(也是主要原因):您将加密结果转换为字符串,但这必须失败,因为密码中的很多字节都是不可打印的代码(这可以解释为什么它有时 - 幸运的是 - 有效)。将输出和相应的输入更改为Base64 编码的字符串将帮助您在正确的工作程序中:
Change
return new String(cipher, UTF);
to
return Base64.getEncoder().encodeToString(cipher);
and
byte[] plainText = decryptWithPrefixIV(cipher.getBytes(UTF), hexStringToByteArray(aesKey));
to
byte[] plainText = decryptWithPrefixIV(Base64.getDecoder().decode(cipher), hexStringToByteArray(aesKey));
输出将是:
true
完整的完整代码:
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
public class Org {
private static final Charset UTF = StandardCharsets.UTF_8;
private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding";
private static final int TAG_LENGTH_BIT = 128;
private static final int IV_LENGTH_BYTE = 12;
public static void main(String[] args) {
System.out.println("");
String plainText = "This is something";
String aesKey = "aa00aa00aa00aa00aa00aa00aa00aa00aa00aa00aa00aa00aa00aa00aa00aa00";
String cipher = encrypt(plainText, aesKey);
System.out.println(plainText.equals(decrypt(cipher, aesKey)));
}
private static String encrypt(String plainText, String aesKey) {
byte[] cipher = encryptWithPrefixIV(plainText.getBytes(UTF),
hexStringToByteArray(aesKey));
return Base64.getEncoder().encodeToString(cipher);
// ### do not convert a byte array to string !
//return new String(cipher, UTF);
}
public static String decrypt(String cipher, String aesKey) {
//byte[] plainText = decryptWithPrefixIV(cipher.getBytes(UTF), hexStringToByteArray(aesKey));
byte[] plainText = decryptWithPrefixIV(Base64.getDecoder().decode(cipher), hexStringToByteArray(aesKey));
return new String(plainText, UTF);
}
public static byte[] encryptWithPrefixIV(byte[] pText, byte[] aesKey) {
byte[] iv = getRandomNonce(IV_LENGTH_BYTE);
byte[] cipherText = encryptWithIV(pText, aesKey, iv);
return ByteBuffer.allocate(iv.length + cipherText.length)
.put(iv)
.put(cipherText)
.array();
}
public static byte[] encryptWithIV(byte[] pText, byte[] aesKey, byte[] iv) {
try {
Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
SecretKey secret = new SecretKeySpec(aesKey, "AES");
cipher.init(Cipher.ENCRYPT_MODE, secret, new GCMParameterSpec(TAG_LENGTH_BIT, iv));
return cipher.doFinal(pText);
} catch (Exception e) {
throw new RuntimeException(e.toString());
}
}
public static byte[] decryptWithPrefixIV(byte[] cText, byte[] aesKey) {
ByteBuffer bb = ByteBuffer.wrap(cText);
byte[] iv = new byte[IV_LENGTH_BYTE];
bb.get(iv);
// bb.get(iv, 0, iv.length);
byte[] cipherText = new byte[bb.remaining()];
bb.get(cipherText);
return decryptWithIV(cipherText, aesKey, iv);
}
public static byte[] decryptWithIV(byte[] cText, byte[] aesKey, byte[] iv) {
try {
Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
SecretKey secret = new SecretKeySpec(aesKey, "AES");
cipher.init(Cipher.DECRYPT_MODE, secret, new GCMParameterSpec(TAG_LENGTH_BIT, iv));
return cipher.doFinal(cText);
} catch (Exception e) {
throw new RuntimeException(e.toString());
}
}
public static byte[] getRandomNonce(int numBytes) {
byte[] nonce = new byte[numBytes];
new SecureRandom().nextBytes(nonce);
return nonce;
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i + 1), 16));
}
return data;
}
private static String bytesToHex(byte[] bytes) {
StringBuffer result = new StringBuffer();
for (byte b : bytes) result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
return result.toString();
}
}
推荐阅读
- json - AWS DynamoDB 加密
- node.js - NodeJS从网络流图像,转换为WebP
- node.js - bcrypt 哈希与用户登录比较
- xml - 我怎样才能让 SVN 自动接受他们的某些文件/文件类型?
- php - 如何在 PHP 中使用 MongoDb 显示列的总和?
- reactjs - 上传文件类型错误时出现错误:网络请求失败,每当我尝试将图片上传到 s3 存储桶时
- c++ - 更新链表中的节点
- javascript - 如何在 ngx-treeview 中更改占位符
- python - 将 Cloud Text to Speech 音频文件保存到云存储
- amazon-web-services - 从 AWS Lambda 发送自定义状态代码