CyberFix Note
セキュアコーディング

パスワードはどう保存すべきか。ハッシュ化とソルトとストレッチングの基礎

対象の目安: Webアプリ開発者 / 実務

ソウ攻撃・脆弱性リサーチ担当
・ 約15分で読めます
パスワードはどう保存すべきか。ハッシュ化とソルトとストレッチングの基礎

パスワードの保存は、Webアプリのなかでも事故の影響がとりわけ大きい部分です。データベースが漏えいしたとき、保存の仕方ひとつで「ログイン情報が一気に悪用される」のか「攻撃者が時間と費用をかけても多くは解けない」のかが分かれます。利用者は同じパスワードを複数のサービスで使い回していることが多く、ひとつのサービスからパスワードが漏れると、別のサービスへの侵入(パスワードリスト攻撃)にもつながります。だからこそ、保存方法は最初に正しく設計しておく価値があります。

この記事では、パスワードをどう保存すべきかを、平文保存や単純なハッシュがなぜ危険なのかという仕組みから順に整理します。ソルトがレインボーテーブルを無効化する理屈、ストレッチング(反復)が何のためにあるのか、現行で推奨されるアルゴリズムは何か、ペッパーはどこに位置づくのかまで、OWASPとNISTの記載に沿って説明します。

先に結論を述べます。現代のパスワード保存は「ソルトを含み、計算コストを意図的に高めた専用のパスワードハッシュ関数」を、自前で発明せず実績ある実装で使う、という一点に集約されます。以降はその理由を分解していきます。

平文保存がなぜ論外なのか

パスワードを平文(暗号化もハッシュ化もしない、そのままの文字列)でデータベースに保存すると、データベースが漏れた瞬間に全利用者のパスワードがそのまま攻撃者の手に渡ります。SQLインジェクションやバックアップの流出、内部不正など、データベースの中身が外に出る経路は複数あります。平文保存はそのどれが起きても即座に全アカウント突破に直結します。

ここで押さえておきたいのは、サービス側はログイン時にパスワードそのものを知る必要がない、という点です。利用者が入力したパスワードが「登録時のものと一致するか」を確かめられればよく、そのためには元のパスワードを復元できない形で保存しておけば足ります。この「復元できないが照合はできる」を実現するのがハッシュ関数です。

ハッシュ関数は、入力から固定長の値(ハッシュ値)を一方向に計算します。同じ入力からは必ず同じハッシュ値が出る一方、ハッシュ値から元の入力を逆算するのは困難です。保存するのはこのハッシュ値で、ログイン時には入力されたパスワードを同じ関数で計算し、保存済みの値と突き合わせます。元のパスワードを保存しないので、データベースが漏れても直ちに平文が手に入るわけではなくなります。

単純な高速ハッシュだけでは守れない理由

「ハッシュ化してあれば安全」とは言いきれません。問題は、どのハッシュ関数を使うかです。MD5やSHA-1、SHA-256といった汎用のハッシュ関数は、もともと大量のデータを高速に処理するために設計されています。この高速さが、パスワード保存では弱点になります。

攻撃者がハッシュ値の一覧を入手すると、候補のパスワードを片端から同じ関数でハッシュ化し、一致するものを探す総当たり攻撃や辞書攻撃を仕掛けられます。汎用ハッシュは1秒間に膨大な回数を計算でき、とくにGPUを使うと並列処理でさらに速くなります。よく使われるパスワードや短いパスワードは、この方法で短時間に解かれてしまいます。OWASPも、SHA-256のような高速ハッシュは攻撃者が短時間に大量の推測を行えるためパスワード保存には適さない、と述べています。

もうひとつの弱点がレインボーテーブルです。これは事前計算型攻撃の一種で、「よく使われるパスワードと、そのハッシュ値」をあらかじめ計算しておく方式を、ハッシュ関数と還元関数のチェーンとして圧縮し、保管量を抑えて表にしたものです。仕組みの細部はともかく、要点は「事前に計算しておいた対応表を使い回せる」ことにあります。ソルトなしの単純なハッシュは、同じパスワードなら誰でも同じハッシュ値になるため、漏れたハッシュ値を表と突き合わせるだけで元のパスワードが判明します。事前計算を一度作っておけば、以後は照合するだけで済むのが攻撃者にとっての旨みです。この事前計算を無力化するのが、次に述べるソルトです。

