パスワードの安全な保存とは
パスワードの安全な保存とは、パスワードそのものではなく、元に戻せない形の値だけを保存することです。セッション管理は、ログイン後の利用者を推測できない札で見分け、期限が来たら無効にする仕組みです。
大学の学生ポータルのデータベースが漏れたとします。パスワードがそのまま入っていれば、全員が他のサイトでも危なくなります。
このレッスンのゴール
メモ
パスワードをソルト付きのハッシュで保存し、セッションの札と期限を安全に扱うコードを書ける。
今日のキーワード
| ことば | いみ |
|---|---|
| ハッシュ値 | 文字から作る、元に戻せない決まった長さの値です。 |
| ソルト | 利用者ごとに違う、ランダムな足し文字です。 |
| セッション | ログインしてから、ログアウトまでのひとまとまりです。 |
| トークン | ログイン済みの利用者を見分ける、推測できない札です。 |
| 有効期限 | その札を使ってよい、最後の時刻です。 |
| Cookie | ブラウザが覚えて、毎回サーバーに送る小さな文字です。 |
学生が入力するセルは、このプレビューでは空白です。
たった1つのルール
注意
試してよいのは、自分専用のラボのコピーだけです。このラボは、わざと弱く作った店で、インターネットにはつながっていません。許可のない他人のシステムに同じことをするのは、不正アクセス禁止法に触れる犯罪です。
パスワードはハッシュにして保存する
ハッシュ関数は、文字から決まった長さの値を作ります。同じ文字からは、いつも同じ値ができます。値から元の文字には戻せません。
ログインのときは、入力を同じ関数にかけ、保存した値と比べます。パスワードそのものは、どこにも残りません。
abc のハッシュ値
printf 'abc' | sha256sumここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
password のハッシュ値
printf 'password' | sha256sumここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
2つの答えを読む
どちらも、64文字の16進数を表示します。3文字の abc も、8文字の password も、同じ長さです。何度実行しても、同じ文字からは同じ値になります。
password の値は、世の中の誰もが知っています。攻撃する人は、よく使われる文字の値を、一覧にして持っているからです。
ソルトで同じ値をなくす
ハッシュだけでは、同じパスワードの人は同じ値になります。そこで ソルト(salt)を使います。利用者ごとに違うランダムな文字を足してから、ハッシュにします。
ソルトは隠さず、ハッシュ値の横に保存します。目的は隠すことではなく、一覧を使えなくすることです。
ソルトで値が変わる
import hashlib
def hash_password(password, salt):
# 遅いハッシュを使います。回数を増やすほど、試す側も遅くなります。
return hashlib.pbkdf2_hmac("sha256", password.encode(), salt.encode(), 100000).hex()
# salt を変えて、実行します。
a = hash_password("sunflower", "salt-A")
b = hash_password("sunflower", "salt-B")
print(a == b)
print(a == hash_password("sunflower", "salt-A"))
print(len(a))
保存の仕方を比べる
| 保存の仕方 | 漏れたときに起きること |
|---|---|
| そのまま保存 | すぐに読まれる。他のサイトの口座も危ない |
| 速いハッシュを1回だけ | 1秒に何億回も試され、弱い語はすぐ破られる |
| ソルトなしのハッシュ | 同じパスワードの人が、一度にわかる |
| ソルト付きの遅いハッシュ | 1人ずつ、時間をかけないと試せない |
学生が入力するセルは、このプレビューでは空白です。
よくある間違い
注意
「SHA-256 で1回ハッシュにすれば十分だ」と考える人がいます。それは違います。速い関数は、攻撃する側にも速いのです。パスワードには、ソルトを付けた、わざと遅いハッシュを使います。
セッションの札は推測できない乱数にする
ログインすると、サーバーは セッション(session)を作り、トークン(token)という札を渡します。ブラウザは Cookie で札を送り続けます。
札を推測できれば、他人になりすませます。だから、安全な乱数で、十分に長く作ります。Python では secrets を使い、random は使いません。
安全な乱数で札を作る
import secrets
# 16バイトを16進数にすると、32文字になります。
token = secrets.token_hex(16)
print(len(token))
print(token == secrets.token_hex(16))
ラボの Python で札の長さを見る
docker exec app python -c "import secrets; print(len(secrets.token_hex(16)))"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
セッションには期限と終わりが要る
セッションの期限は2種類あります。何もしないまま一定時間が過ぎると切れる アイドル期限 と、ログインから最大の長さを決める 絶対期限 です。
ログアウトは、ブラウザの Cookie を消すだけでは足りません。サーバーの側でも、その札を無効にします。
札を守る Cookie の3つの設定
| 設定 | 守ること |
|---|---|
| HttpOnly | 画面の JavaScript から、札を読めなくする |
| Secure | 暗号化した通信でだけ、札を送る |
| SameSite | 他のサイトから起きた送信に、札を付けにくくする |
学生が入力するセルは、このプレビューでは空白です。