パスワードのハッシュ化[Java]
はじめに
こんにちは。
プログラミング初心者wakinozaと申します。
勉強中に調べたことを記事にまとめています。
十分気をつけて執筆していますが、なにぶん初心者が書いた記事なので、理解が浅い点などあるかと思います。
間違い等あれば、指摘いただけると助かります。
記事を参考にされる方は、初心者の記事であることを念頭において、お読みいただけると幸いです。
対象読者
- Webアプリケーションのセキュリティ対策を勉強中の方
動作環境
- Windows11
- Visual Studio Code
- Oracle Java 21
- eclipse 2025 (pleiades)
- MySQL 8.4.6
記事のテーマ
・現在、Webアプリケーションのセキュリティ対策について勉強しています。
この記事では、Servlet/JSPとMySQLをつかったWebアプリケーションを題材に、パスワードのハッシュ化についてまとめます。
目次
1. メッセージダイジェスト
2. メッセージダイジェストの脅威
3. ハッシュ解読対策
4. Servlet/JSPでのコード例
1. メッセージダイジェスト
パスワードが漏洩すると、影響範囲は非常に広くなります。
そのため、万が一SQLインジェクションなどでデータベースの情報が漏洩しても、パスワード情報が悪用されないように、対策する必要があります。
情報の保護というと、まずは「暗号化」が思い浮かぶかもしれません。
確かに、Webアプリケーションの開発言語には暗号化のライブラリが用意されているので、パスワードの暗号化・復号化自体は難しくありません。
しかし、パスワードを暗号化すると、今度は鍵の扱いが難しくなります。
データベース操作をするたびに「鍵」が必要となるため、アプリケーションから参照できるディレクトリに鍵を配置する必要があります。
しかし、アプリケーションからは参照でき、攻撃者の手に渡らないように「鍵」を保管することは非常に困難です。(そもそも、そんな方法があるのなら、パスワードそのものをそうやって保管すれば良いでしょう)
鍵の保管が安全でないなら、鍵で守られているパスワードも安全とは言えません。
そのため、パスワードには、「鍵」による暗号化ではなく、「メッセージダイジェスト」による保護が広く行われています。
「メッセージダイジェスト」とは、任意の長さのデータから、固定長のデータ(メッセージダイジェスト、またはハッシュ値)を作り出すことです。
セキュリティ目的でハッシュ関数が用いられる場合、以下の3つの性質を備えていることが不可欠です。
-[原像計算困難性]:ハッシュ値から元のデータを見つけることが現実的な時間内では困難であるという性質
-[第2原像計算困難性]:元データを与えられたとき、元データと同じハッシュ値を持つ別のデータを見つけることが現実的な時間内では困難であるという性質
-[衝突困難性]:同じハッシュ値を持つ2つのデータを見つけることが困難である性質
パスワードをハッシュ化した場合は、パスワードはメッセージダイジェストの状態でデータベースに保存されます。また、パスワードを照合する時は、入力されたデータをハッシュ化し、データベースに保存されているメッセージダイジェストと照合します。
もし、データベースの情報が漏洩しても、パスワードそのものは保管されていませんし、メッセージダイジェストから元のパスワードを推測することは困難であるため、パスワードの漏洩を防ぐことができるのです。
2. メッセージダイジェストの脅威
しかし、ハッシュ関数が上の3つの要件を満たしていてもなお、攻撃者はパスワードを解析しようと様々な手法を生み出します。
この章では、メッセージダイジェストに対する脅威を、3つ紹介します。
2-1. オフラインブルートフォース攻撃
ハッシュ関数は原像計算困難性(ハッシュ値から元のデータを見つけることが困難という性質)を持ちますが、これは現実的な時間内に解析ができないことを意味しています。
しかし、パスワードは文字数も少なく、文字の種類も制限されるため、高速な計算資源を用いた総当たり攻撃を行えば、現実的な時間内でパスワードが特定されるリスクがあります。
また、ハッシュ関数そのものは、パスワード以外のシステムにも用いられるため、業務効率の改善のために高速化がすすめられてきました。
しかし、ハッシュ関数の計算効率の良さは、システム利用時にはメリットとなりますが、パスワード保護においては攻撃者の試行回数を増やしてしまうという脆弱性に直結します。
つまり、パスワードを単純にハッシュ化するだけでは、総当たり攻撃でパスワードを解析される恐れがあるため、安全とは言い切れないのです。
2-2. レインボーテーブル
2つ目の脅威は、「レインボーテーブル」です。
ハッシュ関数を利用して、事前にオフラインブルートフォース攻撃を実施し、得られたメッセージダイジェストと元データの組み合わせを、辞書として表を作成するという方法です。
データとメッセージダイジェストの対照表が作られてしまえば、パスワードの解析はごく短時間で完了してしまいます。
かつては、パスワードの組み合わせが膨大な数になるため、この手法は実現困難であるとされてきました。しかし、「レインボーテーブル」という手法が開発されたことで、現在では、10文字程度のパスワードであれば、表引きによる短時間の解析が可能になってしまいました。
2-3. データベース内辞書攻撃
既知のハッシュ関数ではレインボーテーブルを作成されてしまうため、未知のハッシュ関数を用いてハッシュ化を行うことが実施されました。
しかし、攻撃者にとって未知のハッシュ関数であっても、脅威が存在します。
それが、データベース内にパスワード辞書を作るというものです。
攻撃者はユーザを装い、アプリケーションに正規の手順で大量のパスワードを保存します。
その後、SQLインジェクションなどの手法でデータベース情報を取得します。
そして、データベースに保存された正規ユーザのメッセージダイジェストの中から、ダミーユーザのメッセージダイジェストと一致するものを探します。ダミーユーザの登録するパスワードはもちろん攻撃者の把握するところなので、一致するメッセージダイジェストがあれば、正規ユーザのパスワードを特定できるのです。
3. ハッシュ解読対策
このように、単純にハッシュ化しただけのパスワードは安全とは言えません。
これらの脅威が成立する背景には、一般的なパスワードの文字数が短く、とりうるパターン数が攻撃側の計算能力に対して十分に大きくないという実態があります。
仮に、パスワードの文字数を20文字以上とすると、これらの脅威にさらされるリスクは非常に小さくなるでしょう。
しかし、すべてのユーザに20文字以上のパスワードを設定させることも現実的ではありません。
そのため、一般的な8文字程度のパスワードであっても、パスワードを解析されない対策が考え出されました。
それが、「ソルト」と「ストレッチング」です。
3-1. ソルト
「ソルト」とは、ハッシュの元データに追加する文字列のことです。ソルトはユーザごとに異なるものが設定されるため、「データベース内にパスワード辞書を作る攻撃」によるパスワードの解析は困難になります。
また、ソルトを追加することで、ハッシュ化する元データの文字数も、取りうるパターン数も大幅に増加します。そのため、「レインボーテーブル」によるパスワードの解析も困難になります。
また、ソルトそのものも、暗号学的に安全な擬似乱数生成器(CSPRNG)を用いて推測されないような値を生成する必要があります。ソルトを提供しているライブラリでは、この乱数生成プロセスが隠蔽されているため、攻撃者がソルトを推測することを困難にしています。
3-2. ストレッチングと新しいハッシュ関数
ソルトは攻撃のリスクを劇的に低下させる手段ですが、「オフラインブルートフォース攻撃」は防ぐことはできません。それは、データベース内のメッセージダイジェストが漏洩しているということは、同じテーブル内のソルト情報も漏洩している可能性が高いからです。
攻撃者は、適当なデータとソルトを組み合わせて総当たり攻撃を実施するだけで、ソルト実施前と同様にパスワードが解析されてしまう可能性があります。
「オフラインブルートフォース攻撃」で問題になるのは、ハッシュ関数が高速に実行出来てしまうという点です。ハッシュ関数が高速に実行されるため、総当たり攻撃を仕掛けても現実的な時間内に解析ができてしまうのです。
そのため、ハッシュ計算を繰り返し行ったり、パスワード保存用に作られた「処理が遅い」ハッシュ関数(BCrypt,PBKDF2,Argon2など)を用いたりすることで、ハッシュ化処理をわざと「遅く」することで対応します。
一回の実行時間が長くなれば、総当たり攻撃に必要な時間は爆発的に増加します。
現実的な時間内に解析が出来なければ、パスワードが解析されるリスクを大幅に減らすことができるのです。
4. Servlet/JSPでのコード例
Java言語では、Argon2やBCryptのパスワードハッシュ化アルゴリズムが用いられています。
この記事では、BCryptの実行コード例を紹介していきます。
BCryptはSpring Securityなどで広く採用されているアルゴリズムで、ソルトとストレッチングの両方を実装しています。また、BCryptはCPUコストを増加させる設計で、一回当たりの計算を重くすることで、並列計算による総当たり攻撃を現実的でない時間に引き延ばします。
それでは、Java(Servlet/JSP)でBCryptを利用したパスワードハッシュ化をコード例で紹介します。
BCryptはSpring Securityのライブラリを利用します。
まずは、MavenでSpring SecurityのCryptoモジュールを導入します。
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
<version>6.2.1</version>
</dependency>
次に、実際にパスワードをハッシュ化してみましょう。
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class RegisterService {
public void registerUser(String username, String rawPassword) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12);
// ハッシュ化を実行
String encodedPassword = encoder.encode(rawPassword);
System.out.println("DB保存用ハッシュ値例: " + encodedPassword);
//DB保存用ハッシュ値例: $2a$12$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
}
}
また、BCryptPasswordEncoderは引数でコストパラメータ(ストレッチング回数)を指定することができます。
コストパラメータは、値を1増やすごとに計算時間は倍増するため、サーバーのスペックと許容できる認証遅延(一般的に250ms〜500ms程度)に基づいて数値を決定します。また将来、ハードウェアの計算速度が向上した場合も、コード変更なしに設定値のみで調整できるのがBCryptの利点です。
次にBCryptが生成する文字列(Modular Crypt Format)をデータベースのテーブルに格納します。
-- テーブル作成(例)
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE,
password_hash VARCHAR(255)
);
-- 手順1で生成した文字列を挿入
INSERT INTO users (username, password_hash)
VALUES ('test_user', '$2a$12$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy');
文字列はおよそ60文字あるので、password_hashカラムの最大値は余裕をもって設定する必要があります。
また、BCryptが生成する文字列には、アルゴリズム識別子、コストパラメータ、ソルト、そして最終的なハッシュ計算結果が含まれています。そのため、データベース側にソルト保存用のカラムを別途用意する必要はありません。
public class LoginService {
public boolean authenticate(String inputPassword, String storedHash) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder.matches(inputPassword, storedHash);
}
}
matchesメソッドは、第1引数に入力された平文のパスワードを渡すと、内部で自動的にハッシュ化し、第2引数の格納されたハッシュ値と照合してくれます。
まとめ
安全なパスワード保存のために本記事では、単純なハッシュ化の危うさと、それを補完するソルト・ストレッチングの重要性を解説しました。
-
ハッシュ化は「不可逆性」だけでなく「計算コスト」が鍵: 高速なハッシュ関数(SHA-256等)をそのまま使わず、BCryptやArgon2のような「意図的に遅い」アルゴリズムを選定することが現代の標準です。
-
ライブラリの適切な利用: BCryptのように、ソルト生成やコスト管理を内部で安全に処理してくれるライブラリを活用することで、実装ミスによる脆弱性を最小化できます。
-
継続的な調整: コストパラメータを環境に合わせて最適化し続ける姿勢が、長期的なセキュリティ担保には不可欠です。
記事は以上です。
最後までお読みいただき、ありがとうございました。
参考情報一覧
この記事は以下の情報を参考にして執筆しました。
- [体系的に学ぶ 安全なWebアプリケーションの作り方 第2版]
- [スッキリわかるサーブレット&JSP入門 第4版]
- [スッキリわかるSQL入門 第3版]
- [MySQL徹底入門 第5版]
- JavaでBCrypt + ストレッチ回数ごとの処理時間計測 (最終更新 2018-11-16) (参照 2026-02-02)
- 【Spring Framework】Spring Securityを使用したログイン機能の実装 (最終更新 2025-06-12) (参照 2026-02-02)