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
相关产品推荐
相关产品推荐

