web-dev-qa-db-ja.com

demystify Flask app.secret_key

app.secret_keyが設定されていない場合、Flaskはセッション辞書の設定またはアクセスを許可しません。

これは、主題に関する フラスコユーザーガイドが言わなければならない のすべてです。

私はウェブ開発を始めたばかりで、セキュリティの仕組みがどうして/どうして機能するのかわかりません。 Flaskが内部で何をしているかを理解したいと思います。

  • Flaskがこのsecret_keyプロパティの設定を強制するのはなぜですか?
  • Flaskはsecret_keyプロパティをどのように使用しますか?
100
MYV

暗号化を必要とするもの(攻撃者による改ざんを防止するため)には、秘密鍵を設定する必要があります。 justFlask自体の場合、その 'anything'はSessionオブジェクトですが、他の拡張機能では同じ秘密。

secret_keyは、単にSECRET_KEY構成キーに設定された値であるか、直接設定できます。

QuickstartのSessionsセクション には、設定する必要のあるサーバー側の秘密の種類についての適切で適切なアドバイスがあります。

暗号化は秘密に依存しています。暗号化で使用するサーバー側のシークレットを設定しなかった場合、誰でも暗号化を解除できます。コンピューターのパスワードのようなものです。シークレットと署名するデータを使用して、署名文字列を作成します。これは、 暗号化ハッシュアルゴリズム を使用して、再作成が困難な値です。元のデータとまったく同じ秘密がある場合にのみ、この値を再作成して、Flaskが変更されたかどうかを検出できます。許可なく。シークレットがクライアントに送信するデータに含まれるFlaskなので、クライアントはセッションデータを改ざんすることができず、新しい有効な署名を作成することを望みます。

Flaskは itsdangerous library を使用してすべてのハードワークを実行します。セッションは、カスタマイズされたJSONシリアライザーで itsdangerous.URLSafeTimedSerializer class を使用します。

82
Martijn Pieters

以下の回答は、主にSigned Cookiessessions(Webアプリケーションで使用される)。 Flaskは、通常(署名なし)Cookie(request.cookiesおよびresponse.set_cookie()経由)と署名付きCookie(flask.session経由)の両方を提供します。答えには2つの部分があります。最初の部分は署名付きCookieの生成方法を説明し、2番目の部分はスキームのさまざまな側面に対応するQAの形式で提示されます。例に使用される構文はPython3ですが、概念は以前のバージョンにも適用されます。

SECRET_KEYとは何ですか(または署名付きCookieを作成する方法)?

Cookieに署名することは、Cookieの改ざんに対する予防策です。 Cookieに署名するプロセス中、SECRET_KEYは、ハッシュ前にパスワードを調整するために「塩」を使用する方法と同様の方法で使用されます。概念の(乱暴に)簡略化された説明があります。例のコードは説明のためのものです。手順の多くは省略されており、すべての機能が実際に存在するわけではありません。ここでの目標は、一般的な考え方を理解することであり、実際の実装はもう少し複雑になります。また、Flaskがバックグラウンドでこれのほとんどを実行することに注意してください。したがって、(セッションAPIを介して)Cookieに値を設定し、SECRET_KEYを提供する以外に、これを自分で再実装することはお勧めしませんが、そうする必要はありません。

貧乏人のクッキー署名

ブラウザーに応答を送信する前に:

(1)最初にSECRET_KEYが確立されます。アプリケーションのみが知っておくべきであり、アプリケーションの再起動を含め、アプリケーションのライフサイクル中は比較的一定に保つ必要があります。

# choose a salt, a secret string of bytes
>>> SECRET_KEY = 'my super secret key'.encode('utf8')

(2)クッキーを作成する

>>> cookie = make_cookie(
...     name='_profile', 
...     content='uid=382|membership=regular',
...     ...
...     expires='July 1 2030...'
... )

>>> print(cookie)
name: _profile
content: uid=382|membership=regular...
    ...
    ...
expires: July 1 2030, 1:20:40 AM UTC

(3)署名を作成するには、SECRET_KEYをCookieバイト文字列に追加(または追加)し、その組み合わせからハッシュを生成します。

