【入門編】 OWASP推奨のパスワードストレージ基準 – 暗号理論・認証基盤 & エンドポイントセキュリティ防御ガイド

こんにちは!セキュリティの世界へようこそ。
新米IT担当者の皆さんや、「セキュリティって何から学べばいいんだろう…」と不安を感じている開発者の方へ。今日は、Webアプリケーション開発で一番最初に、そして一番気をつけるべき「パスワードの安全な保存方法」についてお話ししますね。

世の中にはいろんなサイバー攻撃がありますが、データベースからユーザーのパスワードがごっそり盗まれる事故は、残念ながら後を絶ちません。今回は、世界的セキュリティ専門家集団である「OWASP(オワスプ)」が推奨する最新の基準をベースに、攻撃者の手口とそれを防ぐための「鉄壁の防犯術」を一緒に紐解いていきましょう!

難しい専門用語が出てきても置いてけぼりにしたりはしません。身近な「家の鍵」に例えて優しく解説していきますので、リラックスして読んでいってくださいね。

—

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

まずは、想像してみてください。あなたが自分の家の合鍵を作ったとき、その鍵に「〇〇マンション 101号室 山田様」とでかでかと油性ペンで書いて、近所の公園のベンチに置いておきますか?

「そんなバカな!絶対に盗まれて空き巣に入られる!」と思いますよね。

でもこれ、Webアプリケーションの裏側で、昔からよくやられてしまっている失敗なんです。
ユーザーが入力したパスワード(例:P@ssw0rd123)を、そのままデータベースに文字通り P@ssw0rd123 と保存してしまう。これは、「泥棒に部屋の合鍵をそのままプレゼントしている状態」と同じなんです。

万が一、データベースが不正アクセスを受けてハッカーに中身を見られてしまったとき、パスワードが生の文字(平文=ひらぶん)で保存されていたら、一瞬でゲームオーバーです。ユーザーは他のサービスでも同じパスワードを使い回していることが多いので、芋づる式に他のアカウントまで乗っ取り被害に遭ってしまいます。

だからこそ、パスワードは「そのまま保存してはいけない」という大原則があるのです。

—

2. ハッシュ関数(SHA-2, SHA-3)の限界と「パスワード専用」ではない理由

「じゃあ、パスワードを暗号化して保存すればいいんですね?」と思ったそこのあなた、素晴らしい着眼点です!しかし、ここで最初の大きな罠(落とし穴)があります。

セキュリティの世界には、データを別の形に変換する「暗号化(Encryption)」や、データを不可逆な別の文字列に変換する「ハッシュ関数(SHA-2やSHA-3など)」という技術があります。

例えば、SHA-256というハッシュ関数を使うと、どんな長さのパスワードでも、きれいさっぱり「一方向のめちゃくちゃな文字列(ハッシュ値)」に変換してくれます。

  • 入力: P@ssw0rd123
  • 出力: ef92b778bafe771e...(中略)...8c2

これは一見安全そうに見えます。なぜなら、出力されたハッシュ値から、元のパスワード P@ssw0rd123 を逆算して復元することは(理論上)不可能だからです。

なぜSHA-2やSHA-3をパスワードに使ってはいけないのか?

「おっ、じゃあSHA-256で決まりだね!」……ちょっと待ってください。ここがホワイトハッカーの腕の見せ所であり、初心者が一番間違えやすいポイントです。

SHA-2やSHA-3は、ファイルが改ざんされていないかチェックしたり、電子署名を作ったりするためには「超高速」で計算できるように設計されています。
しかし、この「超高速」というのが、パスワード保存においては致命的な弱点になるんです。

ハッカーたちは、世の中によくあるパスワード(123456 や password など)のハッシュ値を何兆通りもあらかじめ計算した「レインボーテーブル」という辞書を持っています。あるいは、グラフィックボード(GPU)の凄まじいパワーを使って、1秒間に何十億回もの総当たり攻撃(ブルートフォース攻撃)を仕掛けます。

SHA-2のような高速なアルゴリズムだと、ハッカーのパソコンは1秒間に数億〜数十億回もパスワードの推測を試せます。これでは、どんなに複雑なパスワードを作っても、総当たりで瞬く間に破られてしまうのです。

—

3. OWASPが推奨する「現代のパスワードストレージ基準」

では、私たちはどうやってパスワードを守ればいいのでしょうか?
ここで登場するのが、世界基準のセキュリティガイドラインである OWASP ASVS (Application Security Verification Standard) や、OWASP Password Cheat Sheet が推奨する現代の標準規格です。

OWASPは、パスワードを安全に保存するために以下の3つの要素を絶対条件として挙げています。

1. ソルト(Salt)の付与: パスワードごとにランダムな文字列を混ぜ合わせる。
2. ストレッチング(Stretching): 計算をわざと何万回も遅く(重く)して、総当たり攻撃を物理的に不可能にする。
3. 専用アルゴリズムの使用: Argon2id、bcrypt、PBKDF2 のいずれかを使用する(SHA-2やMD5は使わない!)。

それぞれの仕組みを、身近な防犯に例えて詳しく見ていきましょう。

① ソルト(塩)とは? —— 「合鍵に一工夫加える」

みんなが同じ鍵の形(よくあるパスワード password123 など)を使っていたら、泥棒は一つの合鍵の型を作れば、全員の家に入り放題になってしまいますよね。

そこで、家(ユーザー)ごとにランダムな文字列(これをソルトと呼びます)をパスワードの脇にくっつけてからハッシュ化します。
例えば、ユーザーAには salt_abc、ユーザーBには salt_xyz を混ぜ合わせます。

