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

NodeJS加密结果与Python cryptography.fernet加密结果不匹配问题问询

问题根因

1. 密钥长度报错原因

你传入的key是base64编码的字符串,长度为44个字符,并非算法需要的原始字节长度。AES-128-CBC要求密钥为16字节,而Fernet生成的base64密钥解码后是32字节,直接传入编码后的字符串自然会触发长度错误。

2. Fernet密钥拆分必要性

Fernet的32字节原始密钥确实分为两部分:前16字节是signing_key,用于HMAC签名校验;后16字节是encryption_key,用于AES加密。如果只做AES加密不处理签名逻辑,Python的Fernet解密时会因为签名校验失败直接报错。
Fernet密文有固定格式要求,最终输出不是单纯的AES密文,完整结构如下:

  • 固定版本号(1字节,值为0x80)
  • 时间戳(8字节,大端序存储)
  • 16字节随机IV
  • PKCS7填充后的AES密文
  • 32字节HMAC-SHA256签名(签名范围为前面所有字段的拼接结果,用signing_key计算)
  • 以上所有内容拼接后做base64url编码得到最终输出

完整兼容实现代码

const crypto = require("crypto");

// 处理原始Fernet密钥
const fernetKeyB64 = '6saGtiTFEXej729GUWSeAyQdIpRFdGhfY2XFUDpvsu8=';
// 将base64编码的密钥解码为32字节原始Buffer
const fernetKeyBuf = Buffer.from(fernetKeyB64, 'base64');
// 拆分签名密钥和加密密钥
const signingKey = fernetKeyBuf.subarray(0, 16);
const encryptionKey = fernetKeyBuf.subarray(16, 32);

// 加密逻辑
const message = 'Hello World';
const iv = crypto.randomBytes(16); // IV必须为16字节随机值
const cipher = crypto.createCipheriv('aes-128-cbc', encryptionKey, iv);
// NodeJS默认开启PKCS7填充,无需额外处理
const ciphertext = Buffer.concat([cipher.update(message, 'utf8'), cipher.final()]);

// 组装Fernet格式前缀
const version = Buffer.from([0x80]);
const timestamp = Buffer.alloc(8);
timestamp.writeBigUInt64BE(BigInt(Math.floor(Date.now() / 1000)));
const payloadPrefix = Buffer.concat([version, timestamp, iv, ciphertext]);

// 计算HMAC签名
const hmac = crypto.createHmac('sha256', signingKey);
hmac.update(payloadPrefix);
const signature = hmac.digest();

// 拼接所有内容后做base64url编码得到最终密文
const finalFernetBuf = Buffer.concat([payloadPrefix, signature]);
const encryptedStr = finalFernetBuf.toString('base64url');

console.log(encryptedStr);

上述代码生成的密文可直接用Python的Fernet库正常解密,若不需要校验时间戳,Python解密时传入ttl=None即可避免时间差导致的解密失败。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 07:06:00