Skip to main content

Javaアプリケーションでパスワードを正しくハッシュ化する方法!

Java engineering feature

2022年3月24日

0 分で読めます

機密性の高いパスワードを保存する方法はいくつもあります。選択肢があるのはよいことですが、パスワードの保存方法を誤ると、セキュリティ上の悪夢につながりかねません。そこで、いくつかの選択肢をハッシュしてみましょう。この記事では、Javaアプリケーションでパスワードをどのようにハッシュ化すべきかを解説します。ここで紹介する原則はどのエコシステムにも応用できますが、特にJavaでパスワードをハッシュ化する最適な方法を取り上げます。

機密性の高いパスワードの保存方法を考える際、まず検討すべきなのは、認証と認可を自分で行う必要があるのかということです。これらを代わりに担ってくれるサービスは数多くあります。認証・認可サービスを利用すれば、アプリケーションでパスワードを安全に保存する負担を軽減できます。Auth0やOktaなど、手軽に利用できる商用サービスも多数あります。選んだプロバイダーがOpenID Connectなどのオープン標準に対応していれば、Javaアプリケーション(たとえばSpring Boot)にも簡単に統合できます。また、プロバイダーによってはMFA(多要素認証)にも対応しており、エンドユーザーのセキュリティをさらに強化できます。

Javaアプリケーションでパスワードを自分で保存する必要がある場合は、この記事が役立ちます。

保存するパスワードのハッシュ化

アプリケーションにユーザー名とパスワードを保存する場合、何があっても、パスワードを平文で保存してはいけません(言うまでもないことですが!)。システムが侵害されると、攻撃者は平文のパスワードをそのまま読み取り、ユーザーになりすますことができます。

平文パスワードという危険を避けるには、ユーザーのパスワードをハッシュ化して保存します。そのためには、パスワードハッシュ化アルゴリズムが必要です。

パスワードハッシュ化アルゴリズムとは?

ハッシュ化アルゴリズムにはさまざまな種類がありますが、パスワードハッシュ化アルゴリズムは、パスワードの処理に特化して設計された一方向関数です。平文のパスワードを、データベースに安全に保存できるデータ文字列に変換します。この変換を元に戻すことはできません(平文のパスワードからハッシュは生成できますが、ハッシュからパスワードを復元することはできません。これが「一方向」関数と呼ばれる理由です)。

パスワードのハッシュ化の仕組み

パスワードハッシュ化アルゴリズムは、ユーザー認証の際に使います。ユーザーからパスワード(平文)が送られるたびに、すぐにハッシュ化します。その後、登録時にはハッシュを保存し、認証時には保存済みのハッシュと照合してパスワードが正しいかを確認します。

ハッシュ化アルゴリズムは数多くありますが、パスワードに適したものはごくわずかです。たとえば、ファイルの整合性確認など、特定の用途向けに設計されたハッシュ関数も多くあります。こうしたアルゴリズムは、パスワードには適していません。

ユーザーのパスワードには、強力なパスワードハッシュ化アルゴリズムが必要です。優れたパスワードハッシュ化アルゴリズムは、相応の計算能力とメモリを消費します。パスワードハッシュ化アルゴリズムでは、使用する計算リソースが多いほど優れています。

強力なパスワードハッシュ化アルゴリズムは処理に時間がかかるため、総当たり攻撃の効果を弱められます。詳しく解説することもできますが、要点はこうです。ユーザーを最大限保護するには、パスワードに強力で多くのリソースを消費するハッシュ化アルゴリズムを使いましょう。

最適なパスワードハッシュ化アルゴリズムは?

パスワードハッシュ化アルゴリズムの第一の原則は、独自のアルゴリズムを作らないことです!例外は、熟練した暗号技術者である場合です。そうでなければ、業界標準に従い、十分に検証されたアルゴリズムとライブラリをアプリケーションで利用してください。

OWASP Foundationによると、最新のパスワードハッシュ化で検討すべきアルゴリズムはごくわずかです。推奨されるアルゴリズムを見ていきましょう。

Argon2id

現時点(2022年)で最適なアルゴリズムはArgon2idです。Argon2は、Password Hashing Competitionで優勝した鍵導出関数です。次の3つのバージョンがあります。

  • Argon2d:GPUを使った解読攻撃への耐性を最大化

  • Argon2i:サイドチャネル攻撃への耐性を最適化

  • Argon2id:両者を組み合わせたハイブリッド版

Argon2idは、サイドチャネル攻撃とGPUベースの攻撃の両方にバランスよく対処できるため、パスワードに推奨されます。Argon2idアルゴリズムは、メモリサイズ(m)、反復回数(t)、並列度(p)の3つのパラメーターで設定できます。

現在推奨されている設定は、メモリを最低15 MiB、反復回数を2、並列度を1としてArgon2idを使うことです。このアルゴリズムには、事前計算攻撃を防ぐためにユーザーごとのソルトを自動生成する機能が含まれているため、特別な設定は不要です。

