考えてみるとほとんどパスワードを保存するコードを書いたことがない。現状のベストプラクティスを知らなかったのでメモ書き。
現状では bcrypt 使うのがいいみたい。b は Blowfish の b。
Modular Crypt Format というフォーマットとコンパチの出力をする。crypt (3) はもともと DES の短いフォーマットだが、弱すぎたため md5 拡張なりSHA512 拡張などが存在している。(htpasswd でも使われているので見たことある人は多いだろう)
これの Blowflish を使ったものが bcrypt で、prefix に $2$, $2a$, $2x$, $2y$ $2b$ がついている。prefix の違いはバージョンの違いであり、現在は 2b を使うのが良い。ストレッチング回数も出力に含まれるので、あるとき急にストレッチング回数を増やしても特に問題はない。
このフォーマットは出力に salt も含むので、生成された MCF はそのまま保存すれば良い。
Blowfish ってなんだ
Blowfish 自体は一方向ハッシュ化アルゴリズムではなく共通鍵暗号化アルゴリズム。処理速度が早く鍵長がある程度可変でライセンスフリーというのが特徴。
共通鍵暗号なのになんでハッシュとして使えるの?
bcrypt は "OrpheanBeholderScryDoubt" という決まった文字列を 64回 Blowfish で暗号化する。 このとき暗号に使う状態 (暗号化の鍵) を password や salt から初期化する。この初期化にも Blowfish の暗号化が使われる。
暗号化済みテキストに鍵の情報が入ることはない (もしあったとしたら暗号化アルゴリズムではない) ので一方向になる。
また "OrpheanBeholderScryDoubt" に復号化できるキーを探す (クリブ / plain text attack) も Blowfish では難しいため、実質的にブルートフォースアタックしかできない。
備考: salt を別途保存する必要がないのは bcrypt に限らない
そもそも Modular Crypt Format のものは des crypt ですら salt も出力に含んでいる。ググると「bcrypt なら salt は別に保存する必要がない」と書いてる人がいたりする。間違いではないが正しくはない。
Modular Crypt Format のいくつかのパターンを引用する
des_crypt : {salt}{checksum} md5_crypt : $1${salt}${checksum} bcrypt : $2a${rounds}${salt}{checksum} sha256_crypt : $5$rounds={rounds}${salt}${checksum}
( https://passlib.readthedocs.io/en/stable/modular_crypt_format.html から一部をコピペ)
備考: パスワード保存は必要か?
そもそも現代ではプラットフォーマーでなければパスワードを保存しなければならないケースがかなり少ない。OAuth などで外部サービスに認証を委託したほうがはるかに楽だからだ。パスワードを保存するということはそれ自体が難しいうえに、パスワードリセットや2段階認証なども含めてトータルで抜け道がないか考える必要がある。
ref.