Javaでハッシュ値を使ったパスワード照合の仕組みと実装方法
こんにちは。ゆうせいです。
今回は「ハッシュ値を使ってパスワードの照合をする方法」について、丁寧にわかりやすく説明していきます。
前回はPBKDF2などを使って“安全にハッシュ化する方法”を学びましたね。でも、それだけでは終わりませ。実際のアプリケーションでは、「ログイン時に正しいパスワードかどうかをどうやって判断するのか?」という部分がとても大事です。
パスワード照合とは?
照合とは、ユーザーが入力したパスワードが、保存されたハッシュ値と一致するかをチェックする処理です。
ポイントはこうです:
「ハッシュ値は復号できないけど、同じ入力からは同じハッシュが得られる」
この性質を使って、ユーザーが入力したパスワードをもう一度同じ条件でハッシュ化し、保存された値と一致するか比較するわけです。
照合の流れ(図で理解)
[ユーザー入力パスワード]
         ↓
  [ソルトを付加する]
         ↓
[PBKDF2でハッシュ化]
         ↓
[保存されているハッシュと比較]
         ↓
     一致?→ログイン成功!
     不一致→エラー表示
Javaでの実装例(照合処理)
以下に、ハッシュ値とソルトを使ってパスワード照合を行う実装例を紹介します。
前提:ユーザー登録時に「ソルト」と「ハッシュ値」をDBに保存済み。
パスワード照合のコード例(PBKDF2)
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.util.Base64;
public class PasswordVerifier {
    public static boolean verifyPassword(String inputPassword, String storedHashBase64, String storedSaltBase64, int iterations, int keyLength) throws Exception {
        byte[] salt = Base64.getDecoder().decode(storedSaltBase64);
        byte[] storedHash = Base64.getDecoder().decode(storedHashBase64);
        PBEKeySpec spec = new PBEKeySpec(inputPassword.toCharArray(), salt, iterations, keyLength);
        SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        byte[] inputHash = skf.generateSecret(spec).getEncoded();
        // ハッシュの比較
        if (inputHash.length != storedHash.length) return false;
        // タイミング攻撃対策として一定時間かけて比較
        int diff = 0;
        for (int i = 0; i < inputHash.length; i++) {
            diff |= inputHash[i] ^ storedHash[i];
        }
        return diff == 0;
    }
}
実際の照合コードの使い方
String inputPassword = "P@ssw0rd123";
String storedSalt = "ユーザー登録時に保存したソルト(Base64)";
String storedHash = "ユーザー登録時に保存したハッシュ(Base64)";
int iterations = 65536;
int keyLength = 256;
boolean match = PasswordVerifier.verifyPassword(inputPassword, storedHash, storedSalt, iterations, keyLength);
System.out.println(match ? "ログイン成功!" : "パスワードが違います");
注意:なぜBase64が必要なの?
ハッシュやソルトはバイナリデータなので、そのまま文字列として保存すると文字化けします。そこで登場するのがBase64。
Base64は、バイナリデータを文字列として安全に保存・送信するためのエンコード方法です。
比較処理にも落とし穴がある!
storedHash.equals(inputHash);
このような単純な比較は、タイミング攻撃(Timing Attack)のリスクがあります。
安全な実装では、すべてのバイトを最後まで見て、差分を合成するような書き方にしましょう(先ほどの diff |= 方式)。
DBに保存する値の構成例
| 項目 | 内容例 | 
|---|---|
| ソルト | kJ3v93fA7l9a+3DQx7+QvA== | 
| ハッシュ | 2r4S9P5F6dJQq7F7N1eIDoZr… | 
| 回数 | 65536 | 
| 長さ | 256 | 
| アルゴリズム | PBKDF2WithHmacSHA256 | 
このように、ハッシュ値だけでなくパラメータも保存しておくと、将来の照合時に困りません。
まとめ
- パスワード照合では、再ハッシュして一致確認する
- 同じソルト・同じ回数・同じアルゴリズムが必須条件
- 比較は安全な方法(diff |=)で行うのが理想
- Base64を使ってソルト・ハッシュを文字列で保存しよう
次のステップは?
- パスワード登録時の処理と照合処理を統一的に設計する
- Spring Securityなどのフレームワークでの統合も学ぶ
- bcryptなど他のKDFとの比較と導入検討も進めよう
セイ・コンサルティング・グループの新人エンジニア研修のメニューへのリンク
投稿者プロフィール
- 代表取締役
- 
セイ・コンサルティング・グループ株式会社代表取締役。
 岐阜県出身。
 2000年創業、2004年会社設立。
 IT企業向け人材育成研修歴業界歴20年以上。
 すべての無駄を省いた費用対効果の高い「筋肉質」な研修を提供します!
 この記事に間違い等ありましたらぜひお知らせください。