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を使ってソルト・ハッシュを文字列で保存しよう

次のステップは?

  1. パスワード登録時の処理と照合処理を統一的に設計する
  2. Spring Securityなどのフレームワークでの統合も学ぶ
  3. bcryptなど他のKDFとの比較と導入検討も進めよう

セイ・コンサルティング・グループの新人エンジニア研修のメニューへのリンク

投稿者プロフィール

山崎講師
山崎講師代表取締役
セイ・コンサルティング・グループ株式会社代表取締役。
岐阜県出身。
2000年創業、2004年会社設立。
IT企業向け人材育成研修歴業界歴20年以上。
すべての無駄を省いた費用対効果の高い「筋肉質」な研修を提供します!
この記事に間違い等ありましたらぜひお知らせください。