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

Next.js应用中RSA加密随机生成255字节而非预期256字节问题求助

RSA加密在Next.js浏览器环境随机生成255字节输出导致解密失败

问题描述

在Next.js应用中使用Crypto进行RSA加密时,会随机生成255字节的输出而非预期的256字节,导致解密失败。Node.js环境下运行该代码完全正常,但浏览器环境中会随机出现此问题。当前使用的Node版本为v16.20.2。

复现代码

const publicKey = `-----BEGIN CERTIFICATE-----
MIIELzCCAxegAwIBAgIIArnMylZGlBgwDQYJKoZIhvcNAQELBQAwaTELMAkGA1UE
BhMCSU4xFDASBgNVBAgMC01haGFyYXNodHJhMQ8wDQYDVQQHDAZNdW1iYWkxDTAL
BgNVBAoMBEF4aXMxETAPBgNVBAsMCEF4aXNCYW5rMREwDwYDVQQDDAhBUElHV0JN
UzAeFw0yMTEwMjYwODQ2MjFaFw0yMzExMTUwODQ2MjFaMGkxCzAJBgNVBAYTAklO
MRQwEgYDVQQIDAtNYWhhcmFzaHRyYTEPMA0GA1UEBwwGTXVtYmFpMQ0wCwYDVQQK
DARBeGlzMREwDwYDVQQLDAhBeGlzQmFuazERMA8GA1UEAwwIQVBJR1dCTVMwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLkALNhDxRPhW3clvfwgf8W2Wq
kWa/Te4Qkpn+jl/By2m6uu6nf6LBDDDfAbS1tzrn7BntQvowk4gY6umBTEOj9kfu
TUneWW9O3Qgao+Qe8IUySd92B4fKLtlsg4ugbkYi6YKz+Ab+cbzOZlR6Y8Yhf+JM
yfNIcj8tErmt3QtTergKtawtwwCr7vkDXSpkxNsmtFWVPBeC8YD9B1/01M7M/8bU
raVK1SD3ZrgCHi4ppEWbJMPuH0cUqhu1GOZFJyT/s5K5/1GsSbWmXzg4Gw3aZJoH
cdombtRH7VA5AWCchpxkLXk/QWKAbfb4XL7PwvgCz7eL6HCz00Jg0Wipjhh3AgMB
AAGjgdowgdcwDAYDVR0TBAUwAwEB/zAdBgNVHQ4EFgQU5JguYg2Bu0pa8zm3/m8C
+gWCKdIwgZoGA1UdIwSBkjCBj4AU5JguYg2Bu0pa8zm3/m8C+gWCKdKhbaRrMGkx
CzAJBgNVBAYTAklOMRQwEgYDVQQIDAtNYWhhcmFzaHRyYTEPMA0GA1UEBwwGTXVt
YmFpMQ0wCwYDVQQKDARBeGlzMREwDwYDVQQLDAhBeGlzQmFuazERMA8GA1UEAwwI
QVBJR1dCTVOCCAK5zMpWRpQYMAsGA1UdDwQEAwICvDANBgkqhkiG9w0BAQsFAAOC
AQEAsWnpwN9Pv7jSqOssAfb0UPqiE3q4J2G1M8HK/bjZEESaNmUX3ugBG5tsFLj9
RIjhxBMqpjzWGTrBhDItFiSecE/bb5a0TuZ3YWENp8MeShfpAkbldxiL2ivYNUL8
NNIXHm1WFRxcrjKsaUVeEJ7/IFP0r6eSX6Sg3HONWdw+yde0D+VJC5NAkjnmVJhd
eZcQXHuRmmwJWpSXvznewRbV+OhDWyY8MOs5RHqOhpi6IEwmFvdRXC4N5gM7eurp
diZWzhJiS5isDSVlx8ogeGLxxY/6wFixGzBS/1a1GMhvkLYylY/81IT7ViyI07QK
HqlxrbngzQkiAA/ydoXzdMEUQQ==
-----END CERTIFICATE-----`;

const rsaEncryption = (incomingData:string) => {
    const rsaEncryptedData =  crypto?.publicEncrypt(
        {
            key:        publicKey,
            padding:    crypto.constants.RSA_PKCS1_OAEP_PADDING,
            oaepHash:   "SHA-1"
        },
        Buffer.from(incomingData)
    );

    if(rsaEncryptedData.length < 256) {
        console.error('xxx rsaEncryptedData length: ', rsaEncryptedData.length)
        console.error('xxx rsaEncryptedData: ', rsaEncryptedData)
        console.error('xxx incomingData: ', incomingData, '\n incomingData length: ', incomingData.length, '\n','\n ====end====')
    } else {
        console.log('success, expected output ', rsaEncryptedData.length)
    }

    return { rsaEncryptedData }
}

