はじめに
この記事は Scala Advent Calendar(Adventar) の13日目です。パスワード保存に使うBCryptの話をします。
安全にパスワードを保存する
皆様、パスワードをセキュアに保存しているでしょうか?まさかとは思いますが、パスワードを生で保存するとかおぞましいことをしてないでしょうか?
パスワードは攻撃者によってソースコード・DBすべて取得されたとしても、元のパスワードを類推することを不可能とするように保存することにより、パスワード漏洩最後の砦として機能させることが望ましいです。
この条件を満たす方法として、パスワードをHash化して保存する方法があります。ただし単純なHash化では駄目です。まず、類推されにくくて衝突しずらい、暗号学的に良いHash関数アルゴリズムが必要です。また、よくあるHash関数アルゴリズムは(元々高速に計算することを意図していたのもあり)非常に高速に計算が可能であり、総当たり試行されてしまえば、現代のコンピュータスペックをもってすれば一瞬で解読されてしまいます。また、レインボーテーブルと呼ばれる、高速にHashから元の値を類推することが可能なアルゴリズムも存在し、Hash化では不足です。
従って、現状ではセキュアにするために以下のように保存することがのぞましいです。
- 暗号学的に優れたHash関数を使うこと wikipedia
- Passwordへランダムに生成したsaltを加え、文字数を増やす
- Hash化は1回ではなく複数回(現代なら数千回ぐらい?)行う(ストレッチング処理)
と、パスワードを安全に保存するのは考えることが多くて大変だということが分かりました。saltのデータはパスワード毎に必要なので、DBに保存するなら別途カラムが要りますし、暗号学的に優れたハッシュ関数は時代によって変わるので変わったら更新する必要もあるでしょう。回数も時代のCPUに合わせて徐々に増やしていきたいところです。その実装の過程でセキュリティホール付きバグなんて作った日には目も当てられません。
はい、みんなやらなきゃいけない面倒なことはライブラリに投げましょう。BCryptの登場です。
BCrypt
BCryptはアルゴリズムという説明を良く目にしますが、世間であふれてるBCrypt実装を見ると、寧ろパスワードをセキュアに保存するのを支援するライブラリな気がするので、ここでは後者の意味で使っています。
Java界にはいくらかのBCrypt実装がありますが、比較的よくメンテされていて利用者も多そうなSpringframeworkのセキュリティライブラリに含まれているものをお勧めします。sbtだと
libraryDependencies += "org.springframework.security" % "spring-security-web" % "6.1.2"
で追加できます。Versionは最新を調べて使いましょう。
使い方
ハッシュ生成
Encoderを生成してそのencodeメソッドにPasswordを渡すだけで、saltを生成し、Passwordとsaltを結合した文字列を返してくれます。この文字列を次のパスワード認証で渡すだけで認証できます。saltカラムなんて要らない!
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
val bcrypt = new BCryptPasswordEncoder()
def createHash(password: String): String =
bcrypt.encode(password)
パスワード認証
さきほど生成したsalt付きhash文字列を、ユーザが投げてきた文字列と共に、Encoderのmatchesメソッドに渡すだけです
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
val bcrypt = new BCryptPasswordEncoder()
def authenticate(password: String): Boolean = {
val hashString = fromDB()
bcrypt.matches(password, hashString)
}
簡単ですね。
注意事項とか
生成したhash文字列の長さ
60文字です。DBにはCHAR(60)などで置いておけばいいでしょう。
Hash化の回数の制御
BCryptPasswordEncoderの生成時、引数に強さの値を渡すだけです。4-31の値を渡せます。デフォルトは10。指数関数的に処理時間が増えていくので、テストしてから本番投入しましょう。
文字数制限
主要なBCrypt実装は72文字を越える文字列をサポートしていないです。具体的な処理はライブラリ毎に異なるようですが、SpringSecurityの場合は72文字以降を切り捨てるようです。
(初出時にはBCryptで良く使われるBlowfishアルゴリズムが原因と書いていましたが、間違っていた可能性が高いと判断して、主要なBCrypt実装の問題という書き方に変えています)
Encode結果の詳細
Encode処理をすると次のような形で出力されます。
$2a$10$8sKUrdvJn7gpWmMH2qfRduF.vhe2n3diyzf8CvY8GtTsmNJ6HRnBe
この中に
- アルゴリズム
- アルゴリズムの詳細(回数とか)
- Salt
- Hashed Password
の各データが入っています。つまり途中からアルゴリズムや強さを変更しても特に何もせずに移行できるということですね。
おわりに
このようにBCryptを使えばお手軽にパスワードを比較的安全に保存できます。勿論セキュリティの世界に完璧はないですが、大分マシなものになるでしょう。