加密簽名¶
網頁應用程式安全性的黃金法則,就是永遠不要信任來自不可信任來源的資料。有時候,透過不可信任的媒介傳遞資料可能很有用。經過加密簽名的值可以透過不可信任的管道傳遞,並且知道任何篡改都會被偵測到。
Django 提供了低階 API 來簽署值,以及高階 API 來設定和讀取簽名 Cookie,這是網頁應用程式中最常見的簽署用途之一。
您可能也會發現簽署對於以下情況很有用:
產生「恢復我的帳號」網址,發送給遺失密碼的使用者。
確保儲存在隱藏表單欄位中的資料未被篡改。
產生一次性秘密網址,允許臨時存取受保護的資源,例如使用者已付費下載的檔案。
保護 SECRET_KEY
和 SECRET_KEY_FALLBACKS
¶
當您使用 startproject
建立新的 Django 專案時,settings.py
檔案會自動產生,並且取得一個隨機的 SECRET_KEY
值。此值是保護簽名資料的關鍵 – 請務必保護此值,否則攻擊者可以使用它來產生他們自己的簽名值。
SECRET_KEY_FALLBACKS
可用於輪換密鑰。這些值不會用於簽署資料,但如果指定,它們將用於驗證簽名資料,且必須保持安全。
使用低階 API¶
Django 的簽名方法位於 django.core.signing
模組中。要簽署一個值,首先要實例化一個 Signer
實例
>>> from django.core.signing import Signer
>>> signer = Signer()
>>> value = signer.sign("My string")
>>> value
'My string:GdMGD6HNQ_qdgxYP8yBZAdAIV1w'
簽名附加在字串的末尾,在冒號之後。您可以使用 unsign
方法來檢索原始值
>>> original = signer.unsign(value)
>>> original
'My string'
如果您將非字串值傳遞給 sign
,該值將在簽名前被強制轉換為字串,並且 unsign
結果會給您該字串值
>>> signed = signer.sign(2.5)
>>> original = signer.unsign(signed)
>>> 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(key="my-other-secret")
>>> value = signer.sign("My string")
>>> value
'My string:EkfQJafvGyiofrdGnuthdxImIJw'
- class Signer(*, key=None, sep=':', salt=None, algorithm=None, fallback_keys=None)[原始碼]¶
返回一個使用
key
來產生簽名並使用sep
來分隔值的簽名器。sep
不能在 URL 安全的 base64 字母中。這個字母包含字母數字字元、連字符和底線。algorithm
必須是hashlib
支援的演算法,預設為'sha256'
。fallback_keys
是用於驗證簽名資料的額外值列表,預設為SECRET_KEY_FALLBACKS
。
使用 salt
參數¶
如果您不希望特定字串的每個出現都具有相同的簽名雜湊值,您可以將可選的 salt
參數傳遞給 Signer
類別。使用 salt 會使用 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 會將不同的簽名放入不同的命名空間中。來自一個命名空間(特定的 salt 值)的簽名不能用於驗證使用不同 salt 設定的不同命名空間中的相同純文字字串。結果是防止攻擊者使用在程式碼中一個地方產生的簽名字串,作為另一個使用不同 salt 產生(和驗證)簽名的程式碼的輸入。
與您的 SECRET_KEY
不同,您的 salt 參數不需要保持秘密。
驗證帶有時間戳記的值¶
TimestampSigner
是 Signer
的子類別,它將簽名時間戳記附加到值。這允許您確認簽名值是在指定的時間段內建立的
>>> 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'
- class TimestampSigner(*, key=None, sep=':', salt=None, algorithm='sha256')[原始碼]¶
-
- unsign(value, max_age=None)[原始碼]¶
檢查
value
的簽署是否在max_age
秒之前,否則會引發SignatureExpired
。max_age
參數可以接受整數或datetime.timedelta
物件。
- sign_object(obj, serializer=JSONSerializer, compress=False)¶
編碼、選擇性壓縮、附加目前時間戳記,並簽署複雜的資料結構(例如列表、元組或字典)。
- unsign_object(signed_obj, serializer=JSONSerializer, max_age=None)¶
檢查
signed_obj
的簽署是否在max_age
秒之前,否則會引發SignatureExpired
。max_age
參數可以接受整數或datetime.timedelta
物件。
保護複雜資料結構¶
如果您希望保護列表(list)、元組(tuple)或字典(dictionary),您可以使用 Signer.sign_object()
和 unsign_object()
方法,或者簽名模組的 dumps()
或 loads()
函數(它們是 TimestampSigner(salt='django.core.signing').sign_object()/unsign_object()
的快捷方式)。這些方法底層使用 JSON 序列化。JSON 確保即使您的 SECRET_KEY
被竊取,攻擊者也無法透過利用 pickle 格式來執行任意命令。
>>> from django.core import signing
>>> signer = signing.TimestampSigner()
>>> value = signer.sign_object({"foo": "bar"})
>>> value
'eyJmb28iOiJiYXIifQ:1kx6R3:D4qGKiptAqo5QW9iv4eNLc6xl4RwiFfes6oOcYhkYnc'
>>> signer.unsign_object(value)
{'foo': 'bar'}
>>> value = signing.dumps({"foo": "bar"})
>>> value
'eyJmb28iOiJiYXIifQ:1kx6Rf:LBB39RQmME-SRvilheUe5EmPYRbuDBgQp2tCAi7KGLk'
>>> signing.loads(value)
{'foo': 'bar'}
由於 JSON 的特性(列表和元組之間沒有原生的區別),如果您傳入一個元組,您將從 signing.loads(object)
得到一個列表。
>>> from django.core import signing
>>> value = signing.dumps(("a", "b", "c"))
>>> signing.loads(value)
['a', 'b', 'c']
- dumps(obj, key=None, salt='django.core.signing', serializer=JSONSerializer, compress=False)[原始碼]¶
返回 URL 安全、已簽名的 base64 壓縮 JSON 字串。序列化的物件使用
TimestampSigner
簽名。