パスワードをハッシュ化する理由とは?セキュリティ対策の基本をSpring Securityで学ぼう
新人
「先輩、パスワードってデータベースにそのまま保存してもいいんですか?」
先輩
「それは絶対にダメだよ。パスワードは必ずハッシュ化して保存するんだ。」
新人
「ハッシュ化って暗号化とどう違うんですか?」
先輩
「似ているけどまったく違う仕組みなんだ。詳しく説明するね。」
1. パスワードをハッシュ化するとはどういうことか?
ハッシュ化とは、あるデータ(この場合はパスワード)を一方向の関数で変換して、元に戻せない形にする処理のことです。暗号化とは違い、ハッシュ化されたデータは復号することができません。
たとえば「password123」という文字列をハッシュ関数に通すと、ef92b778bafe771e89245b89...のような結果になります。この結果は、同じ入力をしても毎回違う文字列になるようにもできます(ソルトを使う)。
この仕組みは、「セキュリティ対策」として非常に重要で、Spring Securityでも標準で使われています。「パスワード ハッシュ化」は、現代のWebアプリ開発における基本中の基本です。
2. なぜパスワードをそのまま保存してはいけないのか?
パスワードをそのまま保存することは「平文保存」と呼ばれ、極めて危険な行為です。データベースが不正アクセスされた場合、パスワードがすべて漏れてしまうことになります。
たとえば、次のようにパスワードを保存していたとします。
@Entity
public class User {
private String username;
private String password; // 平文保存(これは絶対に避けるべき)
}
このような実装では、たとえデータベースが暗号化されていたとしても、攻撃者が復号キーを手に入れたらパスワードが全て見えてしまいます。これでは「セキュリティ対策」とは言えません。
そこで、ハッシュ化の出番です。Spring Securityを使えば、パスワードのハッシュ化は簡単に実装できますし、強固な「パスワード ハッシュ化」が実現できます。
3. ハッシュ関数の特徴と安全性
ハッシュ関数は、「一方向性」と「衝突耐性」を持った特別な関数です。「一方向性」とは、入力から出力を計算することはできるけれど、出力から入力を逆算することができないという意味です。これにより、パスワードをハッシュ化した結果から、元のパスワードを割り出すことができなくなります。
また、「衝突耐性」とは、異なるデータが同じハッシュ値になること(ハッシュの衝突)が起きにくいという性質です。これも「セキュリティ対策」の重要な要素です。
さらに安全性を高めるために使われるのが「ソルト(salt)」です。ソルトとは、パスワードに付け加えるランダムな文字列のことです。同じパスワードでもソルトを付ければ異なるハッシュ値になります。
たとえば、次のようなソルトを使ったハッシュの概念です。
String password = "mypassword";
String salt = "abc123";
String salted = password + salt;
String hashed = hashFunction(salted);
このように「ハッシュ関数」と「ソルト」の組み合わせで、安全性が大幅に高まります。Spring Security パスワード管理でもこの考え方が基本に組み込まれています。
4. Spring Securityでのハッシュ化の方法
Spring Securityでは、BCryptというハッシュ関数を使った仕組みが標準で用意されています。BCrypt ハッシュ化は、計算コストが高くなるよう設計されているため、パスワードクラックに時間がかかる特徴があります。
開発環境がpleiades + Gradleの場合、Spring Securityの依存関係を追加することでBCryptPasswordEncoderが利用可能になります。
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class PasswordEncoderExample {
public static void main(String[] args) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String rawPassword = "secret123";
String encodedPassword = encoder.encode(rawPassword);
System.out.println("ハッシュ化されたパスワード: " + encodedPassword);
}
}
ハッシュ化されたパスワード: $2a$10$7Q1zBsH0gPvuz7lAjv3WTO...
このコードでは、encode()メソッドで安全にパスワードをハッシュ化しています。同じ文字列を毎回ハッシュ化しても異なる結果になるのは、BCryptが内部でソルトを自動生成してくれるためです。
次は、Spring MVC(@Controller使用)でユーザー登録時にハッシュ化を行う例です。
@Controller
public class SignupController {
@Autowired
private UserRepository userRepository;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@PostMapping("/signup")
public String register(@ModelAttribute User user) {
String hashed = passwordEncoder.encode(user.getPassword());
user.setPassword(hashed);
userRepository.save(user);
return "redirect:/login";
}
}
Spring Security パスワード管理では、このような形でBCryptを活用し、安全な認証処理が実現されます。
5. ハッシュ化と暗号化の違い
ハッシュ化と暗号化は似ているようで、目的も使い方も異なります。初心者の方にとっては混同しやすい部分なので、ここでしっかり区別しておきましょう。
まず、暗号化とは「元に戻せる」処理です。暗号化されたデータは、専用の鍵(キー)を使うことで復号することができます。たとえば、メールの内容やファイルなどを特定の相手にだけ読ませたい場合に使います。
一方、ハッシュ化は「元に戻せない」処理です。パスワードのように、比較だけできればよく、元の値を復元する必要がないケースで使います。
例えるなら、「暗号化」は南京錠をかけて後から鍵で開けられる仕組み、「ハッシュ化」は粉々に砕いたガラスのように元に戻せない仕組みです。
ハッシュ化されたパスワードは、ログイン時に同じハッシュ関数で変換した結果を比較することで認証します。
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
if (encoder.matches(inputPassword, storedHashedPassword)) {
// 認証成功
} else {
// 認証失敗
}
このようにして、Spring Security パスワード管理では、matches()メソッドを使って、ハッシュ値同士の比較による認証処理を行います。これにより、データベース内のパスワードが万が一漏洩しても、元の値を知られるリスクは大幅に下がります。
6. より強力なハッシュアルゴリズム「Argon2」の紹介
パスワードのハッシュ化に使われる代表的なアルゴリズムとして、BCryptが一般的ですが、さらに高い安全性を追求する場面では「Argon2 ハッシュ」というアルゴリズムが注目されています。
Argon2は、パスワードハッシュの国際コンペ「Password Hashing Competition(PHC)」で最優秀に選ばれたアルゴリズムで、計算コストだけでなく、メモリコストや並列性も加味して設計されています。これにより、GPUなどによる総当たり攻撃にも耐性が高くなります。
ただし、現時点(2025年)ではSpring Securityの標準機能としてはArgon2は提供されていません。そのため、Argon2を使うには外部ライブラリやカスタム実装が必要ですが、本記事ではSpring Securityの範囲に限定し、BCryptの理解を優先します。
しかしながら、将来的にSpring Security ハッシュ処理が拡張される可能性を考慮し、Argon2 ハッシュも理解しておくことは非常に重要です。
7. パスワードハッシュ化を使ったセキュアなログイン処理の流れ
パスワードを安全に管理するためには、ハッシュ化された値を使ってログイン処理を行う必要があります。ここでは、Spring Security ハッシュ処理を使ったログイン処理の流れを整理します。
ユーザーがログインフォームにIDとパスワードを入力したとき、Spring Securityは以下のように動作します。
- フォームの入力内容を
UsernamePasswordAuthenticationTokenとして受け取る。 - ユーザー名に一致するユーザー情報をデータベースから取得する。
- 入力されたパスワードと、保存されているハッシュ値を
BCryptPasswordEncoder.matches()で比較する。 - 一致していれば認証成功。セッションを開始し、アプリケーション内の保護されたリソースへのアクセスを許可する。
この一連の処理は、Spring SecurityのAuthenticationManagerとUserDetailsServiceなどを通じて自動的に行われるため、開発者がすべてを手動で実装する必要はありません。
ログイン処理の安全性を確保するために、パスワード ハッシュ化を確実に実装しておくことが前提条件です。
8. ハッシュ化の実装でよくあるミスとその防止策
最後に、ハッシュ化 ミスによってセキュリティが弱くなるケースをいくつか紹介し、それぞれの対策を解説します。
① パスワードをハッシュ化せずに保存してしまう
もっとも基本的なミスです。新規登録時にハッシュ処理を忘れてしまうと、平文のままデータベースに保存されてしまいます。これを防ぐためには、サービス層や@Controller内で明示的にハッシュ化処理を記述する必要があります。
@Controller
public class UserController {
@Autowired
private UserRepository userRepository;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@PostMapping("/register")
public String register(@ModelAttribute User user) {
String encoded = passwordEncoder.encode(user.getPassword());
user.setPassword(encoded); // ハッシュ化して保存
userRepository.save(user);
return "redirect:/login";
}
}
② 同じパスワードで同じハッシュが生成される
固定されたソルトやハッシュ関数を使うと、同じパスワードを使っているユーザーが同じハッシュ値になってしまいます。BCryptでは、内部で自動的にソルトを付与してくれるため、このミスを防げます。
③ 認証時にハッシュ化せずに直接比較してしまう
ログイン時、入力されたパスワードをハッシュ化せずにDBの値とequals()などで比較してしまうケースです。必ずBCryptPasswordEncoder.matches()を使いましょう。
if (encoder.matches(rawPassword, storedHash)) {
// 正常認証
}
④ ハッシュアルゴリズムの選定ミス
MD5やSHA-1など、現在では脆弱とされるアルゴリズムを使うのもミスの一つです。現代ではBCrypt、PBKDF2、Argon2などが推奨されています。
まとめると、パスワード ハッシュ化はセキュリティ対策として欠かせない技術であり、正しい実装と安全なアルゴリズムの選定が求められます。Spring Securityの仕組みを活用すれば、これらのミスを防ぎながら、堅牢なログイン処理を構築することが可能です。