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

PHP AES-GCM加密后JavaScript解密报错求助

AES-GCM跨语言加解密错误修复方案

问题根源分析

原代码存在三个核心问题:

  1. PHP端重复拼接Tag:加密逻辑中两次拼接Tag,导致最终数据格式混乱,无法正确拆分
  2. 二进制Tag与字符串分隔符冲突:GCM的Tag是二进制数据,直接和||拼接可能包含分隔符,导致解密时分割错误
  3. Web Crypto API使用错误:原JS代码将密文和Tag拼接后传入解密函数,但Web Crypto要求Tag单独作为参数传递

修复后的完整代码

PHP加密代码

function encode(string $input): string
{
    return base64EncodeSafe(encodeOpenSsl($input));
}

function encodeOpenSsl(string $string): string
{
    if (!defined('CRYPT_KEY') || !defined('CRYPT_CIPHER')) {
        return $string;
    }

    // 强制校验算法为AES-GCM
    if (!str_ends_with(strtolower(CRYPT_CIPHER), 'gcm')) {
        throw new Exception('Cipher must be AES-GCM type');
    }

    $key = hex2bin(CRYPT_KEY);
    $ivLength = openssl_cipher_iv_length(CRYPT_CIPHER);
    // AES-GCM标准IV长度为12字节,强制校验避免兼容问题
    if ($ivLength !== 12) {
        throw new Exception('Invalid IV length for AES-GCM');
    }

    // 生成强随机IV
    $iv = openssl_random_pseudo_bytes($ivLength, $cstrong);
    if (!$iv || !$cstrong) {
        throw new Exception('Failed to generate secure IV');
    }

    // 执行加密,获取二进制密文和Tag
    $encrypted = openssl_encrypt(
        $string,
        CRYPT_CIPHER,
        $key,
        OPENSSL_RAW_DATA,
        $iv,
        $tag
    );

    if (!$encrypted || empty($tag)) {
        throw new Exception('Encryption failed');
    }

    // 按固定长度拼接:IV(12字节) + 密文 + Tag(16字节)
    $encryptedData = $iv . $encrypted . $tag;
    return base64_encode($encryptedData);
}

function base64EncodeSafe(string $string): string
{
    return str_replace(['/', '+'], ['_', '-'], base64_encode($string));
}

JavaScript解密代码

async function decode(input) {
    return await decodeOpenSsl(base64DecodeSafe(input));
}

function base64DecodeSafe(string) {
    // 恢复标准base64格式并补全填充字符
    string = string.replace(/_/g, '/').replace(/-/g, '+');
    const padLength = (4 - (string.length % 4)) % 4;
    string += '='.repeat(padLength);
    return atob(string);
}

async function decodeOpenSsl(data) {
    const key = hexStringToByteArray(CRYPT_KEY);
    const binaryData = new Uint8Array(
        data.split('').map(c => c.charCodeAt(0))
    );

    const ivLength = 12; // AES-GCM标准IV长度
    const tagLength = 16; // AES-GCM默认128位Tag长度

    // 按固定长度拆分IV、密文、Tag
    const iv = binaryData.slice(0, ivLength);
    const encrypted = binaryData.slice(ivLength, -tagLength);
    const tag = binaryData.slice(-tagLength);

    // 导入原始密钥
    const cryptoKey = await crypto.subtle.importKey(
        'raw',
        key,
        { name: 'AES-GCM' },
        false,
        ['decrypt']
    );

    // 执行解密,Tag单独传入参数
    const decryptedBuffer = await crypto.subtle.decrypt(
        {
            name: 'AES-GCM',
            iv: iv,
            additionalData: new Uint8Array(),
            tagLength: 128,
            tag: tag
        },
        cryptoKey,
        encrypted
    );

    return new TextDecoder().decode(decryptedBuffer);
}

function hexStringToByteArray(hexString) {
    const result = [];
    for (let i = 0; i < hexString.length; i += 2) {
        result.push(parseInt(hexString.substring(i, i + 2), 16));
    }
    return new Uint8Array(result);
}

关键注意事项

  • 确保两端CRYPT_KEY完全一致:AES-256-GCM对应64位十六进制字符串,AES-128-GCM对应32位十六进制字符串
  • CRYPT_CIPHER需统一设置为aes-256-gcm或aes-128-gcm
  • 若使用附加数据(additionalData),两端需保持完全一致

内容的提问来源于stack exchange,提问作者Haidrex

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 12:45:10