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

使用AES-GCM+Base64实现JS加密PHP解密失败求助

AES-GCM跨JS加密/PHP解密失败排查

我尝试用AES-GCM加密算法结合Base64编码在JS中加密数据,再在PHP中解密,但解密始终返回false,且无任何openssl错误。已确认前后端传输的数据一致,Laravel的composer.json中也添加了"ext-openssl": "*",请问哪里操作有误?


前端JS加密方法

async function encryptData(data, key) {
    const encoder = new TextEncoder();
    const encodedData = encoder.encode(data);
    // Ensure the key is 256 bits (32 bytes)
    if (key.length !== 32) {
        throw new Error('AES key must be 256 bits (32 bytes)');
    }
    const cryptoKey = await crypto.subtle.importKey('raw', new TextEncoder().encode(key), {
        name: 'AES-GCM'
    }, false, ['encrypt']);
    const iv = crypto.getRandomValues(new Uint8Array(12));
    const encrypted = await crypto.subtle.encrypt({
        name: 'AES-GCM',
        iv
    }, cryptoKey, encodedData);
    // Concatenate IV and encrypted data and convert to base64
    const combinedData = new Uint8Array(iv.length + encrypted.byteLength);
    combinedData.set(iv);
    combinedData.set(new Uint8Array(encrypted), iv.length);
    // Convert to base64 using btoa
    const base64String = btoa(String.fromCharCode.apply(null, combinedData));
    return base64String; // Return the base64 string without URL encoding
}

前端JS提交加密数据代码

async function submitData() {
    var licence = $('#licence').val();
    const encryptionKey = '12345678901234567890123456789012';

    try {
        let encryptedLicence = await encryptData(licence, encryptionKey);

        var jsonData = {
            licence: encryptedLicence
        };

        var queryParams = Object.keys(jsonData)
            .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(jsonData[key]))
            .join('&');
        var targetUrl = 'submit?' + queryParams;
        window.location.href = targetUrl;
    } catch (error) {
        console.error('Error encrypting data:', error.message);
    }
}

后端PHP接收数据代码

public function formData(Request $request){                                           
    $key = '12345678901234567890123456789012';                     
    $data=$request->licence;                                       
    $decryptedProduct = self::decryptingMyData($data, $key);     
    dd($decryptedProduct);
}

后端PHP解密方法

public function decryptingMyData($encryptedData, $key) {                                                          
    // URL-decode the received data                                                                                 
    $receivedData = urldecode($encryptedData);                                                                      
    $decodedData = base64_decode($receivedData);                                                                    
    $iv = substr($decodedData, 0, 12);                                                                              
    $encryptedText = substr($decodedData, 12);                                                                      
    $decrypted = openssl_decrypt($encryptedText, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv);                     
    if ($decrypted === false) {                                                                                     
        // Decryption failed                                                                                          
        $opensslError = openssl_error_string();                                                                      
        dd($decrypted ,$opensslError);                                                                                           
        return decrypted;                                                                                                
    }                                                                    
    return $decrypted;                                                                                                                                    
}

问题原因与修正方案

核心问题

  1. AES-GCM认证标签未处理:JS的crypto.subtle.encrypt在AES-GCM模式下会自动生成16字节的认证标签,并附加在密文末尾。但PHP的openssl_decrypt在GCM模式下需要单独传入该标签才能完成解密验证,原代码未拆分标签导致验证失败。
  2. 多余的URL解码:Laravel的Request对象已经自动处理了URL解码,手动调用urldecode会破坏Base64结构(比如把+转成空格)。
  3. 语法错误:解密失败分支中return decrypted;是未定义变量,属于语法错误。

修正后的PHP解密方法

public function decryptingMyData($encryptedData, $key) {                                                          
    // Laravel已自动处理URL解码,无需手动调用urldecode
    $decodedData = base64_decode($encryptedData);                                                                    
    // 拆分IV(12字节)、密文、认证标签(16字节)
    $iv = substr($decodedData, 0, 12);                                                                              
    $encryptedText = substr($decodedData, 12, -16); // 提取中间的密文部分
    $tag = substr($decodedData, -16); // 提取最后16字节作为认证标签
    
    // 传入标签参数完成GCM解密验证
    $decrypted = openssl_decrypt($encryptedText, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag);                     
    if ($decrypted === false) {                                                                                     
        $opensslError = openssl_error_string();                                                                      
        dd($decrypted, $opensslError);                                                                                           
        return false;
    }                                                                    
    return $decrypted;                                                                                                                                    
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 21:20:23