Java AES/GCM/NoPadding加密与JavaScript解密适配问题求助
AES/GCM跨语言加解密问题及JS修复方案
我正在使用128位AES/GCM/NoPadding加密算法,需要实现Java加密后在JavaScript中解密、JS加密后Java解密,但目前双向都报错:
- JS加密、Java解密时,报错:Tag mismatch!null
- Java加密、JS解密时,报错:
internal/crypto/cipher.js:164
const ret = this._handle.final();
^Error: Unsupported state or unable to authenticate data
at Decipheriv.final (internal/crypto/cipher.js:164:28)
at decrypt (/tmp/HoErdq6TQ2.js:51:58)
由于Java代码已在生产环境无法修改,请指出JS代码问题并给出修复方案。
Java生产环境代码
import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.Arrays; import javax.crypto.spec.SecretKeySpec; import javax.crypto.Cipher; import javax.crypto.spec.GCMParameterSpec; import java.util.logging.Logger; import java.util.Base64; public class HelloWorld { private final static Logger LOGGER = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); public static void main(String []args) { String masterKey = "2f12cb0f1d2e3d12345f1af2b123dce4"; String encrypted = aesEncryptStringV2("Hello, World!", masterKey); System.out.println(encrypted); String decrypted = aesDecryptStringV2(encrypted, masterKey); System.out.println(decrypted); } private static final String ALGORITHM = "AES/GCM/NoPadding"; private static final int GCM_IV_LENGTH = 12; private static final int GCM_TAG_LENGTH = 16; private static SecretKeySpec setKeyV2(final String myKey) { try { byte[] newKey = myKey.getBytes(StandardCharsets.UTF_8); MessageDigest sha = MessageDigest.getInstance("SHA-512"); newKey = sha.digest(newKey); newKey = Arrays.copyOf(newKey, 16); return new SecretKeySpec(newKey, "AES"); } catch (Exception e) { System.out.println("Error in setKeyV2: "); System.out.println(e.getMessage()); } return null; } public static synchronized String aesEncryptStringV2( final String strToEncrypt, final String secret) { try { SecretKeySpec newSecretKey = setKeyV2(secret); Cipher cipher = Cipher.getInstance(ALGORITHM); GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, new byte[GCM_IV_LENGTH]); cipher.init(Cipher.ENCRYPT_MODE, newSecretKey, gcmParameterSpec); return Base64.getEncoder() .encodeToString(cipher.doFinal(strToEncrypt.getBytes(StandardCharsets.UTF_8 ))); } catch (Exception e) { System.out.println("Error in aesEncryptStringV2: "); System.out.println(e.getMessage()); } return null; } public static synchronized String aesDecryptStringV2( final String strToDecrypt, final String secret) { try { SecretKeySpec newSecretKey = setKeyV2(secret); Cipher cipher = Cipher.getInstance(ALGORITHM); GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, new byte[GCM_IV_LENGTH]); cipher.init(Cipher.DECRYPT_MODE, newSecretKey, gcmParameterSpec); return new String(cipher.doFinal(Base64.getDecoder().decode(strToDecrypt))); } catch (Exception e) { System.out.println("Error in aesDecryptStringV2: "); System.out.println(e.getMessage()); } return null; } }
原JavaScript代码
const crypto = require('crypto'); const cryptoConfig = { cipherAlgorithm: 'aes-128-gcm', masterKey: '2f12cb0f1d2e3d12345f1af2b123dce4', ivLength: 12, tagLength: 16, digest: 'sha512' } const generateKey = () => { var h = crypto.createHash(cryptoConfig.digest); h.update(cryptoConfig.masterKey, 'utf8'); var k = h.digest().slice(0, 16); return k; }; function encrypt(content) { const iv = crypto.randomBytes(cryptoConfig.ivLength); const key = generateKey(); const cipher = crypto.createCipheriv(cryptoConfig.cipherAlgorithm, key, iv, {authTagLength: cryptoConfig.tagLength}); const encrypted = Buffer.concat([cipher.update(content, 'utf8'), cipher.final()]); const tag = cipher.getAuthTag(); return Buffer.concat([iv, encrypted, tag]).toString('base64'); } const decrypt = (encdata, masterkey) => { const bData = Buffer.from(encdata, 'base64'); const iv = bData.slice(0, 12); const tag = bData.slice(bData.length - 16, bData.length); const text = bData.slice(12, bData.length - 16); const key = generateKey(masterkey); const decipher = crypto.createDecipheriv('aes-128-gcm', key, iv); decipher.setAuthTag(tag); const decrypted = decipher.update(text, 'binary', 'utf8') + decipher.final('utf8'); return decrypted; }; const encryptedData = encrypt('hello world'); console.log('encrypt data -> ', encryptedData); const decryptedData = decrypt(encryptedData); console.log('decryptedData -> ', decryptedData);
JS代码问题分析
对比Java代码,JS存在以下关键问题:
- IV使用不一致:Java固定使用全0的12字节IV,JS却用随机IV,导致双向加解密时IV不匹配,GCM认证标签验证失败。
- 密钥生成逻辑错误:
generateKey函数硬编码使用配置中的masterKey,解密时传入的参数未生效,导致密钥生成错误。 - 加密数据格式不匹配:Java加密后直接输出「密文+标签」的Base64(GCM模式自动附加标签),JS却输出「IV+密文+标签」的组合,格式完全不符。
- 解密编码处理错误:使用
binary编码处理密文,易导致字节解析异常。
修复后的JavaScript代码
const crypto = require('crypto'); const cryptoConfig = { cipherAlgorithm: 'aes-128-gcm', ivLength: 12, tagLength: 16, digest: 'sha512' }; // 对齐Java的密钥生成逻辑,支持传入自定义masterKey const generateKey = (masterKey) => { const h = crypto.createHash(cryptoConfig.digest); h.update(masterKey, 'utf8'); return h.digest().slice(0, 16); }; // 对齐Java加密逻辑:使用全0IV,输出「密文+标签」的Base64 function encrypt(content, masterKey) { const iv = Buffer.alloc(cryptoConfig.ivLength, 0); const key = generateKey(masterKey); const cipher = crypto.createCipheriv(cryptoConfig.cipherAlgorithm, key, iv, { authTagLength: cryptoConfig.tagLength }); const encryptedContent = Buffer.concat([cipher.update(content, 'utf8'), cipher.final()]); const tag = cipher.getAuthTag(); return Buffer.concat([encryptedContent, tag]).toString('base64'); } // 对齐Java解密逻辑:使用全0IV,从密文末尾提取标签 const decrypt = (encdata, masterKey) => { const bData = Buffer.from(encdata, 'base64'); const encryptedContent = bData.slice(0, bData.length - cryptoConfig.tagLength); const tag = bData.slice(bData.length - cryptoConfig.tagLength); const iv = Buffer.alloc(cryptoConfig.ivLength, 0); const key = generateKey(masterKey); const decipher = crypto.createDecipheriv(cryptoConfig.cipherAlgorithm, key, iv); decipher.setAuthTag(tag); return Buffer.concat([decipher.update(encryptedContent), decipher.final()]).toString('utf8'); }; // 测试示例 const masterKey = '2f12cb0f1d2e3d12345f1af2b123dce4'; const encryptedData = encrypt('Hello, World!', masterKey); console.log('加密数据 -> ', encryptedData); const decryptedData = decrypt(encryptedData, masterKey); console.log('解密数据 -> ', decryptedData);
内容的提问来源于stack exchange,提问作者kashif-sk
相关产品推荐
相关产品推荐

