You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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存在以下关键问题:

  1. IV使用不一致:Java固定使用全0的12字节IV,JS却用随机IV,导致双向加解密时IV不匹配,GCM认证标签验证失败。
  2. 密钥生成逻辑错误:generateKey函数硬编码使用配置中的masterKey,解密时传入的参数未生效,导致密钥生成错误。
  3. 加密数据格式不匹配:Java加密后直接输出「密文+标签」的Base64(GCM模式自动附加标签),JS却输出「IV+密文+标签」的组合,格式完全不符。
  4. 解密编码处理错误:使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 10:10:14