Node.js中WhatsApp Flows解密报错:RangeError: Invalid key length
WhatsApp Flow 端点解密/加密实现(解决Invalid key length错误)
问题场景
使用Node.js解密WhatsApp Flow加密数据时,触发crypto.createDecipheriv的Invalid key length错误,需完整实现请求解密与响应加密逻辑。
加密数据
{ "encrypted_flow_data": "EfdsCol+ZQMdkWYaa+cB247XRCfTbMkfdwR68ZwU1pQgby4P8tR2l7qDDjh/uLCkzutO0GuMQxoVfm/sluoj4OuMzsd9AttTerts4vcvaDWrbBcXpH5r3m4vu6iNwTQHV4kmL2sETB1g22VbEA==", "encrypted_aes_key": "A01kXb7211vxwfZbSlxN9fxL/HUWBhxjVovJ6jirVzUYK7CYLnlcBoR4AwIWIiP3NSra8DRvVd9gulu+jdDJEbf+8HZHk1k5EllcSFV/r/x1PLwpQ6v8AeOhbfEp8qyjSxb3Ah6/CrY6oDWDLiyqpAWz3KK4UmWw9VxiJOHdLhvfa3lH22zwReboiJyUPFbjKlzKUudnS8EI5JU+ZUW3reEOB5JS8bMJ06asHYm723VUvC3cs8qv1/G02NPlOo3G9sknDSr3fo0uIm5drvq3flLW6Mvcx469Y97x3j44RjEIyXMIs8zUyOo5h9MrTuwruPsjhcBFIByDHH7wwTMPQw==", "initial_vector": "oqVIt/A7/MWVY4hE9IHb6A==" }
原代码核心问题
- 私钥异步读取未等待结果,直接传入解密函数导致变量未赋值
- 私钥变量名前后不一致(
privateKeyPEMvsprivateKeyPem) - AES密钥做了冗余的Base64编码/解码操作,破坏原始密钥长度
- 定义了IV翻转函数但未实际使用,不符合WhatsApp Flow要求
- 未处理异步操作的错误捕获
修复后的请求解密代码
const crypto = require('crypto'); const fs = require('fs').promises; // 使用Promise版文件操作 // 翻转IV字节顺序(WhatsApp Flow强制要求) function flipIV(ivBuffer) { return Buffer.from(ivBuffer.reverse()); } async function decryptFlowRequest(reqBody) { try { // 1. 解析请求参数 const { encrypted_flow_data, encrypted_aes_key, initial_vector } = reqBody; // 2. 读取并加载私钥 const privateKeyPem = await fs.readFile('/path/to/your/private_key.pem', 'utf-8'); const privateKey = crypto.createPrivateKey({ key: privateKeyPem, format: 'pem' }); // 3. RSA-OAEP-SHA256解密AES密钥 const encryptedAesKey = Buffer.from(encrypted_aes_key, 'base64'); const aesKey = crypto.privateDecrypt({ key: privateKey, oaepHash: 'sha256', padding: crypto.constants.RSA_PKCS1_OAEP_PADDING }, encryptedAesKey); // 4. 处理IV(翻转字节) const iv = flipIV(Buffer.from(initial_vector, 'base64')); // 5. AES-256-GCM解密Flow数据 const encryptedFlowData = Buffer.from(encrypted_flow_data, 'base64'); // 分离密文与16字节GCM认证标签 const ciphertext = encryptedFlowData.slice(0, -16); const authTag = encryptedFlowData.slice(-16); const decipher = crypto.createDecipheriv('aes-256-gcm', aesKey, iv); decipher.setAuthTag(authTag); let decrypted = decipher.update(ciphertext); decrypted = Buffer.concat([decrypted, decipher.final()]); return JSON.parse(decrypted.toString('utf-8')); } catch (err) { console.error('解密失败:', err); throw err; } } // Express路由使用示例 // app.post('/flow-endpoint', async (req, res) => { // const decryptedData = await decryptFlowRequest(req.body); // console.log('解密后的请求数据:', decryptedData); // // 业务逻辑处理... // });
响应加密实现
// 加载WhatsApp提供的公钥(用于加密AES密钥) async function loadWhatsAppPublicKey() { const publicKeyPem = await fs.readFile('/path/to/whatsapp_public_key.pem', 'utf-8'); return crypto.createPublicKey({ key: publicKeyPem, format: 'pem' }); } async function encryptFlowResponse(responseData, waPublicKey) { try { // 1. 生成随机AES-256密钥和12字节IV(GCM推荐长度) const aesKey = crypto.randomBytes(32); const iv = crypto.randomBytes(12); const flippedIv = flipIV(iv); // 翻转IV字节 // 2. AES-256-GCM加密响应数据 const cipher = crypto.createCipheriv('aes-256-gcm', aesKey, iv); let encryptedData = cipher.update(JSON.stringify(responseData), 'utf-8'); encryptedData = Buffer.concat([encryptedData, cipher.final()]); const authTag = cipher.getAuthTag(); // 拼接密文与认证标签 const encryptedFlowData = Buffer.concat([encryptedData, authTag]); // 3. RSA-OAEP-SHA256加密AES密钥 const encryptedAesKey = crypto.publicEncrypt({ key: waPublicKey, oaepHash: 'sha256', padding: crypto.constants.RSA_PKCS1_OAEP_PADDING }, aesKey); // 4. 返回符合格式的加密响应 return { encrypted_flow_response: encryptedFlowData.toString('base64'), encrypted_aes_key: encryptedAesKey.toString('base64'), initial_vector: flippedIv.toString('base64') }; } catch (err) { console.error('加密失败:', err); throw err; } } // Express路由使用示例 // app.post('/flow-endpoint', async (req, res) => { // const decryptedData = await decryptFlowRequest(req.body); // // 业务逻辑处理,生成响应数据 // const responseData = { status: 'success', data: { /* 自定义响应内容 */ } }; // const waPublicKey = await loadWhatsAppPublicKey(); // const encryptedResponse = await encryptFlowResponse(responseData, waPublicKey); // res.json(encryptedResponse); // });
关键注意事项
- IV翻转: 必须对初始向量执行字节翻转操作,否则解密/加密会失败
- 密钥长度: AES-256要求32字节密钥,直接使用RSA解密后的原始Buffer,避免冗余编码
- 异步处理: 所有文件读取、加密解密操作需用异步逻辑,避免阻塞事件循环
- 错误捕获: 必须捕获加密解密过程中的异常,防止服务崩溃
内容的提问来源于stack exchange,提问作者vishal chaturvedi
相关产品推荐
相关产品推荐