# encode and salt the cookie, then hash the result
>>> cookie_bytes = str(cookie).encode('utf8')
>>> signature = sha1(cookie_bytes+SECRET_KEY).hexdigest()
>>> print(signature)
7ae0e9e033b5fa53aa....

(4)次に、元のCookieのcontentフィールドの一端に署名を付加します。

# include signature as part of the cookie
>>> cookie.content = cookie.content + '|' + signature
>>> print(cookie)
name: _profile
content: uid=382|membership=regular|7ae0e9...  <--- signature
domain: .example.com
path: /
send for: Encrypted connections only
expires: July 1 2030, 1:20:40 AM UTC

それがクライアントに送信されるものです。

# add cookie to response
>>> response.set_cookie(cookie)
# send to browser --> 

ブラウザからCookieを受信すると:

(5)ブラウザがこのCookieをサーバーに返したら、Cookieのcontentフィールドから署名を削除して、元のCookieを取得します。

# Upon receiving the cookie from browser
>>> cookie = request.get_cookie()
# pop the signature out of the cookie
>>> (cookie.content, popped_signature) = cookie.content.rsplit('|', 1)

(6)アプリケーションのSECRET_KEYで元のCookieを使用して、手順3と同じ方法を使用して署名を再計算します。

# recalculate signature using SECRET_KEY and original cookie
>>> cookie_bytes = str(cookie).encode('utf8')
>>> calculated_signature = sha1(cookie_bytes+SECRET_KEY).hexdigest()

(7)計算結果を、受信したばかりのCookieから以前にポップされた署名と比較します。それらが一致する場合、Cookieが台無しにされていないことがわかります。ただし、Cookieにスペースが追加されただけでも、署名は一致しません。

# if both signatures match, your cookie has not been modified
>>> good_cookie = popped_signature==calculated_signature

(8)それらが一致しない場合、任意の数のアクションで応答し、イベントをログに記録し、Cookieを破棄し、新しいCookieを発行し、ログインページにリダイレクトすることができます。

>>> if not good_cookie:
...     security_log(cookie)

ハッシュベースのメッセージ認証コード(HMAC)

一部のコンテンツの整合性を確保するために秘密鍵を必要とする上記で生成された署名のタイプは、暗号化ではMessage Authentication CodeまたはMAC

前の例では、上記の例はその概念を単純化しすぎており、独自の署名を実装することはお勧めできません。これは、FlaskでCookieに署名するために使用されるアルゴリズムが HMAC と呼ばれ、上記の単純なステップバイステップよりも少し複雑だからです。一般的な考え方は同じですが、この説明の範囲外の理由により、一連の計算は少し複雑です。通常はDIYの作成にまだ興味がある場合、Pythonには開始に役立つモジュールがいくつかあります:)ここに開始ブロックがあります。

import hmac
import hashlib

def create_signature(secret_key, msg, digestmod=None):
    if digestmod is None:
        digestmod = hashlib.sha1
    mac = hmac.new(secret_key, msg=msg, digestmod=digestmod)
    return mac.digest()

hmac および hashlib のドキュメント。


SECRET_KEYの「謎解き」:)

この文脈での「署名」とは何ですか?

これは、一部のコンテンツが、許可された人物またはエンティティ以外の誰かによって変更されていないことを保証する方法です。

署名の最も単純な形式の1つは " checksum "です。これは、2つのデータが同じであることを単純に検証します。たとえば、ソースからソフトウェアをインストールする場合、ソースコードのコピーが作成者のものと同一であることを最初に確認することが重要です。これを行う一般的なアプローチは、暗号化ハッシュ関数を使用してソースを実行し、出力をプロジェクトのホームページで公開されているチェックサムと比較することです。

たとえば、Webミラーからgzip圧縮されたファイルでプロジェクトのソースをダウンロードしようとしているとします。プロジェクトのWebページに公開されているSHA1チェックサムは「eb84e8da7ca23e9f83 ....」です。