ソルトがレインボーテーブルを無効化する仕組み

ソルトは、パスワードをハッシュ化する前に加える、利用者ごとに固有のランダムな値です。パスワードとソルトを連結してからハッシュ化することで、たとえ二人の利用者が同じパスワードを使っていても、ソルトが異なれば保存されるハッシュ値は別物になります。

この性質がレインボーテーブルを無効化します。事前計算の表は「ソルトなしのパスワード」に対して作られているため、利用者ごとにソルトが違うと、あらかじめ作った表をそのまま使い回せなくなり、事前計算の旨みが消えます。さらに、ハッシュ値が利用者ごとに違うので、ひとつの推測候補を全利用者に一度に照合してまとめて解く、という使い回しも効きません。攻撃者はソルトごとに計算をやり直す必要があり、攻撃の総コストはおおむね対象とするハッシュの数に比例して増えます。ただし、攻撃者がGPUで複数のハッシュを並列に計算すること自体は依然として可能で、ソルトはあくまで「事前計算の使い回し」を断つ仕組みである点は押さえておきます。

ソルトは秘密にする必要はなく、ハッシュ値と並べて保存します。目的は秘匿ではなく「事前計算した表の使い回しを断つ」ことだからです。NIST SP 800-63B-4は、ソルトは32ビット以上の長さとし、ソルト値と結果のハッシュ値の両方をパスワードごとに保存することを求めています。実装の面では、後述する現代のパスワードハッシュ関数の多くがソルトを内部で自動生成して結果に埋め込むため、開発者がソルトを手作業で管理する必要は通常ありません。OWASPもこの点に触れ、開発者が自前でソルト生成を扱う必要は基本的にないとしています。

あわせて読みたい

安全なパスワードの作り方と管理。長さ優先・使い回し回避をNISTの考え方から解説

ストレッチングとメモリハード関数

ソルトは事前計算した表の使い回しを防ぎますが、利用者ひとりに狙いを絞ったオフライン総当たり自体を止めるものではありません。そこで効いてくるのが、推測1回あたりの計算コストを意図的に高くするストレッチング(鍵ストレッチング)です。

ストレッチングは、1回の検証にあえてコストをかけることで、攻撃側の総当たり速度を落とします。コストの上げ方は反復回数だけではありません。ハッシュ計算を何千回も何万回も反復する方式に加え、メモリ使用量や並列度といったパラメータでも1回あたりのコストを調整できます。正規のログインでは1回の検証に少し時間がかかるだけですが、攻撃者が総当たりで何十億回も試そうとすると、その1回あたりのコストの分だけ全体の所要時間が膨らみます。これらは調整可能なパラメータ(コストファクタ、ワークファクタ)として設定でき、計算機が速くなるのに合わせて将来引き上げられます。NIST SP 800-63B-4も、コストファクタは検証側の性能を損なわない範囲でできるだけ高くし、計算能力の向上に応じて時間とともに上げていくべきだとしています。

反復だけでなくメモリ消費でコストを上げる関数もあります。Argon2idやscryptは、計算時に大きなメモリを必要とするメモリハード関数です。GPUや専用ハードウェア(ASIC)は並列計算は得意でも、各並列処理に大量のメモリを割り当てるのは苦手なため、メモリを多く使わせると攻撃側のハードウェアの優位が削がれます。これが、現行で推奨されるアルゴリズムにメモリハード関数が並ぶ理由です。

現行で推奨されるアルゴリズム

どの関数を使うべきかは、OWASPのPassword Storage Cheat Sheetが順序立てて示しています。執筆時点のOWASPの推奨は次のとおりです。

アルゴリズム位置づけ(執筆時点のOWASP)
Argon2id新規システムでの第一候補
scryptArgon2idが使えない場合の選択肢
bcryptArgon2idやscryptが使えないレガシーシステム向け
PBKDF2FIPS-140準拠が求められる環境向け

この具体的なアルゴリズム選定と数値は、執筆時点のOWASPの推奨です。パラメータも執筆時点のCheat Sheetに具体値が示されています。Argon2idについては、メモリと反復回数と並列度の組み合わせとして複数の等価な設定が挙げられており、たとえばm=19456(19 MiB)、t=2p=1が示されています。Cheat Sheetはこれを最小要件として、19 MiBのメモリ、反復回数2、並列度1と記載しています。より多くのメモリを使える場合のm=47104(46 MiB)、t=1p=1なども併記されています。bcryptについては、ワークファクタは検証サーバの性能が許す範囲でできるだけ大きくし、最小で10とされています。PBKDF2については、PBKDF2-HMAC-SHA256で600,000回の反復が推奨値として示されています。

