Node.js与Java AES-GCM 256跨语言加解密互兼容问题求助
AES-GCM跨Java/Node.js加解密兼容问题解决
核心问题分析
- Java代码错误:
GCMParameterSpec构造参数使用错误,第一个参数应为认证标签(Tag)的长度(单位:比特),而非AAD的长度。你代码里误用了aad.length*8,虽数值巧合等于128比特(16字节),但未调用updateAAD()导致AAD为空;另外Java中doFinal()返回的字节数组是密文 + Tag的组合,密文长度等于明文长度,并非固定16字节。 - Node.js代码错误:错误固定截取前16字节作为密文,应从末尾取16字节作为Tag,剩余部分为密文;同时
decipher.update()的编码参数使用错误,密文已为Buffer,无需再按base64解析。
修正后的Java加密代码
import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.util.Base64; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; public class Encoder3 { public static void main(String[] args) throws Exception { String base64Key = "XHgxl8qs5D6sejZncSsYg7OqPIeV0uQe5I9Zh+uHLcc="; String originalString = "Hello, NODE JS!"; // GCM标准推荐IV长度为12字节(96比特) byte[] iv = new byte[12]; new SecureRandom().nextBytes(iv); // 若不需要AAD可注释以下两行,需传递AAD给Node.js时保留 byte[] aad = new byte[16]; new SecureRandom().nextBytes(aad); byte[] keyBytes = Base64.getDecoder().decode(base64Key); SecretKey key = new SecretKeySpec(keyBytes, "AES"); Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); // 第一个参数为Tag长度(128比特=16字节),第二个为IV GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv); cipher.init(Cipher.ENCRYPT_MODE, key, gcmSpec); // 使用AAD时必须调用此方法,否则跳过 cipher.updateAAD(aad); // doFinal返回结果:密文 + Tag byte[] cipherTextWithTag = cipher.doFinal(originalString.getBytes(StandardCharsets.UTF_8)); String encodedString = Base64.getEncoder().encodeToString(cipherTextWithTag); String ivString = Base64.getEncoder().encodeToString(iv); String aadString = Base64.getEncoder().encodeToString(aad); // 使用AAD时传递此值 System.out.println("Encoded String (密文+Tag): " + encodedString); System.out.println("IV String: " + ivString); System.out.println("AAD String: " + aadString); // 使用AAD时输出 } }
修正后的Node.js解密代码
const crypto = require('crypto'); function decrypt(encodedString, iv, base64Key, base64Aad = '') { const key = Buffer.from(base64Key, 'base64'); const ivBuffer = Buffer.from(iv, 'base64'); const cipherTextWithTagBuffer = Buffer.from(encodedString, 'base64'); // 从末尾截取16字节作为Tag,剩余部分为密文 const tag = cipherTextWithTagBuffer.slice(-16); const cipherText = cipherTextWithTagBuffer.slice(0, -16); const decipher = crypto.createDecipheriv('aes-256-gcm', key, ivBuffer); // Java使用AAD时必须设置,否则跳过 if (base64Aad) { decipher.setAAD(Buffer.from(base64Aad, 'base64')); } decipher.setAuthTag(tag); // 直接传递Buffer,无需base64编码参数 let decrypted = decipher.update(cipherText, undefined, 'utf-8'); decrypted += decipher.final('utf-8'); return decrypted; } // Example usage const base64Key = 'XHgxl8qs5D6sejZncSsYg7OqPIeV0uQe5I9Zh+uHLcc='; // 替换为Java输出的Encoded String const encodedStringFromJava = '替换为Java生成的密文+Tag的Base64字符串'; // 替换为Java输出的IV String const iv = '替换为Java生成的IV的Base64字符串'; // Java使用AAD时替换为对应值,否则留空 const aad = ''; const decodedString = decrypt(encodedStringFromJava, iv, base64Key, aad); console.log('Decoded String:', decodedString);
关键注意事项
- IV规范:GCM模式优先使用12字节(96比特)IV,这是行业标准,兼顾安全性与性能。
- Tag一致性:两端Tag长度必须统一,常用128比特(16字节)。
- AAD同步:若任意一方使用AAD,另一方必须传入完全相同的AAD值,否则解密会触发认证失败。
- 密文格式:Java输出的密文是「明文长度的密文 + 16字节Tag」,Node.js需按此动态拆分,不能固定截取长度。
内容的提问来源于stack exchange,提问作者Priyabrata Nayak
相关产品推荐
相关产品推荐