const startEncryption =() => {
    const data= 'CF9FNcUpvrzExofwhFKvxameOQoe8iZz'
    let condition = 256;
    while(condition >= 256){
        const receivedData = rsaEncryption(data);
        condition = receivedData.rsaEncryptedData.length;
        if(condition < 256) {
            alert();
            console.error('xxx receivedData:\n ', receivedData.rsaEncryptedData.length, '\n -----end-')
        }
    }
}

startEncryption()

(注:原代码中rsaEncryption函数返回类型标注为string但实际返回对象,已修正)

问题原因

浏览器环境的Web Crypto API与Node.js的crypto模块在处理RSA-OAEP加密输出时存在差异:

  • Node.js的publicEncrypt方法会始终返回固定长度的Buffer(2048位RSA密钥对应256字节),即使输出二进制数据前导为零也会保留。
  • 浏览器环境中,加密结果以ArrayBuffer形式返回,转换为其他格式时可能自动省略前导零字节,导致长度变为255字节,而RSA解密需要严格的256字节输入,因此解密失败。

解决方案

方案1:补全前导零到256字节

在浏览器环境中检查加密结果长度,若不足256字节则在前面补零,确保输出长度固定为256字节。修改rsaEncryption函数如下:

const rsaEncryption = async (incomingData: string) => {
    let rsaEncryptedData;
    if (typeof window !== 'undefined') {
        // 浏览器环境使用Web Crypto API
        const encoder = new TextEncoder();
        const data = encoder.encode(incomingData);
        // 从证书中提取公钥
        const certDer = pemToArrayBuffer(publicKey);
        const cert = await window.crypto.subtle.importKey(
            "spki",
            certDer,
            { name: "RSA-OAEP", hash: "SHA-1" },
            false,
            []
        );
        const publicKeyObj = await window.crypto.subtle.exportKey("spki", cert);
        const importedKey = await window.crypto.subtle.importKey(
            "spki",
            publicKeyObj,
            { name: "RSA-OAEP", hash: "SHA-1" },
            false,
            ["encrypt"]
        );
        const encrypted = await window.crypto.subtle.encrypt(
            { name: "RSA-OAEP" },
            importedKey,
            data
        );
        // 补全前导零到256字节
        const buffer = new Uint8Array(256);
        buffer.set(new Uint8Array(encrypted), 256 - encrypted.byteLength);
        rsaEncryptedData = Buffer.from(buffer);
    } else {
        // Node.js环境使用原有逻辑
        rsaEncryptedData = crypto.publicEncrypt(
            {
                key: publicKey,
                padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
                oaepHash: "SHA-1"
            },
            Buffer.from(incomingData)
        );
    }

    if (rsaEncryptedData.length < 256) {
        console.error('xxx rsaEncryptedData length: ', rsaEncryptedData.length);
        console.error('xxx rsaEncryptedData: ', rsaEncryptedData);
        console.error('xxx incomingData: ', incomingData, '\n incomingData length: ', incomingData.length, '\n','\n ====end====');
    } else {
        console.log('success, expected output ', rsaEncryptedData.length);
    }

    return { rsaEncryptedData };
};

// PEM格式转ArrayBuffer
function pemToArrayBuffer(pem: string) {
    const b64 = pem.replace(/-----BEGIN CERTIFICATE-----|-----END CERTIFICATE-----|\n/g, '');
    const binary = atob(b64);
    const arrayBuffer = new ArrayBuffer(binary.length);
    const uint8Array = new Uint8Array(arrayBuffer);
    for (let i = 0; i < binary.length; i++) {
        uint8Array[i] = binary.charCodeAt(i);
    }
    return arrayBuffer;
}

方案2:统一使用Web Crypto API

Node.js v15及以上版本已支持Web Crypto API,可跨环境统一使用该API,避免环境差异。这样无论在浏览器还是Node.js中,加密逻辑一致,输出处理也统一。

注意事项

  • RSA-OAEP加密的输入数据长度有限制:2048位密钥配合SHA-1哈希时,最大明文长度为256 - 2*20 - 2 = 214字节,需确保输入数据不超过该长度。
  • 确保Next.js中代码的环境判断正确,避免在服务端和客户端混用不同的加密API。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 15:47:02