パスワードのハッシュ化
Webサービスなどでユーザのアカウントを管理するとき,パスワードとユーザ名からハッシュ値を計算して認証する
モチベーション
1. パスワードを直接サーバに保存すると、サーバからデータが流出したとき攻撃者がユーザになりすませてしまう
2. サーバが本当のパスワード(秘匿情報)を知らないまま、この人物が正当なユーザであることだけ検証したい 
ゼロ知識性
3. ハッシュ関数が持つ不可逆性を利用してこれを達成する
不可逆性はある関数がA -> Bの写像であるときBの値からAの値を計算するのが困難な性質を指す
Step 1. ハッシュ化されたパスワードの保存
ユーザ名をA,パスワードをP,soltをS,ハッシュ関数Hとする
ハッシュ関数HにはPassword-Hashing Functionと呼ばれるものを使用する
Key Derivation Functionに分類されるものが使えることもある(PBKDF2など)
これは使えることもあるというだけであり、Password-Hashing Function = Key Derivation Function ではないことに注意
soltはハッシュ値をより安全なものとするためのランダムな値で,パスワードに連結してハッシュ関数に与える
これもハッシュ値が流出した場合を考えると分かりやすい
solt無しの場合: 攻撃者はよく使われがちなパスワードをハッシュ化して流出したDBを検索すれば簡単にパスワードとそのユーザ名を取得できる
solt有りの場合: soltとハッシュ値が流出しても,攻撃者はよく使われるパスワードとDB内の全てのsoltの組み合わせを試さなければならず,解析のための計算量が増え安全性が高まる
Rainbow table 攻撃への耐性が付く
ここでは省くが、soltにpepperという値をさらに加えるというアイデアも存在する
pepperもsoltのようなランダムな値だが、システムレベルで一意な値で、かつHSMや別のストレージなど、とにかくパスワードを保管するシステムとは異なる経路に保管しておくことで安全性を高めるというもの
昔Dropboxが使っていたことで知られる手法
アカウント登録時にユーザからA, Pを受け取り(A, H(A+P+S), S)というデータの組を保存する
table:データベース
ユーザ名 ハッシュ値 solt
A H(A+P+S) S
ハッシュ関数Hによっては一回の適用ではだめで,適用したハッシュ値にさらに同じ関数を適用して...と繰り返し処理する必要がある
これはストレッチ,イテレーションなどと呼ばれる
攻撃者も最適化されており,H(A+P+S)を再現するブルートフォースがかなり高速化されているため,ハッシュ関数を繰り返して計算量を上げることで安全性を高める
繰り返し回数Nはユーザに紐づけて保存しておく
Step 2. ハッシュ化されたパスワードを用いた認証
入力されたユーザ名A,入力されたパスワードP,solt S,Step 1.と同じハッシュ関数Hを用いて認証を行う
ユーザ名を用いてデータベースからユーザ名に対応するハッシュ値とsoltを取り出す
そのハッシュ値とH(A+P+S)を比較して同じ値だったなら認証成功,異なる場合は認証失敗とする
イテレーションが必要ならここで処理する
このとき,エラーメッセージに「ユーザ名は存在しません」だとか「パスワードが間違っています」だとかは返さないようにする
フットプリンティングに使われる可能性がある
#Security
感想
soltってなんですか?
kimitoboku.iconサービスが適当に付ける固有の文字列だとか数値のことで、データベースが流出した時に、既知の流出したユーザ名とパスワードを用いた探索をやりにくくするための物です。solt以外にもハッシュ関数を複数回使うなどいろいろと、工夫があったりします。rtok.icon
ソルト付きハッシュについて
ソルト付きハッシュのソルトはどこに保存するのが一般的か
pictBLandとpictSQUAREがパスワードをMD5のHashで保存していた事で話題になっていたが、実際、ソルトをどう実装すれば良いのかをちゃんと考えられてる人は少なそうに感じた。
基本的には、徳丸さんの記事でも書かれているように、ハッシュ値と同じデータベースにsoltを保存するのがよくあるパターンになる。
ただ、これはソルトも一緒に漏れるじゃん?と思う人もいると思うが、結局、脅威モデルを考えて取捨選択する必要がある。
そもそも、ソルトの目的としては、元のデータを復元を遅らせる事にある。
元のデータの復元を遅らせるという目的であれば、同じデータベースに保存されていても問題ないのである。
もし、もっと、懸念する場合はマスターソルトのような値を環境変数で渡してそれを付加しても良いと思う。
ただ、SQL Injectionで取られて困るデータが本当にパスワードだけなのかはもっと考えた方が良い。
MD5で保存していた場合にどうやってMigrationすればいいのか
ユーザのパスワードをリセットする場合
これがもっとも、綺麗で正当な方法だろう。
ユーザのパスワードをリセットして、ユーザがログインしようとしたタイミングやメールでパスワードの再設定を依頼する。
これが良いのだが、iOSアプリなど、そもそも、ユーザがログインをする事を面倒がるプラットフォームに提供している場合には課題になる。
このパターンを選択する場合は、何度もやる事は難しいので、丁寧にやろう
カラムに新しいハッシュ値とソルトを追加して、既存のHash値とSoltで新しいHash値を計算する場合
これは、すこし汚いがユーザへの影響はないパターン
既存のMD5のハッシュ値はそのまま残して、新たに安全なハッシュとsoltを確保するカラムを追加する。
既存のハッシュ値とユーザ固有のsoltで新しいハッシュ値をデータに入れる
認証時はユーザのパスワードに対して2回、Hashを計算して認証する
データベースが汚くなるが、まぁ、データベースは汚くなる物だとして割切る事も出来る
認証を正しく関数に分けられているなら、その中だけで少し汚いなぁと思うだけですむ
ちゃんと、未来のメンテナに向けて、コメントを残してあげよう