パスワードのハッシュとソルトの仕組みを解説!Spring Security初心者向けガイド
新人
「ログイン機能を作ってるんですが、パスワードってそのまま保存していいんですか?」
先輩
「絶対にそのまま保存しちゃダメだよ。セキュリティ的に大問題になる。」
新人
「じゃあ、どうやって保存するんですか?」
先輩
「ハッシュ化っていう技術を使って暗号化するんだ。詳しく見てみようか。」
1. パスワードをそのまま保存してはいけない理由
アプリケーションにおけるユーザー認証では、ユーザーのパスワードを安全に扱うことが非常に重要です。万が一データベースが流出してしまったとき、パスワードがそのままの文字列で保存されていると、すべてのユーザーアカウントが不正に使用されてしまうリスクがあります。
そのため、パスワードはハッシュ化して保存するのが常識です。ハッシュ化とは、元の文字列を一方向で変換し、復元できないようにする暗号化技術の一種です。
Spring Securityでは、BCryptPasswordEncoderやArgon2PasswordEncoderなどが提供されており、これらを使うことで安全にパスワードのハッシュ化が可能になります。
2. ハッシュとは何か?(SHA-256などとの違いも簡単に)
ハッシュ(Hash)とは、文字列を一定の長さのランダムな文字列に変換する処理です。この処理は一方向であり、変換後の文字列から元の文字列を復元することはできません。これにより、ユーザーのパスワードを安全に管理することができます。
たとえば、以下のように「password123」という文字列をSHA-256でハッシュ化すると、次のような固定長の値になります。
ef92b778ba4c7d10f13deba5b9d088e62a1b85e61ad1e8e8f37ed327c5487f57
ただし、SHA-256やMD5などの一般的なハッシュ関数は、高速すぎるため辞書攻撃やレインボーテーブル攻撃に弱いという欠点があります。これを補うために、Spring SecurityではBCryptやArgon2のような遅延処理が入った安全なアルゴリズムが推奨されています。
JavaでSHA-256によるハッシュ化を行うコードは以下のようになりますが、Spring Securityでは推奨されていません。
import java.security.MessageDigest;
public class SHA256Example {
public static void main(String[] args) throws Exception {
String password = "password123";
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(password.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte b : hash) {
sb.append(String.format("%02x", b));
}
System.out.println(sb.toString());
}
}
このようにSHA-256を使ってもハッシュ化はできますが、Spring Securityを使用する場合はBCryptPasswordEncoderやArgon2PasswordEncoderなど、よりセキュアなエンコーダを使うべきです。
以下はBCryptPasswordEncoderを使用してハッシュ化する例です。
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class BCryptExample {
public static void main(String[] args) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String rawPassword = "password123";
String hashedPassword = encoder.encode(rawPassword);
System.out.println("ハッシュ化されたパスワード: " + hashedPassword);
}
}
このコードでは、入力したパスワードが自動的にソルト付きでハッシュ化されるため、非常に安全です。Spring Securityと組み合わせることで、認証機能を簡単にかつセキュアに実装することができます。
なお、ハッシュ化の処理はPleiadesで作成したSpring BootプロジェクトにSpring Securityの依存関係を追加するだけで利用できます。PleiadesのチェックボックスからSpring Securityを有効にするのを忘れずに。
3. ソルトとは何か?なぜ重要なのか?
ソルト(Salt)とは、パスワードをハッシュ化する前に追加されるランダムな文字列のことです。ソルトを加えることで、同じパスワードでも異なるハッシュ値が生成されるようになり、セキュリティが大幅に向上します。
たとえば、複数のユーザーが「password123」という同じパスワードを使っていたとしても、ソルトが異なれば、最終的に保存されるハッシュ値はそれぞれ異なります。これにより、攻撃者がハッシュ値を比較して同じパスワードを使っているユーザーを特定することができなくなります。
具体的には以下のようなイメージです。
- ユーザーAのパスワード:
password123+ ソルト:abc123→ ハッシュ:xyz001 - ユーザーBのパスワード:
password123+ ソルト:xyz789→ ハッシュ:lmn456
このように、同じパスワードでも異なる結果になるため、パスワードの推測が非常に困難になります。
Spring Securityでは、BCryptやArgon2を使うことで、内部的にソルトを自動で生成・付加してくれるため、開発者がソルトを自前で管理する必要はありません。
4. ハッシュ+ソルトの組み合わせによるセキュリティ強化
ハッシュだけでもある程度の安全性はありますが、ソルト付きハッシュを使うことで、さらに強固なパスワード保護が実現できます。
攻撃者がハッシュ値を使ってパスワードを推測するために利用する代表的な手法が「辞書攻撃」や「総当たり攻撃(ブルートフォース攻撃)」です。ソルトを使うことで、これらの攻撃が無力化されます。
Spring SecurityのBCryptPasswordEncoderは、毎回異なるソルトを内部で生成し、それをパスワードに付加してハッシュ化してくれるため、安全性が非常に高いです。
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class SaltedHashExample {
public static void main(String[] args) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String rawPassword = "password123";
String hash1 = encoder.encode(rawPassword);
String hash2 = encoder.encode(rawPassword);
System.out.println("1回目のハッシュ: " + hash1);
System.out.println("2回目のハッシュ: " + hash2);
}
}
このコードを実行すると、同じパスワードでも異なるハッシュ値が生成されていることがわかります。これは、ソルトが毎回異なるためです。
このように、ハッシュとソルトの組み合わせによって、セキュリティが飛躍的に高まります。Spring Securityを使えば、この仕組みが簡単に実装できます。
5. レインボーテーブル攻撃とは?それにどう対抗するのか?
レインボーテーブル攻撃とは、あらかじめ大量のパスワードとそのハッシュ値を対応させた「変換表(レインボーテーブル)」を用意しておき、データベースに保存されたハッシュ値と照合することで、元のパスワードを推測する攻撃手法です。
たとえば、「password123」のSHA-256のハッシュ値が既知であれば、対応するハッシュ値を検索するだけで簡単に元のパスワードを特定できてしまいます。
この攻撃に対抗するためには、以下のような対策が有効です。
- ハッシュだけでなくソルトを必ず付加する
- 高速なハッシュ関数ではなく、計算に時間がかかるアルゴリズム(BCrypt・Argon2)を使う
レインボーテーブルは、あくまで「固定の入力に対して固定のハッシュ値」が生成されるという特性を利用した攻撃です。しかし、ソルトを加えることで同じパスワードでも毎回異なるハッシュが生成されるため、レインボーテーブルが無意味になります。
さらに、Spring SecurityのBCryptPasswordEncoderやArgon2PasswordEncoderを使うことで、自動的にソルトが付加され、レインボーテーブル攻撃に強い構造になります。
以下のコードは、Spring Securityで用意されているArgon2の例です。
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
public class Argon2Example {
public static void main(String[] args) {
Argon2PasswordEncoder encoder = new Argon2PasswordEncoder();
String rawPassword = "password123";
String hashedPassword = encoder.encode(rawPassword);
System.out.println("Argon2でハッシュ化: " + hashedPassword);
}
}
Argon2はパスワードハッシュコンペ(PHC)で優勝したアルゴリズムで、CPU・メモリ・時間のリソース制御が可能なため、ブルートフォース攻撃にも非常に強いです。
このように、レインボーテーブル攻撃にはソルト付きハッシュ+遅延型アルゴリズムの組み合わせで対抗するのがベストプラクティスです。
6. Spring Securityでハッシュ化を行うには?(BCryptの使い方)
Spring Securityでパスワードのハッシュ化を行うには、BCryptPasswordEncoderを使用するのが一般的です。BCryptは自動的にソルトを生成してくれるため、安全性が非常に高く、初心者にも扱いやすいのが特徴です。
GradleでSpring Securityの依存関係を追加するには、Pleiadesのプロジェクト作成時に「Spring Security」にチェックを入れるだけでOKです。特別な設定をしなくても、BCryptPasswordEncoderはすぐに使用できます。
以下は、実際にBCryptを使ってパスワードをハッシュ化する基本的なコードです。
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class HashExample {
public static void main(String[] args) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String rawPassword = "password123";
String encodedPassword = encoder.encode(rawPassword);
System.out.println(encodedPassword);
}
}
このように、encodeメソッドを呼び出すだけで、ソルト付きのハッシュ化が行われます。ユーザーが同じパスワードを入力しても、毎回異なるハッシュが生成されるため、セキュリティ的にも安心です。
7. BCryptとArgon2の違いと選び方(初心者向けにざっくりと)
Spring Securityでは、BCryptとArgon2の両方がパスワードハッシュ化アルゴリズムとして使えます。では、どちらを選べばいいのでしょうか?
BCryptは長年使われてきた実績あるアルゴリズムで、Spring Securityのデフォルトにもなっています。設定も簡単で、初心者が最初に使うにはぴったりです。
一方、Argon2は近年登場した新しいアルゴリズムで、メモリ使用量・CPU時間・実行時間などを細かく制御できる高度なハッシュ関数です。ブルートフォース攻撃に対する耐性も強化されています。
初心者におすすめの選び方は以下の通りです。
- 簡単・実績・広く使われている → BCrypt
- セキュリティ最優先・将来的に強化したい → Argon2
どちらもソルト付きハッシュを自動で生成してくれるため、安全性は十分にあります。ただし、BCryptはスマートフォンなどリソースの限られた環境でも軽く動作する点も魅力です。
8. 実際に@Controllerを使ってログイン用のハッシュ化処理を実装する簡単なサンプル
では、実際にSpring Bootと@Controllerを使って、ユーザー登録時にパスワードをハッシュ化して保存する例を見てみましょう。ここでは、BCryptを使用します。
まず、ユーザー入力フォームのHTMLは次のようになります(テンプレートエンジンにThymeleafを想定)。
<form action="/register" method="post">
<label>ユーザー名:</label>
<input type="text" name="username"/><br/>
<label>パスワード:</label>
<input type="password" name="password"/><br/>
<button type="submit">登録</button>
</form>
次に、@Controllerを使ったコントローラクラスを作成します。ここでBCryptを使って、フォームから受け取ったパスワードをハッシュ化します。
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Controller
public class RegisterController {
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
@PostMapping("/register")
public String registerUser(@RequestParam String username,
@RequestParam String password,
Model model) {
String hashedPassword = passwordEncoder.encode(password);
// 実際のアプリではDB保存処理が入ります
System.out.println("ユーザー名: " + username);
System.out.println("ハッシュ化されたパスワード: " + hashedPassword);
model.addAttribute("message", "登録が完了しました!");
return "register_result";
}
}
このように、@ControllerとBCryptPasswordEncoderを使うことで、初心者でも簡単にハッシュ化処理が実装できます。セキュリティ上、ハッシュ化されたパスワードはそのままDBに保存してOKです。
また、ログイン時にはユーザーが入力したパスワードと、DBに保存されたハッシュ値をmatchesメソッドで照合するのが基本です。
if (passwordEncoder.matches(inputPassword, storedHashedPassword)) {
// ログイン成功
}
このように、パスワードのハッシュ化と照合はセットで扱うのがポイントです。
Spring Security 設定では、BCryptやArgon2を選択することで、より安全な認証システムが構築できます。パスワードハッシュ化はログイン機能の要ですので、この記事で紹介した内容を参考に、自分のプロジェクトにも取り入れてみてください。