一、概述
web应用程序安全的黄金法则是永远不要信任来自不可信来源的数据。有时,通过不受信任的介质传递数据会很有用。加密签名的值可以通过不受信任的通道安全传递,只要知道将检测到任何篡改。
Django既提供了用于签名值的低级API,也提供了用于设置和读取签名Cookie的高级API,这是web应用程序中最常见的签名用途之一。
二、签名
1、简介
数字签名必须保证以下三点:
报文鉴别——接收者能够核实发送者对报文的签名;
报文的完整性——接收者不能伪造对报文的签名或更改报文内容。
不可否认——发送者事后不能抵赖对报文的签名;
2、作用面
- 生成”找回我的账户”URL 以发送给丢失密码的用户。
- 确认存储在表单隐藏字段中的数据未被篡改。
- 生成一次性的秘密 URL,允许临时访问受保护的资源,例如用户付费下载的文件。
三、django的加密签名
1、保护SECRET_KEY
当你创建一个新的Django项目时,Setting.py文件会自动生成,并随机得到一个值SECRET_KEY。这个值是保证签名数据安全的关键——你必须保证这个值的安全,否则攻击者可以用它来生成自己的签名值。
2、 使用 django.core.signing进行签名
Django 的签名方法位于 django.core.signing
模块中。要签署一个值,首先要实例化一个 Signer
实例:
>>> from django.core.signing import Signer
>>> signer = Signer()
>>> value = signer.sign('username')
>>> value
'username:Y9MJdx50aqtq_Ff88wshyhCylAi6oQKHbPB_65w4yOg'
签名后的值被附加在字符串的结尾,在冒号之后。可以使用 unsign
方法检索原始值:
>>> original = signer.unsign(value)
>>> original
'username'
如果你将非字符串值传递给 sign
,该值将在被签署前被强制变成字符串,并且 unsign
结果将返回此字符串值:
>>> singed = signer.sign(2.5)
>>> singed
'2.5:dkvV2k6Me2AdREwCy5kfEGUdKVevYr9dsQGSzGylbyA'
>>> original = signer.unsign(singed)
>>> original
'2.5'
如果要保护列表、元组或字典,可以使用sign_object()和unsign_object()方法:
>>> signed_obj = signer.sign_object({'message': 'Hello!'})
>>> signed_obj
'eyJtZXNzYWdlIjoiSGVsbG8hIn0:Xdc-mOFDjs22KsQAqfVfi8PQSPdo3ckWJxPWwQOFhR4'
>>> obj = signer.unsign_object(signed_obj)
>>> obj
{'message': 'Hello!'}
如果签名或值被以任何方式修改,将引发 django.core.signing.BadSignature
异常:
>>> from django.core import signing
>>> value += 'm'
>>> try:
... original = signer.unsign(value)
... except signing.BadSignature:
... print("Tampering detected!")
默认情况下, Signer
类使用 SECRET_KEY配置来生成签名。你可以使用不同的密钥传入 Signer
构造函数生成不同的签名:
>>> signer = Signer('my-other-secret')
>>> value = signer.sign('username')
>>> value
'username:MH1z_8LMKdcAIdQzqk9faEN8fb--PHRLO9xB3ly3OXE'
3、Signer的源码解析
class Signer:
def __init__(self, key=None, sep=":", salt=None, algorithm=None):
self.key = key or settings.SECRET_KEY # 签名的密钥
self.sep = sep # 分隔符
if _SEP_UNSAFE.match(self.sep):
raise ValueError(
"Unsafe Signer separator: %r (cannot be empty or consist of "
"only A-z0-9-_=)" % sep,
) # 对传入分隔符进行限制
self.salt = salt or "%s.%s" % (
self.__class__.__module__,
self.__class__.__name__,
) # 如果有加盐参数则使用,否则使用模块名.类名
self.algorithm = algorithm or "sha256" # 默认为sha256
def signature(self, value):
"对传入的值进行加密"
return base64_hmac(
self.salt + "signer", value, self.key, algorithm=self.algorithm
)
def sign(self, value):
"""
返回签名-值:签名值
username:MH1z_8LMKdcAIdQzqk9faEN8fb--PHRLO9xB3ly3OXE
"""
return "%s%s%s" % (value, self.sep, self.signature(value))
def unsign(self, signed_value):
"""对签名值进行还原"""
# 判断分隔符是否在签名后的参数中
if self.sep not in signed_value:
raise BadSignature('No "%s" found in value' % self.sep)
# 对签名后的值以分隔符分割,取后面的签名值
value, sig = signed_value.rsplit(self.sep, 1)
# 将取出的签名值,与value再度签名的值进行比较
if constant_time_compare(sig, self.signature(value)):
return value
raise BadSignature('Signature "%s" does not match' % sig)
4、使用 salt
参数
如果你不希望一个特定字符串的每一次出现都有相同的签名哈希值,你可以使用 Signer
类的可选 salt
参数。 使用盐后会将 盐和你的 SECRET_KEY作为签名哈希函数的种子。
>>> signer = Signer()
>>> signer.sign('My string')
'My string:GdMGD6HNQ_qdgxYP8yBZAdAIV1w'
>>> signer.sign_object({'message': 'Hello!'})
'eyJtZXNzYWdlIjoiSGVsbG8hIn0:Xdc-mOFDjs22KsQAqfVfi8PQSPdo3ckWJxPWwQOFhR4'
>>> signer = Signer(salt='extra')
>>> signer.sign('My string')
'My string:Ee7vGi-ING6n02gkcJ-QLHg6vFw'
>>> signer.unsign('My string:Ee7vGi-ING6n02gkcJ-QLHg6vFw')
'My string'
>>> signer.sign_object({'message': 'Hello!'})
'eyJtZXNzYWdlIjoiSGVsbG8hIn0:-UWSLCE-oUAHzhkHviYz3SOZYBjFKllEOyVZNuUtM-I'
>>> signer.unsign_object('eyJtZXNzYWdlIjoiSGVsbG8hIn0:-UWSLCE-oUAHzhkHviYz3SOZYBjFKllEOyVZNuUtM-I')
{'message': 'Hello!'}
从源代码中可以看出,只要改变salt加盐参数的值,即可以生成不同的签名哈希值。在源代码中默认的加盐为”main.Signer”+sigin
def __init__(self, key=None, sep=":", salt=None, algorithm=None):
self.key = key or settings.SECRET_KEY # 签名的密钥
self.sep = sep # 分隔符
if _SEP_UNSAFE.match(self.sep):
raise ValueError(
"Unsafe Signer separator: %r (cannot be empty or consist of "
"only A-z0-9-_=)" % sep,
) # 对传入分隔符进行限制
self.salt = salt or "%s.%s" % (
self.__class__.__module__,
self.__class__.__name__,
) # 如果有加盐参数则使用,否则使用模块名.类名
self.algorithm = algorithm or "sha256" # 默认为sha256
def signature(self, value):
"对传入的值进行加密"
return base64_hmac(
self.salt + "signer", value, self.key, algorithm=self.algorithm
)
以这种方式使用盐,会将不同的签名放入不同的命名空间。 来自一个命名空间的签名(一个特定的盐值)不能用于验证在使用不同盐值设置的不同命名空间中的同一明文字符串。这样做的结果是防止攻击者将代码中某个地方生成的签名字符串作为输入,输入到使用不同盐值生成(和验证)签名的另一段代码中。
5、验证时间戳值
TimestampSigner
是 Signer的子类,它给值附加一个签名的时间戳。这允许你确认一个签名的值是在特定时间内创建的:特定时间指的就是在进行转换还原时,传入一个datetime.timedelta类型的值,该值要大于(现在的时间-签名的时间)
>>> from datetime import timedelta
>>> from django.core.signing import TimestampSigner
>>> signer = TimestampSigner()
>>> value = signer.sign('hello')
>>> value
'hello:1NMg5H:oPVuCqlJWmChm1rA2lyTUtelC-c'
>>> signer.unsign(value)
'hello'
>>> signer.unsign(value, max_age=10)
...
SignatureExpired: Signature age 15.5289158821 > 10 seconds
>>> signer.unsign(value, max_age=20)
'hello'
>>> signer.unsign(value, max_age=timedelta(seconds=20))
'hello'
TimestampSigner源码(只是将传入需要进行签名的值,进行了二次包装,新值=旧值:当前的时间戳,然后再进行签名)
class TimestampSigner(Signer):
def timestamp(self):
return b62_encode(int(time.time()))
def sign(self, value):
value = "%s%s%s" % (value, self.sep, self.timestamp())
return super().sign(value)
def unsign(self, value, max_age=None):
"""
Retrieve original value and check it wasn't signed more
than max_age seconds ago.
"""
result = super().unsign(value)
value, timestamp = result.rsplit(self.sep, 1)
timestamp = b62_decode(timestamp)
if max_age is not None:
if isinstance(max_age, datetime.timedelta):
max_age = max_age.total_seconds()
# Check timestamp is not older than max_age
age = time.time() - timestamp
if age > max_age:
raise SignatureExpired("Signature age %s > %s seconds" % (age, max_age))
return value
Original: https://blog.csdn.net/adminwg/article/details/127143021
Author: simpleyako
Title: Django-加密签名
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/737116/
转载文章受原作者版权保护。转载请注明原作者出处!