暗号署名
Webアプリケーションのセキュリティの黄金律は、信頼できないソースからのデータを決して信頼しないことです。 信頼できないメディアを介してデータを渡すと便利な場合があります。 暗号で署名された値は、改ざんが検出されることを知っていれば、信頼できないチャネルを安全に通過させることができます。
Djangoは、値に署名するための低レベルAPIと、署名されたCookieを設定および読み取るための高レベルAPIの両方を提供します。これは、Webアプリケーションでの署名の最も一般的な使用法の1つです。
署名は次の場合にも役立ちます。
- パスワードを紛失したユーザーに送信するための「アカウントを回復する」URLを生成します。
- 非表示のフォームフィールドに保存されているデータが改ざんされていないことを確認します。
- 保護されたリソース(ユーザーが支払ったダウンロード可能なファイルなど)への一時的なアクセスを許可するための1回限りのシークレットURLを生成します。
SECRET_KEYの保護
:djadmin: `startproject` を使用して新しいDjangoプロジェクトを作成すると、settings.py
ファイルが自動的に生成され、ランダムな:setting:` SECRET_KEY` 値を取得します。 この値は、署名されたデータを保護するための鍵です。これを安全に保つことが重要です。そうしないと、攻撃者がそれを使用して独自の署名された値を生成する可能性があります。
低レベル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
クラスは:setting: `SECRET_KEY` 設定を使用して署名を生成します。 Signer
コンストラクターに渡すことで、別のシークレットを使用できます。
>>> signer = Signer('my-other-secret')
>>> value = signer.sign('My string')
>>> value
'My string:EkfQJafvGyiofrdGnuthdxImIJw'
- class Signer(key=None, sep=':', salt=None, algorithm=None)
key
を使用して署名を生成し、sep
を使用して値を区切る署名者を返します。sep
を URLセーフbase64アルファベットに含めることはできません。 このアルファベットには、英数字、ハイフン、およびアンダースコアが含まれています。algorithm
は、hashlib
でサポートされているアルゴリズムである必要があり、デフォルトは'sha256'
です。バージョン3.1で変更:
algorithm
パラメーターが追加されました。
バージョン3.2で変更: sign_object()
およびunsign_object()
メソッドが追加されました。
salt引数を使用する
特定の文字列のすべての出現に同じ署名ハッシュを持たせたくない場合は、オプションのsalt
引数をSigner
クラスに使用できます。 ソルトを使用すると、ソルトと:setting: `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を使用すると、さまざまなシグニチャがさまざまな名前空間に配置されます。 ある名前空間(特定のソルト値)からの署名を使用して、異なるソルト設定を使用している別の名前空間で同じプレーンテキスト文字列を検証することはできません。 その結果、攻撃者は、コード内のある場所で生成された署名付き文字列を、別のソルトを使用して署名を生成(および検証)している別のコードへの入力として使用することを防ぎます。
:setting: `SECRET_KEY` とは異なり、salt引数は秘密にしておく必要はありません。
バージョン3.2で変更: sign_object()
およびunsign_object()
メソッドが追加されました。
タイムスタンプ付きの値の確認
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')
- sign(value)
value
に署名し、現在のタイムスタンプを追加します。
- unsign(value, max_age=None)
value
がmax_age
秒より前に署名されているかどうかを確認します。署名されていない場合は、SignatureExpired
を発生させます。max_age
パラメーターは、整数またはdatetime.timedelta
オブジェクトを受け入れることができます。
- sign_object(obj, serializer=JSONSerializer, compress=False)
バージョン3.2の新機能。
エンコード、オプションで圧縮、現在のタイムスタンプの追加、複雑なデータ構造への署名(例: リスト、タプル、または辞書)。
- unsign_object(signed_obj, serializer=JSONSerializer, max_age=None)
バージョン3.2の新機能。
signed_obj
がmax_age
秒より前に署名されているかどうかを確認します。署名されていない場合は、SignatureExpired
を発生させます。max_age
パラメーターは、整数またはdatetime.timedelta
オブジェクトを受け入れることができます。
バージョン3.1で変更:
algorithm
パラメーターが追加されました。
複雑なデータ構造の保護
リスト、タプル、または辞書を保護したい場合は、Signer.sign_object()
およびunsign_object()
メソッドを使用するか、モジュールのdumps()
またはloads()
関数に署名します( TimestampSigner(salt='django.core.signing').sign_object()/unsign_object()
のショートカットです)。 これらは内部でJSONシリアル化を使用します。 JSONは、:setting: `SECRET_KEY` が盗まれた場合でも、攻撃者がピクルス形式を悪用して任意のコマンドを実行できないようにします。
>>> 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)
dumps()
の逆で、署名が失敗した場合はBadSignature
を発生させます。 与えられた場合、max_age
(秒単位)をチェックします。
バージョン3.2で変更: sign_object()
およびunsign_object()
メソッドが追加されました。