AES/GCM/NoPadding 从节点加密并在 java 上解密,抛出 AEADBadTagException:标签不匹配
AES/GCM/NoPadding encrypt from node and decrypt on java, throw AEADBadTagException: Tag mismatch
我想用 NodeJS 加密数据,然后用 Java 解密数据。在网上搜索了很多例子,都没有找到合适的答案。
NodeJS代码如下:
const crypto = require('crypto');
function encrypt(content) {
const salt = crypto.randomBytes(cryptoConfig.saltLength);
const iv = crypto.randomBytes(cryptoConfig.ivLength);
const key = crypto.pbkdf2Sync(cryptoConfig.masterKey, salt, cryptoConfig.iterations,
cryptoConfig.keyLength, cryptoConfig.digest);
const cipher = crypto.createCipheriv(cryptoConfig.cipherAlgorithm, key, iv);
const encrypted = Buffer.concat([cipher.update(content, 'utf8'), cipher.final()]);
const tag = cipher.getAuthTag();
const encdata = Buffer.concat([salt, iv, tag, encrypted]).toString('base64');
return encdata;
}
const cryptoConfig = {
cipherAlgorithm: 'aes-256-gcm',
masterKey: 'sfcpnnjFG6dULJfo1BEGqczpfN0SmwZ6bgKO5FcDRfI=',
iterations: 65535,
keyLength: 32,
saltLength: 16,
ivLength: 12,
tagLength: 16,
digest: 'sha512'
}
const encryptedData = encrypt('hello world');
console.log('encrypt data ->', 加密数据);
JAVA代码:
private static final Charset UTF_8 = StandardCharsets.UTF_8;
private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
private static final int TAG_LENGTH = 16;
private static final int IV_LENGTH = 12;
private static final int SALT_LENGTH = 16;
private static final int KEY_LENGTH = 16;
private static final int ITERATIONS = 65535;
public static String decrypt(String cipherContent, String password) throws Exception {
byte[] decode = Base64.getDecoder().decode(cipherContent.getBytes(UTF_8));
ByteBuffer bb = ByteBuffer.wrap(decode);
byte[] salt = new byte[SALT_LENGTH];
bb.get(salt);
byte[] iv = new byte[IV_LENGTH];
bb.get(iv);
byte[] content = new byte[bb.remaining()];
bb.get(content);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
SecretKey aesKeyFromPassword = getAESKeyFromPassword(password.toCharArray(), salt);
cipher.init(Cipher.DECRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(TAG_LENGTH * 8, iv));
byte[] plainText = cipher.doFinal(content);
return new String(plainText, UTF_8);
}
private static SecretKey getAESKeyFromPassword(char[] password, byte[] salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
KeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH * 8);
SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
return secret;
}
public static void main(String[] args) throws Exception {
String masterKey = "sfcpnnjFG6dULJfo1BEGqczpfN0SmwZ6bgKO5FcDRfI=";
String encryptedData = "<NodeJS return encryptedData>";
String decryptedText = decrypt(encryptedData, masterKey);
System.out.println("Decrypted: " + decryptedText));
}
执行后抛出异常:
Exception in thread "main" javax.crypto.AEADBadTagException: Tag mismatch!
at com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:571)
at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1046)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:983)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:845)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at com.xxx.common.AesCrypto.decrypt(AesCrypto.java:92)
at com.xxx.common.AesCrypto.main(AesCrypto.java:131)
我怎样才能把这个场景改成运行通过
您的代码中总共有 2 个问题阻止您在 NodeJS/Crypto 和 Java 之间成功加密,但是有 2 个更改的代码行它会按预期工作。
NodeJs: 在加密函数的末尾,您将解密所需的参数连接到一个更大的数组。由于 Java 需要 ciphertext:gcmTag
输入,您应该更改行
const encdata = Buffer.concat([salt, iv, tag, encrypted]).toString('base64');
// ### put the auth tag at the end of encrypted
to
const encdata = Buffer.concat([salt, iv, encrypted, tag]).toString('base64');
Java:您要求使用 AES GCM 256 加密,这意味着您必须在两侧使用 256/8 = 32 字节长的密钥,
所以只需更改常量
private static final int KEY_LENGTH = 16; // ### key length for aes gcm 256 is 32 byte
to:
private static final int KEY_LENGTH = 32;
您可以在此处 运行 两个在线代码,NodeJs:https://repl.it/@javacrypto/SoNodeJsCryptoAesGcm256Pbkdf2StringEncryption#index.js 和 Java:
https://repl.it/@javacrypto/SoJavaAesGcm256Pbkdf2StringDecryption#Main.java
输出如下:
节点:
AES GCMC 256 String encryption with PBKDF2 derived key
plaintext: The quick brown fox jumps over the lazy dog
ciphertext: InFN0Zz5MGLTOr5B0xFnvU1k2dPPZPSen7pCWiQguCEQe6KSBSAQjHtUvIeuA7jcl8fK7L63xbq+tsHe9HRYupp554tbEESjrCbsCh1lW2exWDp4YLQd
Java:
AES GCMC 256 String decryption with PBKDF2 derived key
Decrypted: The quick brown fox jumps over the lazy dog
安全警告:代码无异常处理,仅供学习使用。
节点:
var crypto = require('crypto');
console.log('AES GCMC 256 String encryption with PBKDF2 derived key');
var plaintext = 'The quick brown fox jumps over the lazy dog';
console.log('plaintext: ', plaintext);
const cryptoConfig = {
cipherAlgorithm: 'aes-256-gcm',
masterKey: 'sfcpnnjFG6dULJfo1BEGqczpfN0SmwZ6bgKO5FcDRfI=',
iterations: 65535,
keyLength: 32,
saltLength: 16,
ivLength: 12,
tagLength: 16,
digest: 'sha512'
}
var ciphertext = encrypt(plaintext);
console.log('ciphertext: ', ciphertext);
function encrypt(content) {
const salt = crypto.randomBytes(cryptoConfig.saltLength);
const iv = crypto.randomBytes(cryptoConfig.ivLength);
const key = crypto.pbkdf2Sync(cryptoConfig.masterKey, salt, cryptoConfig.iterations,
cryptoConfig.keyLength, cryptoConfig.digest);
const cipher = crypto.createCipheriv(cryptoConfig.cipherAlgorithm, key, iv);
const encrypted = Buffer.concat([cipher.update(content, 'utf8'), cipher.final()]);
const tag = cipher.getAuthTag();
// ### put the auth tag at the end of encrypted
//const encdata = Buffer.concat([salt, iv, tag, encrypted]).toString('base64');
const encdata = Buffer.concat([salt, iv, encrypted, tag]).toString('base64');
return encdata;
}
Java:
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;
public class Main {
private static final Charset UTF_8 = StandardCharsets.UTF_8;
private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
private static final int TAG_LENGTH = 16;
private static final int IV_LENGTH = 12;
private static final int SALT_LENGTH = 16;
//private static final int KEY_LENGTH = 16; // ### key length for aes gcm 256 is 32 byte
private static final int KEY_LENGTH = 32;
private static final int ITERATIONS = 65535;
public static void main(String[] args) throws Exception {
System.out.println("AES GCMC 256 String decryption with PBKDF2 derived key");
String masterKey = "sfcpnnjFG6dULJfo1BEGqczpfN0SmwZ6bgKO5FcDRfI=";
String encryptedData = "YPp3VDo8NeNoXONTHyJ2oy7nSHbFNK31CbA8YU5KBW3ybJeOt+0rLiYQq4y62WknGgYnjom9hT1fqBnXZphZAy5EoOOXh/U1wcJSSO4cQ086GJf6mXCa";
String decryptedText = decrypt(encryptedData, masterKey);
System.out.println("Decrypted: " + decryptedText);
}
public static String decrypt(String cipherContent, String password) throws Exception {
byte[] decode = Base64.getDecoder().decode(cipherContent.getBytes(UTF_8));
ByteBuffer bb = ByteBuffer.wrap(decode);
byte[] salt = new byte[SALT_LENGTH];
bb.get(salt);
byte[] iv = new byte[IV_LENGTH];
bb.get(iv);
byte[] content = new byte[bb.remaining()];
bb.get(content);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
SecretKey aesKeyFromPassword = getAESKeyFromPassword(password.toCharArray(), salt);
cipher.init(Cipher.DECRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(TAG_LENGTH * 8, iv));
byte[] plainText = cipher.doFinal(content);
return new String(plainText, UTF_8);
}
private static SecretKey getAESKeyFromPassword(char[] password, byte[] salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
KeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH * 8);
SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
return secret;
}
}
我想用 NodeJS 加密数据,然后用 Java 解密数据。在网上搜索了很多例子,都没有找到合适的答案。 NodeJS代码如下:
const crypto = require('crypto');
function encrypt(content) {
const salt = crypto.randomBytes(cryptoConfig.saltLength);
const iv = crypto.randomBytes(cryptoConfig.ivLength);
const key = crypto.pbkdf2Sync(cryptoConfig.masterKey, salt, cryptoConfig.iterations,
cryptoConfig.keyLength, cryptoConfig.digest);
const cipher = crypto.createCipheriv(cryptoConfig.cipherAlgorithm, key, iv);
const encrypted = Buffer.concat([cipher.update(content, 'utf8'), cipher.final()]);
const tag = cipher.getAuthTag();
const encdata = Buffer.concat([salt, iv, tag, encrypted]).toString('base64');
return encdata;
}
const cryptoConfig = {
cipherAlgorithm: 'aes-256-gcm',
masterKey: 'sfcpnnjFG6dULJfo1BEGqczpfN0SmwZ6bgKO5FcDRfI=',
iterations: 65535,
keyLength: 32,
saltLength: 16,
ivLength: 12,
tagLength: 16,
digest: 'sha512'
}
const encryptedData = encrypt('hello world'); console.log('encrypt data ->', 加密数据);
JAVA代码:
private static final Charset UTF_8 = StandardCharsets.UTF_8;
private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
private static final int TAG_LENGTH = 16;
private static final int IV_LENGTH = 12;
private static final int SALT_LENGTH = 16;
private static final int KEY_LENGTH = 16;
private static final int ITERATIONS = 65535;
public static String decrypt(String cipherContent, String password) throws Exception {
byte[] decode = Base64.getDecoder().decode(cipherContent.getBytes(UTF_8));
ByteBuffer bb = ByteBuffer.wrap(decode);
byte[] salt = new byte[SALT_LENGTH];
bb.get(salt);
byte[] iv = new byte[IV_LENGTH];
bb.get(iv);
byte[] content = new byte[bb.remaining()];
bb.get(content);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
SecretKey aesKeyFromPassword = getAESKeyFromPassword(password.toCharArray(), salt);
cipher.init(Cipher.DECRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(TAG_LENGTH * 8, iv));
byte[] plainText = cipher.doFinal(content);
return new String(plainText, UTF_8);
}
private static SecretKey getAESKeyFromPassword(char[] password, byte[] salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
KeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH * 8);
SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
return secret;
}
public static void main(String[] args) throws Exception {
String masterKey = "sfcpnnjFG6dULJfo1BEGqczpfN0SmwZ6bgKO5FcDRfI=";
String encryptedData = "<NodeJS return encryptedData>";
String decryptedText = decrypt(encryptedData, masterKey);
System.out.println("Decrypted: " + decryptedText));
}
执行后抛出异常:
Exception in thread "main" javax.crypto.AEADBadTagException: Tag mismatch!
at com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:571)
at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1046)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:983)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:845)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at com.xxx.common.AesCrypto.decrypt(AesCrypto.java:92)
at com.xxx.common.AesCrypto.main(AesCrypto.java:131)
我怎样才能把这个场景改成运行通过
您的代码中总共有 2 个问题阻止您在 NodeJS/Crypto 和 Java 之间成功加密,但是有 2 个更改的代码行它会按预期工作。
NodeJs: 在加密函数的末尾,您将解密所需的参数连接到一个更大的数组。由于 Java 需要 ciphertext:gcmTag
输入,您应该更改行
const encdata = Buffer.concat([salt, iv, tag, encrypted]).toString('base64');
// ### put the auth tag at the end of encrypted
to
const encdata = Buffer.concat([salt, iv, encrypted, tag]).toString('base64');
Java:您要求使用 AES GCM 256 加密,这意味着您必须在两侧使用 256/8 = 32 字节长的密钥, 所以只需更改常量
private static final int KEY_LENGTH = 16; // ### key length for aes gcm 256 is 32 byte
to:
private static final int KEY_LENGTH = 32;
您可以在此处 运行 两个在线代码,NodeJs:https://repl.it/@javacrypto/SoNodeJsCryptoAesGcm256Pbkdf2StringEncryption#index.js 和 Java: https://repl.it/@javacrypto/SoJavaAesGcm256Pbkdf2StringDecryption#Main.java
输出如下:
节点:
AES GCMC 256 String encryption with PBKDF2 derived key
plaintext: The quick brown fox jumps over the lazy dog
ciphertext: InFN0Zz5MGLTOr5B0xFnvU1k2dPPZPSen7pCWiQguCEQe6KSBSAQjHtUvIeuA7jcl8fK7L63xbq+tsHe9HRYupp554tbEESjrCbsCh1lW2exWDp4YLQd
Java:
AES GCMC 256 String decryption with PBKDF2 derived key
Decrypted: The quick brown fox jumps over the lazy dog
安全警告:代码无异常处理,仅供学习使用。
节点:
var crypto = require('crypto');
console.log('AES GCMC 256 String encryption with PBKDF2 derived key');
var plaintext = 'The quick brown fox jumps over the lazy dog';
console.log('plaintext: ', plaintext);
const cryptoConfig = {
cipherAlgorithm: 'aes-256-gcm',
masterKey: 'sfcpnnjFG6dULJfo1BEGqczpfN0SmwZ6bgKO5FcDRfI=',
iterations: 65535,
keyLength: 32,
saltLength: 16,
ivLength: 12,
tagLength: 16,
digest: 'sha512'
}
var ciphertext = encrypt(plaintext);
console.log('ciphertext: ', ciphertext);
function encrypt(content) {
const salt = crypto.randomBytes(cryptoConfig.saltLength);
const iv = crypto.randomBytes(cryptoConfig.ivLength);
const key = crypto.pbkdf2Sync(cryptoConfig.masterKey, salt, cryptoConfig.iterations,
cryptoConfig.keyLength, cryptoConfig.digest);
const cipher = crypto.createCipheriv(cryptoConfig.cipherAlgorithm, key, iv);
const encrypted = Buffer.concat([cipher.update(content, 'utf8'), cipher.final()]);
const tag = cipher.getAuthTag();
// ### put the auth tag at the end of encrypted
//const encdata = Buffer.concat([salt, iv, tag, encrypted]).toString('base64');
const encdata = Buffer.concat([salt, iv, encrypted, tag]).toString('base64');
return encdata;
}
Java:
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;
public class Main {
private static final Charset UTF_8 = StandardCharsets.UTF_8;
private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
private static final int TAG_LENGTH = 16;
private static final int IV_LENGTH = 12;
private static final int SALT_LENGTH = 16;
//private static final int KEY_LENGTH = 16; // ### key length for aes gcm 256 is 32 byte
private static final int KEY_LENGTH = 32;
private static final int ITERATIONS = 65535;
public static void main(String[] args) throws Exception {
System.out.println("AES GCMC 256 String decryption with PBKDF2 derived key");
String masterKey = "sfcpnnjFG6dULJfo1BEGqczpfN0SmwZ6bgKO5FcDRfI=";
String encryptedData = "YPp3VDo8NeNoXONTHyJ2oy7nSHbFNK31CbA8YU5KBW3ybJeOt+0rLiYQq4y62WknGgYnjom9hT1fqBnXZphZAy5EoOOXh/U1wcJSSO4cQ086GJf6mXCa";
String decryptedText = decrypt(encryptedData, masterKey);
System.out.println("Decrypted: " + decryptedText);
}
public static String decrypt(String cipherContent, String password) throws Exception {
byte[] decode = Base64.getDecoder().decode(cipherContent.getBytes(UTF_8));
ByteBuffer bb = ByteBuffer.wrap(decode);
byte[] salt = new byte[SALT_LENGTH];
bb.get(salt);
byte[] iv = new byte[IV_LENGTH];
bb.get(iv);
byte[] content = new byte[bb.remaining()];
bb.get(content);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
SecretKey aesKeyFromPassword = getAESKeyFromPassword(password.toCharArray(), salt);
cipher.init(Cipher.DECRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(TAG_LENGTH * 8, iv));
byte[] plainText = cipher.doFinal(content);
return new String(plainText, UTF_8);
}
private static SecretKey getAESKeyFromPassword(char[] password, byte[] salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
KeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH * 8);
SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
return secret;
}
}