パスワードの保存は、ソフトウェアの世界で「本当に正しい答え」が存在する数少ない領域のひとつです。その答えはすでに公開されていて、継続的に更新されていて、しかも無料で読めます。それにもかかわらず、これほど繰り返し間違えられている領域も珍しくありません。理由は単純で、いま間違いとされている答えのほとんどが、かつてはたしかに正しかったからです。そして正しかった時期を過ぎたあとも、誰もその判断を見直しませんでした。

失敗の中身が奇抜であることは、まずありません。2012年に妥当だった助言に沿って2016年に組まれた仕組みが、いまも動き続け、いまもログインを受け付けている。それだけの話です。そのハッシュ処理を書いた本人が会社を去って以来、誰もその関数を開いていない。監査の対象になったこともなく、障害を起こしたこともないので、誰の課題一覧にも載っていません。

ひとつだけ持ち帰るなら、Argon2idを使ってください。それが無理ならbcryptです。 この記事の残りはすべて細部にすぎません。SHA-256のような汎用ハッシュは、何回繰り返し適用したとしてもパスワード用のハッシュにはなりません。その速さは設計目標として意図的に与えられたものであり、そしてまさにその速さこそ、あなたのデータベースを手に入れた攻撃者から奪おうとしているものだからです。


パスワード保存の正しい形はひとつしかない

意図的に遅く、メモリを大量に必要とする関数で生成したハッシュを保存すること。パスワードごとに一意のランダムなソルトを添えること。そして自分の運用環境のハードウェアに合わせて調整したパラメーターを使うこと。この三つが揃って初めて意味を持ち、どれかひとつが欠けると残りの二つも効き目を失います。

OWASPのパスワード保存に関するガイダンスは、現時点の推奨値を明確な数値として示しています。原則ではなく数値として引用する価値があるので、そのまま表にします。

アルゴリズム推奨パラメーター使いどころ
Argon2idメモリ 19 MiB、2 回の反復、並列度 1第一の選択肢
scryptN=2^17 (128 MiB)、r=8、p=1Argon2idが使えない場合
bcryptワークファクター 10 以上、パスワードは 72 byte までレガシーな環境
PBKDF2-HMAC-SHA256600,000 回の反復FIPS-140準拠が要求される場合

この表について補足が二点あります。ひとつ目は、Argon2idの数値があくまで最小値だということです。メモリを増やして反復回数を減らす組み合わせは等価な取り引きとして扱えるので、47 MiB のメモリに反復 1 回という構成でも、基準値と同等の強度が得られます。自分の環境で許容できる応答時間から逆算して、どちらの軸を伸ばすかを決めてください。

ふたつ目は、bcryptの 72 byte という上限が、入力を拒否する検証ルールではなく、実際に起きる切り捨てだという点です。72 byte を超えた分は何の警告もなく無視されます。長いパスフレーズを受け付けている場合や、bcryptに渡す前に入力をあらかじめハッシュ化している場合には、これが現実の弱点になります。

なお、これらのパラメーターはハードウェアの進歩に合わせて改訂されていきます。実装するときは、この表ではなく一次情報を読んでください。

ソルトは省略できないが、それだけでは足りない

ソルトとは、それぞれのハッシュと並べて保存される、一意のランダムな値です。その役割は狭く、しかし重要です。同じパスワードを使っている二人の利用者が、異なるハッシュ値を持つようにすること。これだけです。この性質によって、あらかじめ計算しておいた総当たり用の対応表、いわゆるレインボーテーブルが役に立たなくなり、攻撃者がひとつのパスワードを解いただけで、それを共有していたすべてのアカウントをまとめて手に入れる事態も防げます。

一方で、ソルトが決してやってくれないことがあります。攻撃者の速度を落とすことです。あなたのデータベースを持っている攻撃者は、当然ソルトも持っています。ログインを検証するためには読み出せなければならない以上、秘密にはできないからです。したがって、高速なハッシュにソルトを添えただけの構成では、攻撃者が各パスワードを個別に、しかし極めて高い速度で攻撃することになります。それはまさに、あなたが避けようとしていた状況そのものです。

遅さはアルゴリズムそのものから来なければなりません。メモリを大量に消費する関数が存在する理由は、まさにそこにあります。計算資源を意図的に食いつぶすことで、専用ハードウェアを用意した攻撃者が得られる優位を、高速なハッシュを相手にする場合よりもはるかに小さく抑えるのです。

ペッパー、つまりすべてのパスワードに共通で適用しつつ、データベースの外側に保管しておく秘密の値は、本物の防御層をもう一枚足してくれます。データベースの内容が丸ごと流出しただけでは、それ以上先へ進めなくなるからです。ただし運用の負担も同時に増えます。ペッパーを更新するには次回ログイン時の再ハッシュが必要になりますし、失えばすべてのアカウントが締め出されます。価値の高いシステムでは支払う価値のある代償ですが、大半の用途では見送って構いません。

何年も生き延びてしまう誤り

ソルト付きの高速ハッシュ。 SHA-256にソルトを添えた構成は、一見すると責任ある実装に見えますが、そうではありません。速いこと自体が脆弱性です。

