【入門編】 パスワードハッシュ化におけるソルトとストレッチングの重要性 – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

こんにちは!セキュリティの世界へようこそ。
新人のIT担当者や、これからアプリ開発をバリバリやっていこうという開発者の皆さん、「ユーザーのパスワードって、どうやって安全に保存すればいいんだろう?」と疑問に思ったことはありませんか?

データベースを覗いたとき、パスワードがそのまま文字で見えていたら…それはもう、鍵をかけずに玄関のドアを開けっ放しにしているようなものです。今回は、泥棒(攻撃者)の手口から私たちのシステムを守るための「ソルト」と「ストレッチング」、そして最強の金庫である「Argon2idやbcrypt」について、身近な防犯の例えを交えながら一歩ずつ学んでいきましょう!

—

1. 泥棒は「合鍵の束」と「超高速の頭脳」を持っている

まず、私たちが相手にする攻撃者がどのような方法でパスワードを破ろうとするのか、少し裏側の話をしますね。

よく映画のように、ハッカーが黒い画面に向かってカタカタとキーボードを叩き、一瞬でパスワードを暴き出すシーンを見たことはありませんか? あれの現実版の一つが「レインボーテーブル攻撃」や「ブルートフォース(総当たり)攻撃」と呼ばれるものです。

これを身近な例えで考えてみましょう。
あなたの家の鍵(パスワード)が「123456」だったとします。泥棒があらかじめ「よく使われる鍵の形」を何億通りも用意した巨大なリスト(これがレインボーテーブルです)を持ってあなたの家のドアの前にやってきました。

昔の古い方式(MD5やSHA1という名前の暗号化技術)を使っていると、泥棒はその巨大なリストと、あなたの家から盗み出した「鍵の暗号データ」を照らし合わせるだけで、ほんの数秒であなたの本当の鍵の形を見つけ出してしまうのです。しかも、MD5やSHA1は「計算がものすごく速い」ため、泥棒にとってはパズルを解くような感覚で一瞬で突破できてしまいます。

「計算が速い」というのは普段のシステムでは良いことですが、パスワードの保護においては「泥棒にとっても計算が速すぎる=破られやすい」という致命的な弱点になってしまうのですね。

—

2. 泥棒の企みを打ち砕く「ソルト(塩)」の魔法

では、どうすればこの泥棒のズルいリスト攻撃を防げるでしょうか? ここで登場するのが「ソルト(Salt)」です。

ソルトとは、日本語で「塩」ですね。料理に塩を振るように、パスワードを保存するときにユーザー一人ひとり違う「ランダムな文字列(塩)」を混ぜ合わせるテクニックです。

防犯に例えてみましょう。
もし、アパートの住人全員が「同じマスターキー」を使っていたら、泥棒は1つの合鍵を作るだけでアパートの全室に入り放題になってしまいますよね。これが「ソルトがない状態」です。

そこで、部屋ごとに少しずつ鍵の溝の形を変え、さらに「どの部屋にどんな追加パーツ(ソルト)を組み合わせたか」というレシピをメモしておきます。
泥棒がデータベースを盗み見ても、ユーザーAさんとユーザーBさんでは混ざっている「ソルト」が違うため、あらかじめ用意した「全員共通の合鍵リスト(レインボーテーブル)」が全く役に立たなくなるのです。ユーザーごとに計算結果がバラバラになるため、泥棒は一人ずつ地道に解読するしかなくなり、攻撃の難易度を跳ね上げることができます。

—

3. さらにドアを重くする「ストレッチング」と最新の金庫

ソルトでリスト攻撃を防げるようになりましたが、それだけではまだ少し不安です。なぜなら、もし泥棒が特定の1人のパスワードに狙いを定め、猛烈な勢いで総当たり攻撃を仕掛けてきたら、計算が速い古いシステムではいつか破られてしまうからです。

そこで登場するのが「ストレッチング(延伸)」です。
これは、パスワードのハッシュ化(暗号のような変換)の計算を、あえて何万回、何十万回と意図的に何重にも繰り返させる仕組みのことです。

防犯で例えるなら、ドアを開けるのに「鍵を差し込んで、10万回パズルを解かないと開かない頑丈な仕掛け」を作るようなものです。
正当なユーザーがログインするときは、わずかコンマ数秒(例:0.1秒)待たされるだけなので全く問題ありません。しかし、1秒間に何百万回も試したい泥棒側からすると、1回計算するのに時間がかかりすぎてしまい、攻撃に何年、何十年という途方もない時間がかかるようになります。これこそが、泥棒を諦めさせる最大の防御になります。

そして、この「ソルト」と「ストレッチング」を最高峰のレベルで組み合わせてくれているのが、現代のセキュリティ標準である「bcrypt(ビークリプト)」や「Argon2id(アルゴンツーアイディー)」というパスワードハッシュアルゴリズムです。

