Kotlin で BCrypt を使ってパスワードをハッシュ化する
パスワードは暗号化ではなく Salt 付きのハッシュ化で保存すべき理由と、BCrypt を選ぶと Salt 用のカラムが要らなくなる話です。
- 公開日
この記事は別サイトにも掲載しています。 https://iganin.hatenablog.com/entry/2021/09/09/232902
TL;DR;
- パスワードは一般的な暗号化だけでなく Salt も加えた上で Hash 化するのが望ましい
- BCrypt を使用すると暗号化データの中に Salt の情報が含まれるため Salt Column が不要
- jBCrypt/BCrypt.java at master · jeremyh/jBCrypt · GitHub が Java ではおそらく安牌
パスワード暗号化
前提としてパスワードの保存について。 平文保存はまずめちゃくちゃまずいと思う、ネットワークに侵入されて DB 見られたら終わってしまう。 暗号化で十分かというと実はそうでもない。内部に悪意のある管理者がいた場合 DB にアクセスしてパスワードを復号するということもあり得る。 パスワードはシステムの最後の砦として、ユーザー本人以外が知り得ない状態にするのが望ましい。
そのために多くの場合 Hash 化を行うと思う。Hash 化を行うことでパスワードの入力値 → ハッシュ化パスワードの変換はでき、ハッシュ化パスワードと DB に保存されたハッシュ化パスワードを比較し一致しているかどうか判断することができる。 しかし、ハッシュ化パスワードから元のパスワードを導き出すことはできないため、内部の悪意のある管理者によるパスワード取得を防ぐことができ、より堅牢にできる。 ただし、簡単な Hash 化関数だと元に戻すことができてしまうらしいし、Hash 化の回数が少ないと脆弱性が増すということにも注意が必要らしい。
また、上記を行った上でもパスワードに対して辞書攻撃やレインボーテーブル攻撃をされてしまう可能性もある。 Salt をユーザーごとに設定し、パスワードに Salt を付与することでこの攻撃を防ぐことができる。
以上より、パスワード保存に当たっては以下が必要だと思う。
- 暗号化
- 適切なハッシュ化
- Salt の付与
BCrypt
Blowfish 暗号化を実装したライブラリらしい。 多くの記事で推奨されているのと、社内のセキュリティに詳しいエンジニアが Blowfish 暗号化を推奨していたので、パスワードの暗号化では BCrypt を使用すれば恐らく大きな間違いはないと思う。
使い方は至って簡単。必須なメソッドは下記のようになる。
// salt を生成する。引数で Hash 化の回数が決まるらしい。 2 の引数乗実行される。数を増やすと指数的に処理時間が増大するため注意
String gensalt(int log_rounds)
// 平文のパスワードと salt からハッシュ化されたパスワードを生成する
String hashpw(String password, String salt)
// 平文のパスワードとハッシュ化されたパスワードを比較し一致しているかどうかを判定する
// 引数に salt が不要である点に注意
String checkpw(String plaintext, String hashed)
最後に
セキュリティ周りのことを書くのはドキドキします。 間違っている点があったらご指摘ください。