Python Fernet加密数据在Node.js aes-128-cbc解密时提示IV长度无效
问题原因与修复方案
核心错误点
- 对Fernet格式理解错误:Fernet的32字节密钥base64解码后,前16字节是HMAC签名密钥,后16字节才是AES加密密钥,IV不存在于密钥中,而是固定存储在密文头部。
- 传参类型错误:Node.js的
crypto.createDecipheriv要求密钥、IV传入Buffer类型,你将Buffer转成binary字符串传入,长度计算失准,直接触发IV长度无效报错。 - 编码逻辑错误:Python端
Fernet.encrypt()返回值本身已经是base64字符串,你又额外做了一次base64编码,等于双重编码;同时请求头声明application/json但实际传form表单数据,格式不匹配。
Fernet标准密文结构
Fernet密文base64解码后按字节拆分如下:
- 第1字节:固定版本号
0x80 - 第2-9字节:8字节时间戳
- 第10-25字节:16字节AES-CBC初始向量IV
- 第26字节到倒数32字节:AES加密后的实际密文
- 最后32字节:HMAC-SHA256签名,用于校验密文完整性
修复后代码
Python端
import requests from cryptography.fernet import Fernet def get_key(): return Fernet.generate_key() def enc(input_text, key): f = Fernet(key) return f.encrypt(input_text.encode()) msg = "hello world" key = get_key() cypher = enc(msg, key) print("msg: " + msg) print("key: " + key.decode()) print("cyp: " + cypher.decode()) url='http://127.0.0.1:8088/test' # 传form表单无需加json请求头,去掉多余的二次base64编码 r = requests.post(url, data={"key": key.decode(), "msg": cypher.decode()})
Node.js端
const express = require('express'); const crypto = require('crypto'); const app = express(); const port = 8088; app.use(express.urlencoded({extended: true, limit:'100mb', parameterLimit:1000000})); app.disable('x-powered-by'); function decrypt(fernetKeyB64, ciphertextB64) { // 解码密钥,拆分HMAC签名密钥与AES密钥 const rawKey = Buffer.from(fernetKeyB64, 'base64'); const signingKey = rawKey.slice(0, 16); const aesKey = rawKey.slice(16, 32); // 解码密文,按Fernet规范拆分各段 const rawCipher = Buffer.from(ciphertextB64, 'base64'); if (rawCipher[0] !== 0x80) throw new Error('无效的Fernet版本标识'); const iv = rawCipher.slice(9, 25); // 从密文头部提取16字节IV const ciphertext = rawCipher.slice(25, rawCipher.length - 32); const hmacSign = rawCipher.slice(rawCipher.length - 32); // 校验HMAC签名,防止密文被篡改 const hmac = crypto.createHmac('sha256', signingKey); hmac.update(rawCipher.slice(0, rawCipher.length - 32)); const calcSign = hmac.digest(); if (!crypto.timingSafeEqual(calcSign, hmacSign)) throw new Error('密文签名校验失败'); // AES-128-CBC解密,自动处理PKCS7填充 const decipher = crypto.createDecipheriv('aes-128-cbc', aesKey, iv); const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]); return decrypted.toString('utf8'); } app.post("/test", function(req, res) { const fernetKey = req.body.key; const cipherMsg = req.body.msg; console.log("收到key: " + fernetKey); console.log("收到密文: " + cipherMsg); try { const decResult = decrypt(fernetKey, cipherMsg); console.log("解密结果: " + decResult); res.writeHead(200, {"Content-Type": "application/json"}); res.end(JSON.stringify({"status": "OK", "data": decResult})); } catch (e) { console.error("解密失败:", e.message); res.writeHead(500, {"Content-Type": "application/json"}); res.end(JSON.stringify({"status": "ERROR", "msg": e.message})); } }); app.listen(port, () => { console.log("Listening on localhost:" + port); });
内容的提问来源于stack exchange,提问作者Siedler
相关产品推荐
相关产品推荐

