【入門編】 パスワードハッシュ化におけるソルトとストレッチングの重要性 – 暗号理論・認証基盤 & エンドポイントセキュリティ防御ガイド

こんにちは!Webアプリケーション開発やインフラの管理、本当にお疲れ様です。新人のIT担当者や、これからセキュリティの勉強を始める方にとって、覚えることがたくさんあって大変な時期ですよね。

今回は、システム開発において避けて通れない「パスワードの保存方法」について、一緒にしっかりと学んでいきましょう!

「パスワードって、データベースにそのまま保存しちゃダメなの?」
「よく聞く『ソルト』や『ストレッチング』って、一体なんだろう?」

そんな疑問を持ったことはありませんか?実はここを間違えると、万が一データが盗まれたときに、ユーザーの大切なアカウントが芋づる式に破られてしまうんです。身近な防犯の例えを交えながら、一歩ずつ分かりやすく解説していきますね。

—

1. なぜパスワードはそのまま保存してはいけないのか?

まずは、私たちの身の回りにある「鍵」の例えから考えてみましょう。

想像してみてください。あなたが家を出るとき、玄関の鍵を閉めますよね。もし、その鍵のコピーを町中の掲示板にペタッと貼り出していたとしたら……どうなるでしょうか?当然、誰でも簡単にあなたの家に侵入できてしまいますよね。

Webの世界でもこれと全く同じことが起き得ます。データベース(DB)にユーザーのパスワードをそのまま(平文で)保存している状態は、まさに「合鍵を掲示板に貼り出している」ようなものなんです。

どれだけ強固なファイアウォールを築いても、SQLインジェクションなどの脆弱性を突かれたり、内部の不正によってDBが丸ごと盗まれるリスクをゼロにすることはできません。だからこそ、「盗まれても中身が分からない状態」にしておく必要があるのです。

—

2. 「ハッシュ化」という名のシュレッダー

ここで登場するのが「ハッシュ関数(暗号学的ハッシュ関数)」です。代表的なものに SHA-256 などがあります。

ハッシュ化とは、パスワードを特定のアルゴリズムに通すことで、全く別の意味不明な文字列(ハッシュ値)に変換する仕組みです。例えば、password123 というパスワードをハッシュ化すると、ef92b778bafe771e89245b89ecbc08a44a4e166c06659911881f383d4473e94f のような長くてランダムな文字列に変わります。

ここで重要なポイントがあります。

  • 不可逆性(一方通行): ハッシュ化された文字列から、元のパスワードを逆算して復元することは(理論上)できません。シュレッダーにかけた書類を元に戻せないのと同じです。
  • 一意性: 同じパスワードを入力すれば、必ず同じハッシュ値になります。ログイン時には、ユーザーが入力したパスワードを再びハッシュ化し、DBに保存されているハッシュ値と一致するかどうかで本人確認を行います。

「おっ、これで完璧に安全だね!」……と言いたいところですが、実はハッシュ化だけでは現代の攻撃に対して少し心もとないのです。そこでお兄さん(お姉さん)たちエンジニアが頭を悩ませることになります。

—

3. 攻撃者の武器:「レインボーテーブル」と「総当たり攻撃」

攻撃者は、盗み出したハッシュ値を何とかして元のパスワードに戻そう(あるいは同じ意味になる文字列を探そう)とします。

ここで使われるのが、「ブルートフォース攻撃(総当たり攻撃)」や「レインボーテーブル」という手法です。

  • ブルートフォース攻撃: aaaaaa、aaaaab、aaaaac……と、ありとあらゆる組み合わせを片っ端から試してハッシュ化し、一致するものを探す力技です。
  • レインボーテーブル: よく使われるパスワードと、そのハッシュ値をあらかじめ大量に計算してリスト化した「辞書」のようなものです。これを使えば、一瞬でパスワードが特定されてしまいます。

特に、世の中の多くの人が password や 123456 といった「よくある弱いパスワード」を設定しているため、単純なハッシュ化だけだと、あっという間に解析されてしまうのです。

—

4. 鉄壁の守りを作る「ソルト(Salt)」と「ストレッチング」

ここで本日の主役である「ソルト」と「ストレッチング」が登場します。先ほどの家の鍵の例えをバージョンアップさせてみましょう。

ソルト(Salt:塩)とは?

ソルトとは、パスワードをハッシュ化する直前に、ランダムな別の文字列(おまけ)をくっつける技術のことです。料理にパラパラと塩を振るイメージからこう呼ばれています。

例えば、ユーザーAさんのパスワードが password123 で、ランダムなソルトが xyz789 だとしたら、これらを合体させた password123xyz789 をハッシュ化します。

【ソルトの効果】
もし同じパスワード password123 を使っているユーザーが2人(AさんとBさん)いたとしても、それぞれに違うソルト(Aさんは xyz789、Bさんは abc456)を混ぜていれば、生成されるハッシュ値は全く別のものになります。
これにより、先ほど説明した「レインボーテーブル(事前計算済みの辞書)」が全く役に立たなくなるのです!攻撃者はユーザーごとに一から計算をし直さなければならず、手間が何倍にも跳ね上がります。