Bouncy Castleを使ったSpring Security Crypto

JavaアプリケーションにArgon2idを実装する最も簡単な方法は、spring-security-cryptoライブラリを使うことです。Springフレームワークの一部ですが、Springフレームワークの他の機能を使う必要はありません。

spring-security-cryptoライブラリには、利用できるArgon2PasswordEncoderがあります。技術的にはエンコーダーではなくハッシャーなので、名前は少し適切でないと思います。Springライブラリは、Argon2アルゴリズムをJavaで完全に実装した依存関係としてbouncycastleを使用しています。spring-security-cryptoライブラリはbouncycastleの使いやすいインターフェースと考えることができ、利用を簡単にしてくれます。bouncycastleに加えて、ログ出力に使うcommon-loggingも依存関係に追加する必要があります。

<dependency>
   <groupId>org.springframework.security</groupId>
   <artifactId>spring-security-crypto</artifactId>
   <version>5.6.2</version>
</dependency>
<dependency>
   <groupId>commons-logging</groupId>
   <artifactId>commons-logging</artifactId>
   <version>1.2</version>
</dependency>
<dependency>
   <groupId>org.bouncycastle</groupId>
   <artifactId>bcpkix-jdk15on</artifactId>
   <version>1.70</version>
</dependency>

Argon2PasswordEncoderはデフォルトでArgon2idを使用し、m=4MiB、t=3、p=1に設定されています。反復回数は最低値を上回っていますが、メモリ使用量をさらに増やすことも検討するとよいでしょう。以下のJavaの例では、最低要件であるm=15Mib、t=2、p=1を使用しています。ソルトとハッシュの長さには、デフォルトと同じ値を使っています。

Argon2PasswordEncoder encoder = new Argon2PasswordEncoder(32,64,1,15*1024,2);
var myPassword = "ThisIsMyPassword";

var encodedPassword = encoder.encode(myPassword);
System.out.println(encodedPassword);

var validPassword = encoder.matches(myPassword, encodedPassword);
System.out.println(validPassword);

このコードの出力は次のようになります。

$argon2id$v=19$m=15360,t=2,p=1$YpRuuQhW1dHOimAnWD5TRU6Sebitu+fIrmIrenr+YOM$hkEXhhHpu2NUcPwhV4IUQelQdf4I8V+iyFsFiC8BYEisE3oWFv96zYeNA1i/awhaDo1XHz6Pp/1r55SS/I4AIA
True

このハッシュには、エンコーダーに指定した設定やソルト情報など、必要な情報がすべて含まれています。そのため、encoder.matches()を使って元のパスワードが一致するかを簡単に確認できます。設定の異なるArgon2PasswordEncoderの別インスタンスでも同様に動作します。

JVM向けArgon2 Bindingを使う

JVM向けのArgon2 Bindingライブラリは、Springライブラリに代わる選択肢です。Moritz Halbritterが開発したこのライブラリは、JNAを使ってネイティブのCライブラリを呼び出します。この方法の利点は、Argon2の開発者によるオリジナル実装を使えることと、パフォーマンス上のメリットが期待できることです。そのため、システム上で基盤となるCライブラリを利用できる必要があります。

パッケージマネージャーを使ってCライブラリを自分でインストールする方法と、コンパイル済みのArgon2ライブラリをまとめたライブラリに依存する方法があります。前者は、言うまでもなくサイズが小さいため推奨されます。

コンパイル済みライブラリを使わない場合:

<dependency>
    <groupId>de.mkammerer</groupId>
    <artifactId>argon2-jvm-nolibs</artifactId>
    <version>2.11</version>
</dependency>

コンパイル済みライブラリを使う場合:

<dependency>
    <groupId>de.mkammerer</groupId>
    <artifactId>argon2-jvm</artifactId>
    <version>2.11</version>
</dependency>

このライブラリの使い方はとても簡単です。以下のコード例では、Springの例と同じ最低限のパラメーターを使った同等の処理を紹介します。

Argon2 argon2 = Argon2Factory.create(Argon2Factory.Argon2Types.ARGON2id, 32, 64);
var myPassword = "ThisIsMyPassword";

var hash = argon2.hash(2,15*1024,1, myPassword.toCharArray());
System.out.println(hash);

var validPassword = argon2.verify(hash, myPassword.toCharArray());
System.out.println(validPassword);

scrypt

何らかの理由でArgon2idを使えない場合は、scryptが次善の選択肢です。Argon2idと同様に、scryptも強力なパスワードハッシュ化アルゴリズムで、さまざまなコストパラメーターを設定して消費するリソースを増やせます。