bcryptには入力長の制限という固有の注意点があります。bcryptが扱える入力は最大72バイトで、それを超えた分は実装によっては切り捨てられることがあります。日本語を含むパスワードは1文字あたりのバイト数が大きく、文字数の見た目より早く72バイトに達しうるため、長いパスフレーズを安全に扱いたい場合は影響を受けます。bcryptを使うなら、利用するライブラリが超過分をどう扱うか(切り捨てるのか、拒否するのか、事前にハッシュして詰めるのか)を確認しておきます。

これらの数値は計算機の性能向上に合わせて改定されるため、実装時には必ず最新のCheat Sheet本文で現行値を確認してください。ここに挙げた値は執筆時点のOWASPの記載にもとづくものです。

NISTの要件は、アルゴリズムの順位づけとは別の角度から枠を定めています。NIST SP 800-63B-4は、パスワード(記憶シークレット)はソルトを付与し、承認された一方向の鍵導出関数(パスワードハッシュ)で保存すること、ソルト値と結果のハッシュ値の両方を保存すること、ソルトは32ビット以上とすることを求めています。コストファクタは検証側の性能を損なわない範囲でできるだけ高くすべきだとも述べています。どの具体的なアルゴリズムを第一候補とするかは、ここでは執筆時点のOWASPの推奨に拠っています。なお、FIPSや連邦政府の要件に適合させる必要がある場合は、許容されるアルゴリズムが別途定まるため、適用対象の最新の要件を個別に確認してください。

アルゴリズムの優先順位(Argon2id, scrypt, bcrypt, PBKDF2)と、Argon2idの最小要件(19 MiB, 反復2, 並列度1)やm=47104(46 MiB)などの設定例、bcryptのワークファクタ最小値10とbcryptの72バイト制限、PBKDF2-HMAC-SHA256で600,000回、高速ハッシュがパスワード保存に不向きである旨、ペッパーをシークレット管理やHSMで分離保管する旨は、OWASP Password Storage Cheat Sheetの執筆時点の記載にもとづきます。これらのパラメータは改定されるため、実装時に最新版で再確認してください。ソルト付き一方向の鍵導出関数での保存、ソルトとハッシュの両方を保存、ソルトは32ビット以上、コストファクタを検証性能の範囲で高く保つこと、はNIST SP 800-63B-4の記述によります。Argon2やbcryptを具体的に第一候補とする選定はOWASPの推奨であり、NISTの記述に帰属させていません。IPAの「安全なウェブサイトの作り方」はパスワードの取扱いを対策項目として扱う国内資料として参照しており、ハッシュ方式やパラメータの具体的な根拠は現行のOWASP等に拠っています。

ペッパーの位置づけと限界

ソルトに加えて、ペッパーという追加の層を設けることがあります。ペッパーは、すべての利用者に共通して使う秘密の値で、データベースとは別の場所で管理する点がソルトと違います。ハッシュ化の際にパスワードへ加える、あるいはハッシュ結果をペッパーを鍵にして処理する、といった形で使います。

ペッパーの狙いは、データベースだけが漏れた状況への上乗せ防御です。ソルトはハッシュ値と一緒に保存されるため、データベースが丸ごと漏れればソルトも攻撃者に渡ります。一方ペッパーはデータベースの外、たとえばシークレット管理の仕組みやHSM(ハードウェアセキュリティモジュール)に置くので、SQLインジェクションやデータベースバックアップの流出だけではペッパーが手に入らず、攻撃者はハッシュを解けません。OWASPも、ペッパーはシークレットであり、シークレット管理の仕組みやHSMにパスワードのデータベースとは分けて保管すべきだとしています。

ただしペッパーは万能ではありません。効果はペッパーがデータベースと別に守られていることが前提で、アプリケーションサーバまで侵害されてペッパーごと漏れれば、上乗せ分の防御は失われます。さらに、ペッパーが漏れたときの差し替えはパスワードの再設定を伴うなど、運用の負担も生じます。ペッパーはあくまで鍵管理が前提の追加層であり、ソルトやストレッチングの代わりにはならない、と理解しておくのが妥当です。ペッパーのような秘密値の保管そのものは、シークレット管理の設計に踏み込む話になります。

