暗号署名¶
ウェブアプリケーションのセキュリティの黄金律は、信頼できない情報源からのデータを決して信頼しないことです。時には、信頼されていない媒体を通してデータを渡すことが有用な場合もあります。暗号的に署名された値は、改ざんが検知されることを前提に、信頼されていない経路を安全に通過させることができます。
Django は、値の署名に関する低レベル API と、Web アプリケーションでの署名の最も一般的な用途の一つである署名付きクッキーの設定と読み取りに関する高レベル API の両方を提供しています。
また、以下のような場合にも署名が役に立つでしょう:
パスワードを紛失したユーザーに送信する「アカウントの回復」URLの生成。
非表示のフォームフィールドに保存されたデータが改ざんされていないことの確認。
保護されたリソースへの一時的なアクセスを許可するためのワンタイムシークレットURLの生成。例えばユーザーが料金を支払ったダウンロード可能なファイルなど。
SECRET_KEY と SECRET_KEY_FALLBACKS の保護¶
startproject によって新しいプロジェクトを作る際、 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:v9G-nxfz3iQGTXrePqYPlGvH79WTcIgj1QIQSUODTW0'
署名が文字列の最後にコロンに続いて付加されます。元の値を取得するには、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:bzb48DBkB-bwLaCnUVB75r5VAPUEpzWJPrTb80JMIXM'
>>> 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:o3DrrsT6JRB73t-HDymfDNbTSxfMlom2d8TiUlb1hWY'
- class Signer(*, key=None, sep=':', salt=None, algorithm=None, fallback_keys=None)[ソース]¶
Returns a signer which uses
keyto generate signatures andsepto separate values.sepcannot be in the URL safe base64 alphabet. This alphabet contains alphanumeric characters, hyphens, and underscores.algorithmmust be an algorithm supported byhashlib, it defaults to'sha256'.fallback_keysis a list of additional values used to validate signed data, defaults toSECRET_KEY_FALLBACKS.
salt 引数を使用する¶
特定の文字列が出現するたびに同じ署名ハッシュを使用したくない場合は、オプションで Signer クラスの引数 salt を使用できます。ソルトを使用すると、署名ハッシュ関数にソルトと SECRET_KEY の両方をシードにします:
>>> signer = Signer()
>>> signer.sign("My string")
'My string:v9G-nxfz3iQGTXrePqYPlGvH79WTcIgj1QIQSUODTW0'
>>> signer.sign_object({"message": "Hello!"})
'eyJtZXNzYWdlIjoiSGVsbG8hIn0:bzb48DBkB-bwLaCnUVB75r5VAPUEpzWJPrTb80JMIXM'
>>> signer = Signer(salt="extra")
>>> signer.sign("My string")
'My string:YMD-FR6rof3heDkFRffdmG4pXbAZSOtb-aQxg3vmmfc'
>>> signer.unsign("My string:YMD-FR6rof3heDkFRffdmG4pXbAZSOtb-aQxg3vmmfc")
'My string'
>>> signer.sign_object({"message": "Hello!"})
'eyJtZXNzYWdlIjoiSGVsbG8hIn0:-UWSLCE-oUAHzhkHviYz3SOZYBjFKllEOyVZNuUtM-I'
>>> signer.unsign_object(
... "eyJtZXNzYWdlIjoiSGVsbG8hIn0:-UWSLCE-oUAHzhkHviYz3SOZYBjFKllEOyVZNuUtM-I"
... )
{'message': 'Hello!'}
Using salt in this way puts the different signatures into different namespaces. A signature that comes from one namespace (a particular salt value) cannot be used to validate the same plaintext string in a different namespace that is using a different salt setting. The result is to prevent an attacker from using a signed string generated in one place in the code as input to another piece of code that is generating (and verifying) signatures using a different salt.
SECRET_KEY とは異なり、salt 引数は秘密にする必要はありません。
タイムスタンプ値の検証¶
TimestampSigner は Signer のサブクラスで、値に署名されたタイムスタンプを追加します。これにより、署名された値が指定された期間内に作成されたことを確認できます:
>>> from datetime import timedelta
>>> from django.core.signing import TimestampSigner
>>> signer = TimestampSigner()
>>> value = signer.sign("hello")
>>> value
'hello:1stLqR:_rvr4oXCgT4HyfwjXaU39QvTnuNuUthFRCzNOy4Hqt0'
>>> 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)[ソース]¶
Checks if
valuewas signed less thanmax_ageseconds ago, otherwise raisesSignatureExpired. Themax_ageparameter can accept an integer or adatetime.timedeltaobject.
- sign_object(obj, serializer=JSONSerializer, compress=False)¶
複雑なデータ構造(例えば、リスト、タプル、または辞書)をエンコードし、オプションで圧縮し、現在のタイムスタンプを追加し署名します。
- unsign_object(signed_obj, serializer=JSONSerializer, max_age=None)¶
Checks if
signed_objwas signed less thanmax_ageseconds ago, otherwise raisesSignatureExpired. Themax_ageparameter can accept an integer or adatetime.timedeltaobject.
複雑なデータ構造の保護¶
リスト、タプル、辞書を保護したい場合は、 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:1stLrZ:_QiOBHafwucBF9FyAr54qEs84ZO1UdsO1XiTJCvvdno'
>>> signer.unsign_object(value)
{'foo': 'bar'}
>>> value = signing.dumps({"foo": "bar"})
>>> value
'eyJmb28iOiJiYXIifQ:1stLsC:JItq2ZVjmAK6ivrWI-v1Gk1QVf2hOF52oaEqhZHca7I'
>>> 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を使って署名されます。