—

4. 実装してみよう!PHPでの安全なパスワード処理

百聞は一見に如かず。実際に開発現場でどのように実装するのか、PHPを例に見ていきましょう。一歩ずつ、安全なコードを書いていきますね。

古いシステムで見かける md5() や sha1() は絶対に使わないでください。PHPには、自動的に強力なソルトの生成とストレッチングを行ってくれる素晴らしい標準関数が用意されています。

パスワードを安全にハッシュ化して保存するコード

新規登録時やパスワード変更時は、password_hash() 関数を使います。この関数を使うだけで、内部で自動的に安全なソルトが生成され、計算コスト(ストレッチング)も適切に処理されます。

<?php
// ユーザーが入力したプレーンテキストのパスワード(実際にはフォーム等から受け取ります)
$user_input_password = "MySecurePassword123!";

// password_hash() を使って安全にハッシュ化する
// PASSWORD_DEFAULT を指定しておけば、PHPのバージョンアップに合わせて
// その時代における最も推奨されるアルゴリズム(現在はbcryptやArgon2idなど)が自動適用されます。
$hashed_password = password_hash($user_input_password, PASSWORD_DEFAULT);

// データベースには、この $hashed_password を保存します。
// パスワードの原典(平文)は絶対にデータベースに保存してはいけません!
echo "データベースに保存するハッシュ値: " . $hashed_password;
?>

ログイン時にパスワードを検証するコード

ユーザーがログインしようとしたとき、入力されたパスワードが保存されているハッシュと一致するかどうかを確認するには、password_verify() 関数を使います。

<?php
// ログインフォームから送信されてきたパスワード
$input_password_from_form = "MySecurePassword123!";

// あらかじめデータベースから取得しておいた、安全にハッシュ化された文字列
// (実際にはDBからSELECTした値が入ります)
$stored_hash_from_db = "$2y$10$abcdefghijklmnopqrstuv... (データベース内のハッシュ値)";

// password_verify() で検証を行う
// この関数は、ハッシュ値の中に含まれているソルトやコストの情報を自動で読み取り、
// 入力されたパスワードを同じ条件でハッシュ化して安全に比較してくれます。
if (password_verify($input_password_from_form, $stored_hash_from_db)) {
    // 認証成功!
    echo "ログイン成功です!ようこそお越しくださいました。";
} else {
    // 認証失敗
    echo "パスワードまたはユーザー名が間違っています。";
}
?>

—

5. より強固なセキュリティを目指す「Argon2id」のパラメータ設定

もし、さらに高いセキュリティレベルが求められるシステム(金融系や機密性の高い管理画面など)を構築する場合は、最新かつ最強のアルゴリズムである Argon2id を明示的に指定することもできます。

PHP 7.3以降では、以下のようにコストパラメータを細かくチューニングして設定することが可能です。

<?php
$password = "SuperSecretPassword!";

// Argon2id を使用し、メモリ使用量や計算コストを明示的に指定するオプション配列
$options = [
    'memory_cost' => 65536, // 使用するメモリ量 (Kibibytes)。ここでは 64MB を指定してメモリ消費型の攻撃を防ぎます。
    'time_cost'   => 4,     // 計算の繰り返し回数(ストレッチングの度合い)。処理を重くして総当たりを防ぎます。
    'threads'     => 2,     // 並列処理のスレッド数。サーバーのCPUコアに合わせて調整します。
];

// Argon2id でハッシュ化を実行
$secure_hash = password_hash($password, PASSWORD_ARGON2ID, $options);

echo "Argon2idで生成されたハッシュ: " . $secure_hash;
?>

このように、フレームワークや言語が提供する標準の関数を正しく使うだけで、複雑な暗号化の数学的知識を深く持っていなくても、世界トップクラスの堅牢なパスワード管理を実現することができます。

—

まとめ

今回は、パスワードの安全な保存に欠かせない「ソルト」と「ストレッチング」、そしてMD5などの古い技術の危険性について紐解いてきました。

  • MD5やSHA1は計算が速すぎるため、泥棒のリスト攻撃(レインボーテーブル)で一瞬で破られる
  • ソルト(塩)を混ぜることで、ユーザーごとに異なる合金を作り、一括での解析を防ぐ
  • ストレッチング(延伸)で計算にあえて時間をかけさせ、総当たり攻撃を現実的ではないほど遅くする
  • 現場では自作の暗号化をせず、PHPの password_hash() / password_verify()(bcryptやArgon2id)を必ず利用する

セキュリティの基本は「面倒くさいを排除し、仕組みで守る」ことです。ぜひ今日の知識をあなたのプロジェクトや日々の開発に活かして、安全で信頼されるシステムを一緒に作っていきましょう!一歩ずつ、確実にスキルアップしていきましょうね。

コメント

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