ストレッチング(Stretching:引き延ばし)とは?

ストレッチングとは、ハッシュ化の計算を「あえて何千回、何万回と意図的に繰り返す」仕組みのことです。

通常のハッシュ関数(SHA-256 など)は、パソコンが高速に処理できるように設計されているため、1秒間に何億回もの計算ができてしまいます。これでは攻撃者が総当たり攻撃を行う際にも有利になってしまいますよね。

そこで、ストレッチングを取り入れることで、1回あたりのハッシュ計算にあえて「0.1秒」や「0.5秒」といったわずかな遅延を発生させます。

  • ログインする本人は「0.3秒待たされる」くらいなので、ユーザー体験(UX)的にはほとんど気になりません。
  • しかし、攻撃者が何億回もパスワードを推測しようとすると、膨大な時間がかかりすぎて現実的に攻撃が不可能になります。

—

5. 実務で使うべきアルゴリズム:Argon2 と bcrypt

では、実際にシステムを構築する際、どの技術を使えばいいのでしょうか?
昔は MD5 や SHA-256 をパスワードハッシュに使うことが推奨されていましたが、現在のセキュリティ基準では「パスワードハッシュ専用に作られたアルゴリズム」を使うことが絶対のルールとなっています。

現在、実務で安心して採用できる代表的なアルゴリズムは以下の2つです。

1. Argon2(アルゴンツー):
現在の最高峰(パスワードハッシュコンペティションの勝者)です。CPUだけでなくメモリも大量消費させることで、専用のハードウェア(GPUなど)を使った高速な総当たり攻撃を極めて困難にします。迷ったらまずはこれを選びましょう。
2. bcrypt(ビークリプト):
古くから使われており、現在でも非常に信頼性が高いアルゴリズムです。コストパラメータ(計算量)を自由に変更できるため、マシンの性能向上に合わせて負荷を調整できます。

—

6. 実装コード例(PHPによる password_hash の実践)

百聞は一見に如かず。PHPを例に、安全なパスワード保存と検証のコードを見てみましょう。PHPの標準関数である password_hash() は、内部で自動的に強力なソルトの生成とストレッチングを行ってくれます。

<?php
// ==========================================
// 1. ユーザー新規登録時(パスワードのハッシュ化)
// ==========================================

// ユーザーが入力した平文のパスワード(実際にはフォーム等から受け取る)
$plainPassword = 'User_Secret_Password_123!';

// password_hash() を使うことで、自動的に安全なソルト生成とストレッチングが行われます
// PASSWORD_DEFAULT を指定しておけば、PHPのバージョンアップに合わせた最新のアルゴリズムが使われます
$hashedPassword = password_hash($plainPassword, PASSWORD_DEFAULT);

// この $hashedPassword をデータベースに保存します(元のパスワードは絶対に保存しない!)
// 出力例: $2y$10$e0MYzXyjpJS7Pd0RVvHwHe... (ソルトやコスト情報もこの中に含まれます)
echo "DBに保存するハッシュ値: " . $hashedPassword . "\n";


// ==========================================
// 2. ログイン認証時(パスワードの検証)
// ==========================================

// ログイン画面でユーザーが入力したパスワード
$inputPassword = 'User_Secret_Password_123!';

// データベースから取得したハッシュ値(模擬的に上の変数を使用)
$storedHashFromDB = $hashedPassword;

// password_verify() を使って検証する
// 入力されたパスワードを、DBに眠るハッシュ値に含まれるソルトを使って同じ条件でハッシュ化し、比較します
if (password_verify($inputPassword, $storedHashFromDB)) {
    echo "認証成功!ログインを許可します。\n";
} else {
    echo "認証失敗:パスワードが間違っています。\n";
}
?>

コードのポイント

  • 開発者が自分でわざわざランダムなソルトの文字列を生成してデータベースの別カラムに保存する……といった面倒な実装をする必要はありません。password_hash() は、生成したソルトをハッシュ値の中に一緒に埋め込んでくれるため、検証時も自動でそれを読み取ってくれます。なんて親切なんでしょう!

—

まとめ

いかがでしたでしょうか? 今回のポイントをもう一度おさらいしておきましょう。

1. パスワードは絶対にそのまま保存しない(ハッシュ化する)。
2. ソルト(ランダムなオマケ)を混ぜることで、レインボーテーブル攻撃を防ぐ。
3. ストレッチング(意図的な計算の遅延)を行うことで、総当たり攻撃のスピードを極限まで落とす。
4. 自作の暗号アルゴリズムは使わず、PHPの password_hash() や、各言語の標準的な Argon2 / bcrypt ライブラリを信頼して使う。

セキュリティの仕組みは難しく見えますが、「泥棒から大切な家を守るための防犯グッズ」だと捉えると、すんなり理解しやすくなりますよね。

日々の開発の中で、「このデータは安全に守られているかな?」と少し立ち止まって考える習慣を大切にしながら、一歩ずつ頼もしいエンジニアへの階段を上っていきましょう!応援しています!

コメント

タイトルとURLをコピーしました