OWASP Password cheat sheetによると、CPUとメモリのコストパラメーター(N)、ブロックサイズ(r)、並列化パラメーター(p)をそれぞれ最低値以上に設定する必要があります。以下は最低限必要な設定です。

  • N=2^16(64 MiB)、r=8(1024 bytes)、p=1

  • N=2^15(32 MiB)、r=8(1024 bytes)、p=2

  • N=2^14(16 MiB)、r=8(1024 bytes)、p=4

  • N=2^13(8 MiB)、r=8(1024 bytes)、p=8

  • N=2^12(4 MiB)、r=8(1024 bytes)、p=15

上記のscryptパラメーターは、どれを選んでも同等に強力なパスワードハッシュを生成します。N、r、pの値を変えることで、好みに応じてscryptアルゴリズムが使用するCPUやメモリのリソースを調整できます。以下の推奨例では、実際の利用方法を示しています。そのままコピーして使えるので簡単です。

spring-security-cryptoライブラリは、SCryptをはじめ多くのアルゴリズムに対応しています。SCryptEncoderは、先ほどArgon2Encoderを使ったのと同じ方法で利用できます。以下の例では、CPUとメモリの割り当てのバランスを取るため、中央の推奨値を使っています。

SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(16384, 8, 4, 32, 64);
var myPassword = "ThisIsMyPassword";

var encodedPassword = encoder.encode(myPassword);
System.out.println(encodedPassword);

var validPassword = encoder.matches(myPassword, encodedPassword);
System.out.println(validPassword);

出力:

e0804$8FQ4x/ntwEz2ZNu8QRyIQJlAXR+gQkiG3WulLMEq/kioVtaFiKE7sZDGgtmqUmwB8OE+f7Eagux9QXG478unLw==$RS1Bz5Uf30dWGxc+vtlkjj7tPnPdgq8YD1V8odhPW4A=
true

BCryptを使う

Argon2idとscryptを利用できない場合は、BCryptも強力な選択肢です。BCryptを使う場合、ワークファクターパラメーターは必ず10以上に設定してください。

これまでと同様にspring-security-cryptoライブラリを使えます。デフォルトのワークファクターは10ですが、以下の例では14に設定しています(2022年時点で妥当な値です)。

BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(14);

ワークファクターを高く設定するほどハッシュは強力になりますが、処理の完了にはより多くのCPUリソースと時間が必要です。私のローカルマシンでは、BCryptをワークファクター10で実行すると平均約89msかかります。ワークファクター14では平均1033msで、ほぼ12倍の時間です。正確なベンチマークではありませんが、ワークファクターを上げたときの影響はわかります。

パスワードのハッシュ化に関するベストプラクティス

アプリケーションにパスワードハッシュ化を実装する際は、いくつかのベストプラクティスを念頭に置いてください。

  1. パスワードハッシュ化アルゴリズムを自分で実装するのは避け、十分に検証された開発者向けライブラリを使いましょう。暗号技術は複雑な分野です。一般的なアルゴリズムを自分で実装しようとすると、多くの問題が起こりかねません。

  2. コンピューターは年々高性能・高速になっているため、パスワードハッシュ化アルゴリズムとそのパラメーターも調整が必要です。最新のパスワードハッシュ化アルゴリズムでは、ハッシュ計算に使うリソース量を開発者が指定します。そのため、時間の経過に応じてパラメーターを更新しなければなりません。こうした情報を常に把握するには、最新のセキュリティニュースやトレンドを追うことが大切です。たとえばSnyk blogは、セキュリティ情報や経時的なトレンドを把握するのに役立つ情報源です。

Snykなどの無料ツールを使ってセキュリティ問題を事前に特定し、利用しているライブラリや手法の安全性を確認しましょう。Snykのようなツールは、コードのセキュリティ問題を早期に発見し、修正方法や詳しい情報も提示できます。テクノロジーが進化する中で、コードベースの安全性を長期にわたって維持するために欠かせない存在です。

CTFを始めよう

オンデマンドのバーチャル入門ワークショップで、CTFチャレンジの解き方を学びましょう。

続きを読む

blog feature ai violet
Blog

VulnBump:AIエージェントはビルドを壊さずに脆弱な依存関係を修正できるか?

VulnBumpは、実際の依存関係アップグレード307件を対象にAIエージェントをテストします。脆弱性が解消され、ビルドとテストが成功した場合にのみ、修正としてカウントされます。

blog feature snyk container custom base recommendations
Blog

Evo Agentic AppSecのご紹介:AI開発のスピードに対応するセキュリティ

AI駆動型開発のスピードに、従来のセキュリティワークフローは追いついていません。SnykのAgentic AppSecは、セキュリティエージェントが脆弱性を継続的に検出、修正、検証し、人が基準を定められるようにします。

Blog

フロンティアモデルは脆弱性を発見した。攻撃者だけがエクスプロイトチェーンを見つけた。

静的解析で欠陥は見つかりましたが、ライブ攻撃テストで侵害につながる連鎖を実証できたのは唯一でした。Evo COS、Claude Security、Claude Code Securityを比較します。