# so you get the code from the mirror
download https://mirror.example-codedump.com/source_code.tar.gz
# you calculate the hash as instructed
sha1(source_code.tar.gz)
> eb84e8da7c....

両方のハッシュは同じであり、同一のコピーがあることを知っています。

クッキーとは?

Cookieに関する詳細な議論は、この質問の範囲を超えています。 SECRET_KEYが有用である理由と理由をよりよく理解するために、最小限の理解が役立つ場合があるため、ここで概要を説明します。 HTTP Cookieに関するいくつかの個人的な読み物をフォローアップすることを強くお勧めします。

Webアプリケーションの一般的なプラクティスは、クライアント(Webブラウザー)を軽量キャッシュとして使用することです。 Cookieは、このプラクティスの実装の1つです。通常、Cookieは、ヘッダーによってサーバーがHTTP応答に追加するデータです。ブラウザによって保持され、リクエストを発行するときに、HTTPヘッダーを介してサーバーに送信されます。 Cookieに含まれるデータを使用して、いわゆるstatefulnessをエミュレートできます。これは、サーバーがクライアントとの継続的な接続を維持しているように見えます。この場合、接続を「アライブ」に保つためのワイヤの代わりに、クライアントの要求を処理した後のアプリケーションの状態のスナップショットのみを取得します。これらのスナップショットは、クライアントとサーバー間でやり取りされます。サーバーは、リクエストを受信すると、まずCookieのコンテンツを読み取り、クライアントとの会話のコンテキストを再確立します。次に、そのコンテキスト内で要求を処理し、クライアントに応答を返す前に、Cookieを更新します。したがって、進行中のセッションの錯覚は維持されます。

クッキーはどのように見えますか?

典型的なCookieは次のようになります。

name: _profile
content: uid=382|status=genie
domain: .example.com
path: /
send for: Encrypted connections only
expires: July 1 2030, 1:20:40 AM UTC

Cookieは、最新のブラウザから簡単に閲覧できます。たとえば、Firefoxでは[設定]> [プライバシー]> [履歴]に移動し、個々のCookieを削除します

contentフィールドは、アプリケーションに最も関連しています。他のフィールドには、主にメタ命令があり、さまざまな影響範囲を指定します。

Cookieを使用する理由

簡単な答えはパフォーマンスです。 Cookieを使用すると、さまざまなデータストア(メモリキャッシュ、ファイル、データベースなど)で物事を調べる必要性が最小限に抑えられるため、サーバーアプリケーション側で物事が高速化されます。 Cookieが大きいほどネットワーク上のペイロードが大きくなるため、サーバー上のデータベース検索で保存したものはネットワーク上で失われる可能性があることに注意してください。 Cookieに何を含めるかを慎重に検討してください。

Cookieに署名する必要があるのはなぜですか?

Cookieはあらゆる種類の情報を保持するために使用され、その一部は非常に機密性が高い場合があります。また、それらは本質的に安全ではないため、クライアントとサーバーの両方の当事者にとって何らかの方法で安全であると見なされるために、いくつかの補助的な予防措置を取る必要があります。 Cookieに署名することで、サーバーアプリケーションをだまそうとする際に改ざんされる可能性がある問題に特に対処できます。他のタイプの脆弱性を軽減する他の手段があります。Cookieについて詳しく読むことをお勧めします。

Cookieはどのように改ざんできますか?

Cookieはクライアントにテキスト形式で存在し、簡単に編集できます。サーバーアプリケーションが受信したCookieは、いくつかの理由で変更されている可能性がありますが、その中には無害ではないものもあります。ユーザーに関する許可情報をCookieに保持し、その情報に基づいて特権を付与するWebアプリケーションを想像してください。 Cookieが改ざん防止されていない場合、誰でも自分のステータスを変更して「role = visitor」から「role = admin」にステータスを上げることができ、アプリケーションは賢明ではありません。

Cookieに署名するためにSECRET_KEYが必要なのはなぜですか?

クッキーの検証は、ソースコードの検証方法とは少し異なります。ソースコードの場合、元の作成者は参照フィンガープリント(チェックサム)のトラスティおよび所有者であり、公開されます。信頼できないのはソースコードですが、公開署名は信頼します。したがって、ソースのコピーを検証するには、計算されたハッシュをパブリックハッシュと一致させるだけです。

