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

window.crypto.subtle.decrypt抛出OperationError问题的排查与解决

前端RSA-OAEP解密会话密钥时抛出OperationError的解决方法

问题描述

开发会话加密方案时,前端调用window.crypto.subtle.decrypt使用私钥解密会话密钥时,抛出了OperationError。

相关代码

前端密钥生成代码

// 生成密钥的方法
static async generateKeysForUser(): Promise<{ publicKey: string, privateKey: string }> {
    const keyPair = await window.crypto.subtle.generateKey(
        {
            name: "RSA-OAEP",
            modulusLength: 2048,
            publicExponent: new Uint8Array([1, 0, 1]), // 65537
            hash: "SHA-1",
        },
        true,
        ["encrypt", "decrypt"]
    );

    const publicKey = await window.crypto.subtle.exportKey("jwk", keyPair.publicKey);
    const privateKey = await window.crypto.subtle.exportKey("jwk", keyPair.privateKey);

    const publicKeyStr = JSON.stringify(publicKey);
    const privateKeyStr = JSON.stringify(privateKey);

    await this.storeKeys(privateKeyStr, publicKeyStr);

    return publicKey;
}

后端会话密钥加密代码

// 后端生成会话密钥的方法
public static function generateSessionKey($length = 32)
{
    return bin2hex(random_bytes($length)); // 32字节 = 256位
}

public static function jsonToPem($publicKeyJson)
{
    $jwkObject = new JWK($publicKeyJson);
    $rsaKey = RSAKey::createFromJWK($jwkObject);
    return $rsaKey->toPEM();
}

public static function encryptSessionKey($sessionKey, $publicKeyJson): string
{
    $publicKeyPem = self::jsonToPem($publicKeyJson);
    $publicKey = openssl_pkey_get_public($publicKeyPem);
    openssl_public_encrypt($sessionKey, $encryptedSessionKey, $publicKey);
    return base64_encode($encryptedSessionKey);
}

前端解密代码

// 前端解密会话密钥的方法
private static async importKey(jwk: JsonWebKey, keyType: 'private' | 'public'): Promise<CryptoKey> {
    console.log(jwk);
    let operation = keyType == 'private' ? 'decrypt' : 'encrypt';
    console.log(keyType, operation);
    let result = await window.crypto.subtle.importKey(
        "jwk",
        jwk,
        {
            name: "RSA-OAEP",
            hash: "SHA-1"
        },
        true,
        [operation]
    );
    console.log(result);
    return result;
}

static async decryptSessionKey(encryptedSessionKeyBase64: string, jwk: JsonWebKey): Promise<ArrayBuffer | null> {
    try {
        let privateKey = await this.importKey(jwk, "private");

        const encryptedSessionKey = Uint8Array.from(atob(encryptedSessionKeyBase64), c => c.charCodeAt(0));

        console.log('Encrypted Session Key (Uint8Array):', encryptedSessionKey);
        console.log('Private Key:', privateKey);

        const result = await window.crypto.subtle.decrypt(
            {
                name: "RSA-OAEP"
            },
            privateKey,
            encryptedSessionKey
        );

        console.log('Decrypted Result (ArrayBuffer):', result);

        return result;
    } catch (e) {
        console.error('Decryption failed:', e);
        return null;
    }
}

排查与解决方案

已确认前后端密钥一致,尝试将哈希算法从SHA-256改为SHA-1后问题仍存在,最终定位问题出在填充方式不匹配:

前端使用的RSA-OAEP算法默认使用OAEP填充,而PHP的openssl_public_encrypt函数默认使用PKCS#1 v1.5填充,两者不兼容导致解密失败。

修改后端加密代码,在openssl_public_encrypt中添加OPENSSL_PKCS1_OAEP_PADDING参数,指定使用OAEP填充即可解决问题:

public static function encryptSessionKey($sessionKey, $publicKeyJson): string
{
    $publicKeyPem = self::jsonToPem($publicKeyJson);
    $publicKey = openssl_pkey_get_public($publicKeyPem);
    openssl_public_encrypt($sessionKey, $encryptedSessionKey, $publicKey, OPENSSL_PKCS1_OAEP_PADDING);
    return base64_encode($encryptedSessionKey);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 12:07:03