パスワードは、ソルト付きの遅いハッシュで保存する
パスワードの安全な保存とは、パスワードそのものではなく、ソルトを加えて遅く計算したハッシュ値だけをデータベースに残すことです。データベースが漏れても、元のパスワードを取り出しにくくするためです。
このレッスンでは、なぜ普通のハッシュでは足りないのかを実際に確かめ、鍵や秘密情報の置き場所も学びます。
このレッスンのゴール
メモ
パスワードと鍵を、漏れても被害が小さくなるように保存することができる。
キーワード
| ことば | いみ |
|---|---|
| ソルト | ユーザーごとに変える、ランダムな追加の文字列 |
| 辞書攻撃 | よくあるパスワードを順に試して、当たりを探す攻撃 |
| 遅いハッシュ | 計算にわざと時間をかけるハッシュ関数 |
| 反復回数 | 同じ計算を繰り返す回数。多いほど遅くなる |
| 秘密情報 | パスワード、鍵、トークンなど、漏れてはいけない値 |
| 最小限の権限 | 必要な人だけに、必要なだけ許すこと |
学生が入力するセルは、このプレビューでは空白です。
普通のハッシュだけでは、足りない
パスワードをそのまま保存してはいけません。漏れたら全員のパスワードが読まれます。そこで、ハッシュ値を保存します。
ところが、同じパスワードからは同じハッシュ値ができます。攻撃者は、よくあるパスワードのハッシュ値を先に大量に作り、漏れた一覧と見比べるだけで当たりを探せます。これが辞書攻撃です。
パスワードをそのままハッシュにする
printf 'pass1234' | sha256sumここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
例: 同じパスワードは同じ値になる
| ユーザー | パスワード | 保存された値の先頭16文字 |
|---|---|---|
| ユーザーA | pass1234 | bd94dcda26fccb4e |
| ユーザーB | pass1234 | bd94dcda26fccb4e |
| ユーザーC | sakura99 | (別の値) |
学生が入力するセルは、このプレビューでは空白です。
ソルトを足すと、同じパスワードも別の値になる
ソルトは、ユーザーごとに変えるランダムな文字列です。パスワードの前に付けてからハッシュにします。ソルト自体は秘密ではなく、ハッシュ値と一緒に保存します。
これで、同じパスワードでも値が変わります。攻撃者は、ユーザーごとに最初から計算し直すことになります。
ソルト abcd を付ける
printf 'abcdpass1234' | sha256sumここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
ソルト wxyz を付ける
printf 'wxyzpass1234' | sha256sumここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
ソルトの長さを確かめる
python3 -c "import secrets; print(len(secrets.token_hex(16)))"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
ソルトは安全な乱数から作る
先ほどの Run は 32 と表示します。16バイトの乱数を16進数にすると、32文字になるからです。中の値は毎回違うので、ここには書きません。
ソルトは secrets のような、安全な乱数の部品で作ります。時刻や連番のような、予想できる値は使いません。
よくある間違い
注意
「ソルトを付ければ、ハッシュは遅くなり、安全になる」と考える人が多くいます。ソルトは辞書攻撃の使い回しを防ぐだけで、1回の計算は速いままです。遅くするには、反復回数の多い専用のハッシュ関数が必要です。
遅いハッシュで、総当たりを割に合わなくする
普通のハッシュは速く、攻撃者は1秒に何億回も試せます。パスワード用には、わざと遅い関数を選びます。同じ計算を何万回も繰り返すものです。
本物の利用者の1回のログインなら、少し遅くても困りません。攻撃者は何億回も試すので、大きな負担になります。反復回数は、時代とともに増やします。
反復回数10万のハッシュを作る
python3 -c "import hashlib; print(hashlib.pbkdf2_hmac('sha256', b'pass1234', b'salt', 100000).hex())"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
反復回数を変えて比べる
import hashlib
# 反復回数が多いほど、1回の計算に時間がかかります。
salt = b"abcd"
password = b"pass1234"
for rounds in [1000, 100000]:
digest = hashlib.pbkdf2_hmac("sha256", password, salt, rounds)
print(rounds, digest.hex()[:16])
# password を自分の好きな文字列に変えて、もう一度実行しましょう。
鍵や秘密情報は、コードの外に置く
鍵、トークン、パスワードをプログラムの中に書くと、コードを共有した全員に見えます。履歴にも残ります。
秘密情報は、コードの外の保管場所に置き、必要なプログラムにだけ渡します。ファイルなら、持ち主だけが読めるようにします。これが最小限の権限です。
練習用のフォルダを作る
mkdir -p cryptolabここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
鍵のかわりのファイルを作る
printf 'topsecret' > cryptolab/key.txtここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
持ち主だけが読み書きできるようにする
chmod 600 cryptolab/key.txtここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
モードを確かめる
stat -c '%a' cryptolab/key.txtここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。