Cookieの場合、アプリケーションは署名を追跡しませんが、SECRET_KEYを追跡します。 SECRET_KEYは参照指紋です。 Cookieは、正当であると主張する署名付きで移動します。ここでの正当性とは、署名がCookieの所有者、つまりアプリケーションによって発行されたことを意味します。この場合、信頼できないと主張し、署名の有効性を確認する必要があります。これを行うには、自分だけが知っている要素を署名に含める必要があります。それはSECRET_KEYです。誰かがクッキーを変更するかもしれませんが、彼らは有効な署名を適切に計算するための秘密の要素を持っていないので、彼らはそれをだますことはできません。少し前に述べたように、チェックサムに加えて秘密鍵も提供するこのタイプのフィンガープリンティングは、メッセージ認証コードと呼ばれます。

セッションはどうですか?

従来の実装のセッションは、contentフィールド、session_idにIDのみを保持するCookieです。セッションの目的は、署名されたCookieとまったく同じです。つまり、Cookieの改ざんを防ぎます。ただし、クラシックセッションのアプローチは異なります。セッションCookieを受信すると、サーバーはIDを使用して、データベース、ファイル、またはメモリ内のキャッシュなどのローカルストレージでセッションデータを検索します。通常、セッションCookieは、ブラウザーが閉じられると期限切れになるように設定されます。ローカルストレージルックアップのステップのため、このセッションの実装では通常、パフォーマンスが低下します。署名されたCookieが推奨される代替手段になりつつあり、それがFlaskのセッションの実装方法です。つまり、Flaskセッション署名付きCookieであり、Flaskで署名付きCookieを使用するには、 Session AP​​I。

なぜクッキーも暗号化しないのですか?

Cookieの内容は、暗号化する前に署名したりこれは、ブラウザからは見えないほど機密性が高いと判断された場合に行われます(暗号化によりコンテンツが隠されます)。ただし、単にCookieに署名するだけで、ブラウザー上のCookieの可視性と使いやすさを維持しながら、それらが干渉されることを防ぎたいというニーズに対応できます。

SECRET_KEYを変更するとどうなりますか?

SECRET_KEYを変更すると、以前のキーで署名されたallCookieを無効にしています。アプリケーションは、以前のSECRET_KEYで署名されたCookieを含むリクエストを受信すると、新しいSECRET_KEYを使用して署名を計算しようとしますが、両方の署名は一致しません。データは拒否され、ブラウザが初めてサーバーに接続しているようになります。ユーザーはログアウトされ、古いCookieは内部に保存されているものとともに忘れられます。これは、期限切れのCookieの処理方法とは異なることに注意してください。署名がチェックアウトされると、期限切れのCookieのリースが延長される場合があります。無効な署名は、単なる無効なCookieを意味します。

したがって、すべての署名済みCookieを無効にしない限り、SECRET_KEYを長期間同じに保つようにしてください。

良いSECRET_KEYとは何ですか?

秘密鍵は推測するのが難しいはずです。 Sessions のドキュメントには、ランダムキー生成の優れたレシピがあります。

>>> import os
>>> os.urandom(24)
'\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O<!\xd5\xa2\xa0\x9fR"\xa1\xa8'

キーをコピーし、SECRET_KEYの値として構成ファイルに貼り付けます。

ランダムに生成されたキーを使用する以外に、おそらくあなただけが知っている文にバイト形式でエンコードされた単語、数字、記号の複雑な組み合わせを使用できます。

Donotは、呼び出されるたびに異なるキーを生成する関数でSECRET_KEYを直接設定します。たとえば、これをしないでください:

# this is not good
SECRET_KEY = random_key_generator()

アプリケーションを再起動するたびに、新しいキーが与えられ、以前のキーが無効になります。

代わりに、インタラクティブなpythonシェルを開き、関数を呼び出してキーを生成し、それをコピーして構成に貼り付けます。

53
Michael Ekoka