ハッシュではなく暗号化。 暗号化は設計上、元に戻せます。つまり鍵が存在するということであり、その鍵を手に入れた者はすべてのパスワードを平文で手に入れます。パスワードは決して復元可能であってはならず、検証可能であれば十分です。

一度決めたきり上げられないワークファクター。 2015年に選ばれたbcryptのコスト値は、2026年のハードウェアに対しては明らかに弱くなっています。次にログインが成功した時点で引き上げてください。その瞬間だけは平文が手元にあり、再ハッシュできます。

切り捨てや文字種の制限。 上限がおおよそ64文字を下回っていたり、特定の文字が禁止されていたりする場合、それはたいてい、パスワードの中身を気にする形で保存されているという合図です。中身を気にする必要はありません。どのみちハッシュ化して捨てるはずの値だからです。

時間差が漏れる比較。 検証には定数時間の比較を使わなければなりません。標準ライブラリの関数はそのように実装されていますが、自前で書いた等値判定はそうなっていません。

パスワードをログに残す。 エラーログ、リクエストのトレース、監視システムのいずれかに流れ込んだ時点で、データベースよりもアクセス制御の緩い場所が三つ増えたことになります。

既存システムを移行する

手元にないものを再ハッシュすることはできません。保持しているのはハッシュ値だけだからです。したがって現実的な方法はひとつ、ログインの瞬間に更新することです。

利用者の認証が成功したとき、その平文パスワードは一瞬だけメモリ上に存在します。まず古いハッシュに対して検証し、そのまま現行のアルゴリズムで新しいハッシュを計算して、保存済みの値を置き換えてください。数か月もあれば、稼働中のアカウントの大半は利用者に見える変化なしで移行が完了します。

どのアルゴリズムで保存されているかはレコードごとに記録し、新旧が共存できるようにしてください。そのうえで、残った古いハッシュを無効化して該当利用者にリセットを求める期日を決めます。弱いハッシュのまま眠っているアカウントこそ、攻撃者が最も欲しがる対象です。

近道として、古いハッシュを新しいアルゴリズムでもう一度包むのはやめてください。動作はします。しかし見た目だけが現代的になり、実際の防御の境界は弱いハッシュのまま残ります。

アルゴリズムより重要なこと

正しいハッシュ処理は、データベースが漏れたときにあなたを守ります。しかし、より頻度の高い攻撃、つまり他所で入手された本物のパスワードで誰かがそのままログインしてくる事態に対しては、何の効果もありません。

認証エンドポイントでのレート制限、多要素認証の提供、新しいパスワードを既知の流出リストと突き合わせる検査、そしてクレデンシャルスタッフィングの兆候に対する警報。これらはすべてその問題を扱うものであり、日々の運用という尺度で見れば、Argon2idとbcryptのどちらを選ぶかよりも重要です。APIセキュリティの対策も、まったく同じ系統に属する問題として整理できます。

どちらもやる価値があります。そして順序については正直であるべきです。ハッシュ処理は安価で、一度直せばそのまま効き続けるので、最初に片付けてしまってください。そのうえで、継続的に労力を注ぐべき先はログイン経路です。攻撃が実際に到達するのはそこだからです。Mecanikではアプリケーションセキュリティ分析の一環として、この両方を確認しています。



よくある質問

2026年に最適なパスワードハッシュアルゴリズムは何ですか? Argon2idです。最小でもメモリ 19 MiB、2 回の反復、並列度 1 を指定してください。利用できない場合は scrypt を N=2^17、r=8、p=1 で使う方法が代替となり、ワークファクター 10 以上の bcrypt はレガシーな環境に適しています。FIPS-140の要件がある場合は PBKDF2-HMAC-SHA256 を 600,000 回の反復で使ってください。

パスワードにソルトを付ければ十分ですか? いいえ。ソルトは同一のパスワードから異なるハッシュを生成させ、レインボーテーブルを無効化し、ひとつ解読されたパスワードがそれを共有する全アカウントを開けてしまう事態を防ぎます。しかし攻撃者の速度は落としません。ログインを検証するためにソルトは読み出せる必要があるからです。遅さは意図的にメモリを大量消費するアルゴリズムから得るしかありません。

なぜパスワードにSHA-256を使ってはいけないのですか? その速さが設計目標であり、速さこそ、あなたのデータベースを握った攻撃者から奪おうとしているものだからです。汎用のハッシュ関数は、何回適用しようと、またソルトを付けようと、パスワード用のハッシュにはなりません。

bcryptにパスワード長の上限はありますか? あります。72 byte です。それを超えた分は拒否されるのではなく、黙って切り捨てられます。長いパスフレーズを受け付けている場合や、bcryptに渡す前に入力をハッシュ化している場合には重要です。切り捨てを知らせるエラーは一切出ないからです。

利用者のパスワードなしでハッシュを更新するにはどうしますか? ログイン時に行います。利用者が認証する瞬間だけ平文が手元にあるので、古いハッシュで検証したうえで、ただちに現行のアルゴリズムで再ハッシュしてください。どのアルゴリズムで保存したかはレコードごとに記録して新旧を共存させ、残った古いハッシュを無効化して該当利用者にリセットを求める期日を決めておきます。