暗号による署名

revision-up-to:17812 (1.4)
Django 1.4 で新たに登場しました: リリースノートを参照してください

Web アプリケーションセキュリティの黄金律は、信頼できないソースからのデータを決 して信頼しないということです。しかし時には信頼できない媒体にデータを通すことが 便利かもしれません。暗号により署名された値を使えば、どんな改ざんも検出されると いう知識のもとで、信頼できない経路を安全に通過できます。

Django は値を署名するための低レベル API と、署名つきクッキーを設定したり読み込 むための高レベル API を提供しています。署名つきクッキーは Web アプリケーション で最もよく使われる署名の一つです。

また、次のような場合に署名が使えることにお気づきでしょうか:

  • パスワードを紛失したユーザに送るために「アカウント復旧」用 URL を生成する。
  • フォームの hidden フィールドに格納されたデータが改ざんされていないことを確認 する。
  • 保護されたリソース (例えば料金を支払ってダウンロードできるファイル) に一時的 にアクセスするためのワンタイム URL を生成する。

SECRET_KEY の保護

startproject を使って新しく Django プロジェクトを作成すると、 settings.py ファイルが自動的に生成され SECRET_KEY にはランダム 値がセットされます。この値はデータを安全に署名するためのキーなので、このキーを 秘密に保つことが不可欠です。そうしなければ攻撃者がキーを使って自分の署名された 値を生成できてしまうでしょう。

低レベル API を使う

class Signer

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
u'My string'

署名または値が何らかの方法で変更されると、 django.core.signing.BadSignature 例外が送出されます:

>>> 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('My string')
>>> value
'My string:EkfQJafvGyiofrdGnuthdxImIJw'

ソルト (salt) 引数を使う

同じ文字列に対して毎回同じ署名ハッシュを取得したくないなら、オプションの salt 引数を Signer クラスに使うことができます。ソルトを使うと 署名ハッ シュ関数はソルトと SECRET_KEY の両方を使ってハッシュを生成します:

>>> signer = Signer()
>>> signer.sign('My string')
'My string:GdMGD6HNQ_qdgxYP8yBZAdAIV1w'
>>> signer = Signer(salt='extra')
>>> signer.sign('My string')
'My string:Ee7vGi-ING6n02gkcJ-QLHg6vFw'
>>> signer.unsign('My string:Ee7vGi-ING6n02gkcJ-QLHg6vFw')
u'My string'

このようにソルトを使うと、署名が別々の名前空間に置かれます。同じプレーンテキス トの検証でも、特定のソルト値を使ったある名前空間から得られた署名を、別のソルト を設定した名前空間で使うことはできません。この結果により、コードのある場所で生 成された署名つき文字列を、違うソルトを使って生成 (検証) している別の場所への入 力として攻撃者が使うことを防げます。

SECRET_KEY と違って、ソルト引数は秘密にする必要がありません。

タイムスタンプつきの値を検証する

class TimestampSigner

TimestampSignerSigner のサブクラスで、署名つきタイムスタンプ を値に付け加えます。これによって署名された値が特定の期間に作られたことを確認で きます:

>>> from django.core.signing import TimestampSigner
>>> signer = TimestampSigner()
>>> value = signer.sign('hello')
>>> value
'hello:1NMg5H:oPVuCqlJWmChm1rA2lyTUtelC-c'
>>> signer.unsign(value)
u'hello'
>>> signer.unsign(value, max_age=10)
...
SignatureExpired: Signature age 15.5289158821 > 10 seconds
>>> signer.unsign(value, max_age=20)
u'hello'

複雑なデータ構造を保護する

リストやタプルや辞書を保護したいのであれば、 signing モジュールの dumpsloads 関数を使ってください。これらの関数は Python の pickle モジュールを 真似た動きをします。ただし舞台裏では JSON シリアライズを使います。 JSON は SECRET_KEY が盗まれた時でさえ、攻撃者が pickle フォーマットを利用し て任意のコマンドを実行できないことを保証します:

>>> from django.core import signing
>>> value = signing.dumps({"foo": "bar"})
>>> value
'eyJmb28iOiJiYXIifQ:1NMg1b:zGcDE4-TCkaeGzLeW9UQwZesciI'
>>> signing.loads(value)
{'foo': 'bar'}
dumps(obj, key=None, salt='django.core.signing', compress=False)

sha1 で署名され base64 で圧縮された URL を安全に使える JSON 文字列を返しま す。

loads(string, key=None, salt='django.core.signing', max_age=None)

dumps() の逆です。署名が間違っていると BadSignature を送出します。