加密簽名

網頁應用程式安全性的黃金法則,就是永遠不要信任來自不可信任來源的資料。有時候,透過不可信任的媒介傳遞資料可能很有用。經過加密簽名的值可以透過不可信任的管道傳遞,並且知道任何篡改都會被偵測到。

Django 提供了低階 API 來簽署值,以及高階 API 來設定和讀取簽名 Cookie,這是網頁應用程式中最常見的簽署用途之一。

您可能也會發現簽署對於以下情況很有用:

  • 產生「恢復我的帳號」網址,發送給遺失密碼的使用者。

  • 確保儲存在隱藏表單欄位中的資料未被篡改。

  • 產生一次性秘密網址,允許臨時存取受保護的資源,例如使用者已付費下載的檔案。

保護 SECRET_KEYSECRET_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 參數不需要保持秘密。

驗證帶有時間戳記的值

TimestampSignerSigner 的子類別,它將簽名時間戳記附加到值。這允許您確認簽名值是在指定的時間段內建立的

>>> 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')[原始碼]
sign(value)[原始碼]

簽署 value 並將目前的時間戳記附加到其中。

unsign(value, max_age=None)[原始碼]

檢查 value 的簽署是否在 max_age 秒之前,否則會引發 SignatureExpiredmax_age 參數可以接受整數或 datetime.timedelta 物件。

sign_object(obj, serializer=JSONSerializer, compress=False)

編碼、選擇性壓縮、附加目前時間戳記,並簽署複雜的資料結構(例如列表、元組或字典)。

unsign_object(signed_obj, serializer=JSONSerializer, max_age=None)

檢查 signed_obj 的簽署是否在 max_age 秒之前,否則會引發 SignatureExpiredmax_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 簽名。

loads(string, key=None, salt='django.core.signing', serializer=JSONSerializer, max_age=None, fallback_keys=None)[原始碼]

dumps() 相反的操作,如果簽名失敗,則引發 BadSignature。如果給定了 max_age(以秒為單位),則檢查該值。

返回頂部