あわせて読みたい

シークレット管理の実務。APIキー・認証情報をハードコードせず、Vaultやマネージドサービスで守りローテーションする

実装で踏み外さないための勘所

仕組みを理解したうえで、実装側の判断を整理します。

パスワード保存の実装チェックリスト

  • パスワードを平文で保存していないか。保存しているのは検証用のハッシュ値か
  • MD5やSHA-1やSHA-256などの高速ハッシュ単体を、パスワード保存に使っていないか
  • Argon2idなど現行で推奨されるパスワードハッシュ関数を、実績ある実装やライブラリ経由で使っているか
  • ソルトが利用者ごとに固有のランダム値になっているか(多くのライブラリは自動で行う)
  • 計算コスト(メモリ、反復回数、ワークファクタ)を現行の推奨に沿って設定しているか
  • ハッシュ値にアルゴリズムやパラメータの情報が含まれ、将来の方式移行ができる形になっているか
  • ペッパーを使う場合、データベースとは別の場所(シークレット管理やHSM)で鍵を管理しているか

最初の原則は、ハッシュ方式を自前で発明しないことです。反復処理やソルトの扱いを独自実装すると、漏れや取り違えで弱くなりがちです。広く検証された実装やライブラリ、あるいは認証を担うIDプロバイダに任せるのが堅実です。多くのライブラリは、ソルトの自動生成と、アルゴリズム名やパラメータを含んだ形式での保存までまとめて面倒を見ます。

次に、移行できる設計にしておくことです。推奨アルゴリズムやパラメータは時間とともに更新されます。ハッシュ値の中に使用したアルゴリズムとパラメータを記録しておけば、ログイン成功時にその場で新しい方式へ再ハッシュする、といった段階的な移行が可能になります。アルゴリズムを固定で埋め込んでしまうと、後からの更新が難しくなります。

そして、保存方法だけで総当たりに勝とうとしないことです。ストレッチングはオフラインでの解読を遅らせますが、オンラインで何度もログインを試みる攻撃には、レート制限やアカウントロック、多要素認証(MFA)といった別の層が必要です。保存方法と運用側の対策は役割が違うので、重ねて備えます。オンラインでの総当たりへの具体的な備えは個別記事で扱っています。

あわせて読みたい

総当たり攻撃と使い回しの悪用から見るログインの守り方

なお、IPAの「安全なウェブサイトの作り方」も、ウェブサイトにおけるパスワードの取扱いを対策項目のひとつとして取り上げています。国内の一般的な資料として方針を把握する出発点には使えますが、版が新しいとは限らないため、ハッシュ方式や具体的なパラメータの根拠は現行のOWASP等に従ってください。

パスワードの保存は、突き詰めると「元のパスワードを保存せず、ソルトを含み、計算コストを意図的に高めた専用のハッシュ関数を、実績ある実装で使う」という形に収れんします。平文保存は漏えい即全アカウント突破につながるため論外で、高速ハッシュ単体は総当たりやレインボーテーブルに弱く、ソルトが事前計算した表の使い回しを断ち、ストレッチングやメモリハード関数が解読の速度を落とします。NIST SP 800-63B-4はソルト付きの一方向の鍵導出関数での保存(ソルト32ビット以上、ソルトとハッシュの両方を保存)を求め、具体的なアルゴリズム選定では執筆時点のOWASPがArgon2idを第一候補に挙げています。ペッパーは鍵管理を前提にした上乗せの層であって、基本の代わりにはなりません。自前発明を避け、将来の方式移行ができる形で実績ある実装に委ねることが、この領域でつまずかないための足場になります。

出典・参考

この記事をシェア

関連する記事

防御・ハードニング

総当たり攻撃と使い回しの悪用から見るログインの守り方

総当たり攻撃が成立する仕組みと、辞書攻撃やパスワードスプレーという派生、他サービスから漏れた認証情報を試すクレデンシャルスタッフィングとの違いを整理し、長いパスフレーズや多要素認証、レート制限まで、個人と運用側それぞれの守り方をNISTやOWASP、IPAの一次情報に沿って入門者向けに説明します。