前言
itsdangerous是flask中引用的一个第三方包,用来解决生成token等网络安全问题。
本篇博客将对itsdangerous官网的主要部分进行翻译。
主页
有时你想发送一些数据到不被信任的环境中时,然后稍后将其取回,为了安全,数据必须被签名,以防被篡改。
给定一个仅有你知道的key,你可以加密签名你的数据,并且把数据交给其他人。当你拿回数据时,你可以确认每人篡改它。
接收人可以看到数据,但是他们无法修改数据,除非接受人拥有你的key。所以,如果你能保持key的私密和复杂性,那么一切安好。
安装
使用pip进行安装和更新:
$ pip install -U itsdangerous
使用用例
- 对URL中的用户ID进行签名,并将URL发送给用户来取消订阅。这种方式下,你不需要生成一次性token并把它存入数据库。类似这种用户的激活链接都是如此。
- 签名对象可以被存储来cookies或其他不被信任的资源中,不被信任的资源的意思是,你不需要在server端保存session,从而减少了大量的数据库查询。
- 签名信息可以安全的往返于服务端和客户端,使数据的状态从服务端传递到客户端时有效。
基本概念
序列化器(Serializer)和签名器(Signer)
itsdangerous包提供了两种级别的数据处理。签名接口是基础系统,就是给定一个基于签名参数的bytes字符串。签名接口包装了一个可被序列化的签名器,签名其他数据在bytes字符串两边。
通常,你要使用序列化器时(而不是签名器),可以通过序列化器配置签名参数,甚至可以提供回调签名器用新的参数来更新旧的token。
秘钥(Secret Key)
通过秘钥签名是安全的,通常一个秘钥被用于所有签名器,并且用盐(salt)来区分不同的上下文。变更秘钥将会使所有现存token失效。
秘钥是一个长的、随机的、bytes类型的字符串。这个字符串一定要保密,不应该保存在源代码中或提交到版本控制系统中(例如github、gitlab)。如果一个攻击者知道了秘钥,他们就能合法的修改并解密数据。如果你怀疑这种事情已经发生了,那么就修改秘钥来使所有现存token失效。
一种保存秘钥的方式时,从环境变量中读取。当首次部署时,生成一个key,并在应用运行时设置环境变量。所有的进程管理工具(例如systemd)和托管服务都可以指定环境变量。
import os
from itsdangerous.serializer import Serializer
# 正常应该将秘钥存放于环境变量中
# SECRET_KEY = os.environ.get("SECRET_KEY")
# 生成随机key
SECRET_KEY = os.urandom(16).hex()
s = Serializer(SECRET_KEY)
盐(Salt)
盐是结合秘钥用于派生一个唯一的key用于区分不同上下文。不同于秘钥,盐没有随机性,而且能被存放在代码中。它在上下文之间必须唯一,而不是私有的。(换句话说,多个上下文公用同一个盐)
例如,你想通过发送含有激活链接的邮件来激活用户的账户,并且更新链接来匹配不同的账户。如果你签名的是用户id,并且你没有使用不同的盐,一个用户可以重新使用链接中的token来更新账户。如果你使用了不同的盐,签名将会不同,并不会在其他上下文中生效。
这里是我个人的一些理解。由于用‘盐’来区分不同的上下文,那么盐不变,程序即在同一上下文,链接中token有效,若盐变了(多个盐),上下文改变,链接中token失效。
from itsdangerous.url_safe import URLSafeSerializer
# 相同的key,不同的盐
secret_key = "secret-key"
s1 = URLSafeSerializer(secret_key, salt='activate')
r1 = s1.dumps(42)
print(r1)
s2 = URLSafeSerializer(secret_key, salt='upgrade')
r2 = s2.dumps(42)
print(r2)
print('=' * 50)
# 相同的key,相同的盐
secret_key = "secret-key"
s3 = URLSafeSerializer(secret_key, salt='activate')
r3 = s3.dumps(42)
print(r3)
s4 = URLSafeSerializer(secret_key, salt='activate')
r4 = s4.dumps(42)
print(r4)
# 加载数据
print("="*50)
print("加载数据")
print(s4.loads(s3.dumps(42)))
秘钥轮换(key rotation)
秘钥轮换可以提供一个额外的层,来减轻攻击者发现秘钥的概率。一个轮换系统将保存一个合法key的列表,周期性的生成和删除key。如果攻击者耗费4周能破解一个key,但是轮换系统将周期设为3周,攻击者将无法使用任何破解过的key。
然而,如果用户没有在3周内刷新token,token也将失效。
系统生成并维护在itsdangerous包的外面这个key列表,但itsdangerous支持验证key列表。
用于替代传递单个key,你可以传递一个key的列表,把旧的转换为新的。当签名的最后一个key被使用,并当每个key都被验证后,将抛出验证异常。
摘要方法安全(Digest Method Security)
每个签名器都配置了一个摘要方法(digest method),这是一个哈希函数,被用作和生成HMAC签名的中间步骤一样。默认方法是hashlib.sha1()。偶尔,用户们也担心这个默认方法,因为他们听说过SHA-1哈希碰撞。
当作为中间步骤使用时,HMAC的迭代步骤,SHA-1是不安全的。事实上,甚至MD5在HMAC中仍然安全。哈希的安全性将在HMAC中不被应用。
如果一个项目考虑SHA-1风险,可以对签名器配置不同的摘要方法,例如hashlib.sha512()。可以配置一个SHA-1的回调签名器,用来更新旧的token。SHA-512生成一个更长的hash字符串,所以token会在URL中或者cookies中占用更多空间。
序列化接口(Serialization Interface)
签名接口仅能签名bytes字符串。为了可以签名其他类型,Serializer类提供了dumps/loads接口,有些类似于python的json模块,将一个对象序列化为字符串,然后再对其签名。
from itsdangerous.serializer import Serializer
s = Serializer('secret-key')
# 使用dumps方法对数据签名并序列化
r1 = s.dumps([1,2,3,4])
print(r1)
# 用loads方法对数据确认签名并反序列化
r2 = s.loads(r1)
print(r2)
默认情况,数据用内建的json模块序列化为JSON格式,这种内部的序列化可以被子类更改。
为了记录和验证签名的时长,查看Signing With Timestamps。
为了在URL中安全的使用签名格式,查看URL Safe Serialization
响应失败
如果签名检查失败,异常中包含有用的属性,允许你检查载荷(payload)。这必须格外小心,因为此时您知道有人篡改了您的数据,但它可能对调试有用。
网友评论