これによって、たとえユーザーAとユーザーBが全く同じパスワード(password123)を設定していたとしても、生成されるハッシュ値は全く別のものになります。ハッカーが辞書攻撃を使って一つのパスワードを破ったとしても、他のユーザーのパスワードまで一網打尽にすることはできなくなるのです。

② ストレッチングとは? —— 「金庫のダイヤルをあえて重くする」

先ほど、「SHA-2は高速すぎて危ない」とお話しました。そこで、現代のパスワード用アルゴリズムは、あわざと計算処理を何万回もループさせます(これをストレッチングと言います)。

パソコンにとっては「わざと重い処理をさせる」ことで、1回パスワードを検証するのにあえて0.1秒や0.2秒といった「絶妙な待ち時間」を発生させます。
正当なユーザーがログインするときには「0.2秒待つくらい何ともない」ですが、ハッカーが100万回パスワードを総当たりしようとすると、何十時間・何百年という膨大な時間がかかるようになります。つまり、「計算コストの暴力で攻撃者の心を折る」のがストレッチングの狙いです。

—

4. 【実務向けコード例】PHPとNode.jsで学ぶ安全な実装

理屈が分かったところで、次は実務でどう書くのかを見ていきましょう。
幸いなことに、現代の主要なプログラミング言語には、ソルトの生成もストレッチングも全部自動でやってくれる「神機能」が標準で用意されています。私たちが複雑なアルゴリズムの数学を気にする必要はありません。

実装例1: PHPの場合(password_hash関数を使う)

PHPでは、標準関数である password_hash() を使うだけで、内部で自動的に安全なアルゴリズム(現在は主に bcrypt や Argon2i/Argon2id)を選び、ランダムなソルトの生成とストレッチングを勝手に行ってくれます。

<?php
// ユーザーが新規登録画面で入力したパスワード(平文)
$plainPassword = 'MySecurePassword123!';

// 【安全なパスワードハッシュ化】
// password_hash関数を使うだけで、自動的にソルト生成とストレッチングが行われます。
// 第2引数には、現在推奨されている PASSWORD_DEFAULT を指定します。
$hashedPassword = password_hash($plainPassword, PASSWORD_DEFAULT);

// データベースには、この $hashedPassword のみを保存します!
// パスワードの生データ ($plainPassword) は絶対にデータベースに保存してはいけません。
echo "データベースに保存する値: " . $hashedPassword . "\n";


// --- 【ログイン時の検証処理】 ---
$inputFromUser = 'MySecurePassword123!'; // ログイン画面で入力されたパスワード

// password_verify関数で、保存されているハッシュ値と入力された平文を安全に比較します。
if (password_verify($inputFromUser, $hashedPassword)) {
    echo "ログイン成功!ようこそ!\n";
} else {
    echo "パスワードが間違っています。\n";
}
?>

実装例2: Node.js (Express等) の場合(bcrypt ライブラリを使う)

Node.js環境であれば、コミュニティで広く信頼されている bcrypt ライブラリを使うのが定番です。ここでもコストパラメータ(ストレッチングの強さ)を適切に設定します。

const bcrypt = require('bcrypt');

async function handleUserRegistration() {
    const plainPassword = 'MySecurePassword123!';
    
    // ソルトを生成する際のコスト係数(ワークファクター)。
    // 数値が大きいほどストレッチングの回数が増え、安全になりますが処理も重くなります。
    // OWASPや現在の標準では 12 以上が推奨されています。
    const saltRounds = 12;

    try {
        // 【安全なハッシュ化】
        // bcryptが自動でランダムソルトを生成し、ストレッチング込みでハッシュ化してくれます。
        const hashedPassword = await bcrypt.hash(plainPassword, saltRounds);

        console.log("データベースに保存する値:", hashedPassword);

        // --- 【ログイン時の検証処理】 ---
        const inputFromUser = 'MySecurePassword123!';

        // bcrypt.compare を使えば、タイミング攻撃(処理時間の差を狙う攻撃)を防ぎつつ安全に比較できます。
        const match = await bcrypt.compare(inputFromUser, hashedPassword);

        if (match) {
            console.log("ログイン成功!");
        } else {
            console.log("パスワードが違います。");
        }

    } catch (error) {
        console.error("エラーが発生しました:", error);
    }
}

handleUserRegistration();

どちらのコードも、非常にシンプルですよね。
自前で「独自の暗号アルゴリズムを作ろう」なんて絶対に考えてはいけません。セキュリティの世界では「車輪の再発明(すでに証明された安全な仕組みを使わずに自分で新しく作ること)」は最大のタブーです。必ず言語が提供している標準の仕組みや、信頼されたライブラリを使いましょう。

—

5. まとめとこれからの第一歩

ここまで、OWASPが推奨するパスワードストレージの基準について、仕組みや実装例を交えてお話ししてきました。

  • パスワードは絶対に生(平文)で保存しない!
  • SHA-2やSHA-3は「高速すぎる」のでパスワード保存には使わない!
  • 「ソルト」と「ストレッチング」を組み合わせた専用のアルゴリズム(Argon2id や bcrypt)を使う!
  • 各言語の標準関数や信頼できるライブラリ (password_hash や bcrypt など) に処理を丸投げする!

セキュリティは、一度学んで終わりではなく、日々進化する技術や攻撃手法に合わせてアップデートしていく泥臭い分野でもあります。でも、今日学んだこの基本の「パスワードの保存ルール」さえしっかりと守っていれば、あなたの手掛けるWebアプリケーションは、万が一データベースが破られたとしても、ユーザーのパスワードを守り抜く強力な盾を手に入れたことになります。

「一歩ずつ対策を学んでいきましょう!」
焦らず、一つひとつの仕組みを理解しながら、安全で信頼されるエンジニアへの階段を一緒に登っていきましょうね。それでは、また次回のセキュリティ解説でお会いしましょう!

コメント

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