Fernet 是一种安全的消息传递格式,它的非持久、轻量级、加密的特性可以在保证安全的同时显著降低分布式系统运行所需的开销,近来已经得到了越来越广泛地运用。
概念上来说,Fernet 由一段用户提供的信息,一个 256位的key和当前时间信息,按一定的格式产生一个 token。在使用时,只有同时拿到 token 和 key,才能解析出用户传递的信息,信息的内容和格式都可以任意指定。
技术上看,Fernet 使用 AES-CBC 对用户信息加密,使用 SHA256 HMAC 进行签名,最后用 base64url 对结果编码用于传递。
格式
Key Format
Fernet key 是一个经过 base64url 编码的字符串,由两部分组成:
Signing-key ‖ Encryption-key
Signing-key 128 bits,用来签名
Encryption-key 128 bits,用来加密数据。
Token Format
Fernet token 也是经过 base64url 编码的一连串字符,组成如下:
Version ‖ Timestamp ‖ IV ‖ Ciphertext ‖ HMAC
Version, 8 bits,Fernet 版本号,目前只有一个 128 (0x80).
Timestamp, 64 bits,时间戳
IV, 128 bits,AES-CBC 加密初始化向量
Ciphertext, variable length, multiple of 128 bits,用户信息加密后的密文
HMAC, 256 bits,使用 SHA256 和 Signing-key 对前4部分签名后的值
生成过程
使用用户信息和 key 生成 fernet token 过程如下:
当前时间生成时间戳
随机生成初始向量 IV
构造密文
将用户信息补齐为 16bytes(128bit)的倍数
使用 AES 128 CBC 模式,IV 和 Encryption-key,对补齐后的信息加密
使用用户提供的 Signing-key 计算出 HMAC
按 Token 格式拼接上述所有字串
base64url 对字串编码生成最终 Token
验证过程
使用 key 验证 token,并获取用户信息的过程如下:
base64url 解码 Token
确认开头版本号为 0x80
获取创建时间戳,如果有 token 生命周期限制,可以和当前时间比较来判断 token 是否过期
使用 Signing-key 重新计算前半部分,得到HMAC
比较新生成 HMAC 与 Token 中的是否一致
使用 AES-CBC,IV 和 Encryption-key,解密密文
去掉补齐部分后返回用户信息
单Key使用
>>> from cryptography.fernet import Fernet >>> key = Fernet.generate_key() >>> f = Fernet(key) >>> token = f.encrypt(b"my deep dark secret") >>> token b'gAAAAABbn0KZnHPTdRV9j8jZQMwZrY8tnEkxrYhDE7watw1_oUHLdcr7e4nI2DKtNg-cNwE2duXiBFEjh1VozlpfNGejeknTPFSAk9pUWtSnQCN9hqMBlSA=' >>> f.decrypt(token) b'my deep dark secret' |
多Key使用
>>> from cryptography.fernet import Fernet, MultiFernet >>> key1 = Fernet(Fernet.generate_key()) >>> key2 = Fernet(Fernet.generate_key()) >>> f = MultiFernet([key1, key2]) >>> token = f.encrypt(b"Secret message!") >>> token b'...' >>> f.decrypt(token) b'Secret message!' |
MultiFernet 使用第一个 key 对信息加密,解密时遍历所有的 key 进行解密,这样保证了对 token 的前向兼容。
Keystone 用法
分布式系统里,Key 往往需要分发到许多节点,而长时间使用固定的 Key 会造成一定的安全隐患,OpenStack 里对 key 做了分类和统一管理,并提供了一个轮换机制避免这个问题:
Primary key:用来加密和解密 Token,始终拥有最大的编号
Secondary key:曾经是 Primary key,但是已经被降级,仅用来解密,编号仅次于 Primary key
Staged key:下一个 Primary key,可以用来解密,编号始终为0
现在再看这个轮换图,是不是清楚了很多。


适用场景
Fernet Token 常常用来做身份验证,类似原理的还有 JWT(JSON Web Token),它们的优点:
无状态,方便水平扩展
签名认证,安全可信
信息自包含,减少重复查询
缺点:
Token 较长,占用空间
使用http头传递,不能包括太多信息
传递时信息相当于明文,不能放敏感信息,最好配合 https 使用
需要额外生命周期管理和密钥管理
不能主动撤销和更新已生成的 Token
最适合它们的场景是一次性的短期认证和简单信息传递,比如 OpenStack 里的身份认证,权限控制等,充分发挥无状态认证的优势。




