パスワード保存と、ハッシュ化・ソルト化・ストレッチング。

パスワード保存と、ハッシュ化・ソルト化・ストレッチング。の説明画像

AIで実装が速くなった今こそ、ログイン周りの知識を落としたくない

最近、AIのおかげで実装のスピードは本当に上がったと感じます。

画面を作る。モデルを作る。認証を通す。少なくとも「動くもの」を用意するところまでは、以前よりずっと速くなりました。

その中でも、セキュリティの大事な観点が抜けないか という不安は残ります。

特にログイン周りは、見た目には普通に動いていても、

  • なぜ bcrypt なのか
  • ソルトはどこで効いているのか
  • ストレッチングは何のためか
  • 将来もっと強い方式へ移行できるのか

のような部分で設計の質が変わります。

動いてはいる。フレームワークにも準拠している。AIもそれっぽい実装を返してくれる。
ですが、必要な知識を持たないまま使っている状態は、やはり脆いと思うのです。

実装そのものが危険というより、

  • AIが何をやってくれていて
  • 何をやってくれていない可能性があって
  • どこが弱点になりうるのか
  • 将来どう見直すべきか

に気づけないことが危うい状態だと思います。

ログイン周りは、少なくともこのブラックボックス状態のまま置いておきたくないです。
その中でも、パスワード保存の話はまさにその典型だと感じています。

実際、パスワード保存を理解しようとすると、

  • ハッシュ化とは何か
  • ソルトは何のためにあるのか
  • ストレッチングは必要なのか
  • bcrypt と Argon2id はどう見ればよいのか

など、次々と考慮事項が出てきます。

ライブラリやフレームワークを使うにしても、せめて構造だけは押さえておきたいです。

そこで今回は、パスワード保存で最低限押さえておきたい「ハッシュ化」「ソルト化」「ストレッチング」と、その先の選定について、AIが何かを抜かしても気づけるだけの知識を持つことを目指して整理してみようと思います。

問題は「動いているか」より「抜けに気づけるか」

パスワード保存は、ライブラリやフレームワークを使えば、表面上は比較的簡単に実装できます。
でも本当に大事なのは、次の観点を知識として持てているかどうかだと思っています。

  • なぜ暗号化ではなくハッシュ化なのか
  • ソルトはどういう役割を持つのか
  • なぜ SHA-256 単体ではなく bcrypt や Argon2id を使うのか
  • ワークファクタやメモリコストは何のためにあるのか
  • 制約や移行方針まで含めて、どう設計するのか

このあたりを知らないまま採用していると、

  • AIが大事な前提を落としても見抜けない
  • レビューで止めるべき点を止めにくい
  • 将来の移行方針を持ちにくい
  • 事故が起きたときにどこが弱かったのか整理しにくい

という弱さにつながります。

AI時代は、認証実装そのものは速く作れます。
でも、速く作れることと、抜けを見抜けることは別問題です。

だからこそ、パスワード保存は「動いたからOK」で流さず、最低限の構造を知ったうえで選びたいです。

ハッシュ化とは何か

まず、ハッシュ化は元の値に戻せない形へ変換することです。

ここで大事なのは、パスワード保存は「暗号化」よりも「ハッシュ化」で考えるべき、という点です。

暗号化は、鍵があれば元に戻せます。
でも、パスワード保存では、そもそも元の文字列を復元できる必要はありません。

必要なのは、

  1. ユーザーが入力したパスワード
  2. 保存済みのハッシュ

を比較して、一致しているか判定できることです。

なので、パスワードは“読める形で持たない”ほうが自然です。

この観点は、OWASP Password Storage Cheat Sheet でも、パスワードは原則として暗号化ではなくハッシュ化すべきだと整理されています。

ソルト化とは何か

次にソルトです。

ソルトは、各ユーザーごとに付与するランダムな値です。パスワードにその値を組み合わせてからハッシュ化します。

これがないと、同じパスワードを使っているユーザーは同じハッシュになりやすく、

  • 「同じパスワードを使っている人」が見えやすい
  • 事前計算済みのレインボーテーブルに弱くなる
  • 攻撃者がまとめて試しやすくなる

という問題が出てきます。

逆にソルトがあれば、同じパスワードでも保存結果は変わります。

ここは誤解しやすいのですが、modern なパスワードハッシュ関数では、ソルトを自分で雑に実装するより、ライブラリやフレームワークに任せるほうが安全です。

OWASP でも、Argon2id や bcrypt などの実装では、広く使われているライブラリがソルト生成と管理を内部で扱うことが多いと説明されています。

ストレッチングとは何か

ストレッチングは、ハッシュ計算を意図的に重くすることです。

パスワード保存で怖いのは、データベースが漏れたあとにオフラインで総当たりされることです。
このとき、計算が速い方式だと攻撃側が大量に試せてしまいます。

なので、

  • 1回で終わる速い計算

ではなく、

  • わざと時間やメモリを使う計算

にして、攻撃コストを上げる必要があります。

bcrypt でいう cost、Argon2id でいう m t p のようなパラメータは、この考え方に関係しています。

OWASP でも、ワークファクタはセキュリティと性能のバランスを見て決めるべきで、ハッシュ計算は一般論として 1 秒未満に収まるくらいを目安に調整するよう案内されています。

つまり、ストレッチングは「強そうだから付けるおまけ」ではなく、パスワード保存の本体側に近い考え方だと思っています。

まず避けたいのは「高速ハッシュをそのまま使う」こと

実務だと、次のような実装をうっかり見かけることがあります。

# NG例
Digest::SHA256.hexdigest(password)

一見すると、

  • ハッシュ化している
  • 平文ではない

ので良さそうに見えます。

でも、SHA-256 のような高速ハッシュをそのままパスワード保存に使うのは、「速いこと」がむしろ弱点になりやすいです。

パスワード保存では、ファイルの整合性確認のような「速いほうが良いハッシュ」と、求められる性質が違います。

Rails なら、少なくとも自前でこのあたりを組むより、

class User < ApplicationRecord
  has_secure_password
end

のようにフレームワークが用意している導線に乗るほうがよさそうです。

Rails の公式 API / Security Guide でも、has_secure_passwordbcrypt を使ってパスワードのハッシュを保存する仕組みとして説明されています。

bcrypt は今でもよく使われる。Rails では特に自然な選択肢

まず、現場でよく見かけるのは bcrypt だと思います。

Rails では ActiveModel::SecurePassword has_secure_password が bcrypt 前提で動きますし、Rails Security Guide でもその流れが案内されています。

この意味では、Rails で素直に認証を組むなら bcrypt は実務的な選択肢です。

bcrypt の良いところ

  • 実績が長く、広く使われている
  • Rails の標準導線に乗りやすい
  • cost で計算コストを調整できる
  • ソルトも含めてライブラリ側に任せやすい

bcrypt-ruby の README ではデフォルト cost は 12 とされていますし、OWASP でも legacy system で使うなら work factor は少なくとも 10 以上を推奨しています。

自前で SHA-256 + 謎実装を組むくらいなら、bcrypt に寄せたほうがずっと安全です。

bcrypt の気になるところ

一方で、弱点や制約もあります。

  • CPU コスト中心で、Argon2id のようなメモリハード性はない
  • 入力長に制約がある
  • いま新規選定する最有力候補かというと、少し議論がある

特に入力長については重要です。
Rails の has_secure_password でも、パスワード長は 72 bytes 以下というバリデーションが入ります。OWASP でも、bcrypt の多くの実装では 72 bytes 制限があると整理されています。

なので、bcrypt は「ダメ」ではありませんが、

  • 何も考えなくてよい万能解

でもない、という理解がちょうどよいのではないでしょうか。

Argon2id は、新規選定なら第一候補に置きたい

Argon2id は、いまのパスワード保存の話をするときに外しにくい存在です。

OWASP Password Storage Cheat Sheet では、Argon2id を第一候補として推奨しています。
また、RFC 9106 でも、Argon2 は memory-hard function として定義され、Argon2id は side-channel attack protection と brute-force cost のバランスを取る変種として説明されています。

Argon2id の良いところ

  • CPU だけでなくメモリ使用量も防御に使える
  • GPU ベースの総当たりに対して強い方向へ寄せやすい
  • m(メモリ量)、t(反復回数)、p(並列度)で調整できる
  • 新規システムで「より良いデフォルト」を選びたいときに相性がよい

OWASP では、最低ラインの一例として 19 MiB / iteration 2 / parallelism 1 などが紹介されています。

特に、攻撃側の計算資源が強くなる時代ほど、メモリも使わせる設計は意味があると思っています。

Argon2id の気になるところ

もちろん、Argon2id にもデメリットはあります。

  • bcrypt より導入の敷居が高いことがある
  • パラメータ調整の自由度が高いぶん、設計判断が増える
  • メモリ使用量が増えるので、運用環境によっては扱いに注意がいる

ここは Rails 開発者目線だと特にそうで、少なくとも公式の has_secure_password 導線は bcrypt ベースです。
そのため、Argon2id を使うなら、gem 選定・検証・運用方針を自分で持つ必要が出やすいと思います。

この点は、「Argon2id が優れている」ことと、「今の自分たちのプロダクトにすぐ入れやすい」ことが別問題だ、という話でもあります。

これらを語る上で、他に大事な観点

ハッシュ化・ソルト化・ストレッチングだけでも大切ですが、実務ではさらにいくつか気にしたい点があります。

1. pepper をどう扱うか

OWASP では、ソルトに加えて pepper も追加防御として紹介されています。

pepper はソルトと違ってユーザーごとに異なる値ではなく、DB とは別の安全な場所に置く共通秘密です。

これがあると、DB だけ漏れたケースでは攻撃の難易度を上げられます。
ただし、漏洩時の運用やローテーションは重くなるので、何でも入れればよいわけでもありません。

2. 将来の再ハッシュを前提にする

今の最適解は、数年後の最適解ではないかもしれません。

だからこそ、

  • ワークファクタを上げられるか
  • bcrypt から Argon2id へ移行できるか
  • ログイン時に再ハッシュできるか

を最初から考えておくほうがよいです。

OWASP でも、ワークファクタ更新は「次回ログイン時に再ハッシュする」アプローチが一般的だと説明されています。

3. 最低限の認証周辺防御も必要

パスワード保存方式が強くても、

  • ブルートフォース対策
  • レート制限
  • パスワードリセットの安全性
  • ログや監査

が弱いと、別のところから崩れます。

つまり、保存方式は大事ですが、それだけでセキュリティが完成するわけではありません。

AI時代ほど、「動く実装」を鵜呑みにしない

ここまで見てくると、

  • Rails ならまず bcrypt + has_secure_password は堅い
  • ただし新規選定を柔軟にできるなら Argon2id は魅力が大きい

という整理になると思っています。

ここで大事なのは、「標準だから bcrypt を使う」こと自体が悪いわけではないという点です。
むしろ Rails では、それは自然で堅実な選択です。

ただし、その選択について少なくとも

  • 何が守れて
  • どんな制約があり
  • どこが将来の見直しポイントなのか

を把握しておきたいです。

未来は、AI によって攻撃も防御もさらに多様化していくはずです。
攻撃側は、より安く、より多く、より速く試せるようになります。

だからこそ、保存方式の選定でも「一応動く」より「抜けに気づける知識を持つ」を積み重ねたいです。

明日からできること

  1. 自分のシステムが今どの方式でパスワードを保存しているか把握する
  2. SHA-256 単体 のような実装になっていないか確認する
  3. Rails ならまず has_secure_password と bcrypt の仕様を理解する
  4. 新規システムなら Argon2id を採用できるか検討する
  5. 72 bytes 制限、pepper、再ハッシュ方針まで設計に入れる
  6. AIが抜かしそうな観点をレビュー項目として残す

最初から完璧にやるのは難しいです。
でも、認証基盤は後から直すほどしんどいので、まずは保存方式をブラックボックスにしないところから始めるのがよさそうです。

まとめ

パスワード保存は、単に「平文で持たない」や「ハッシュ化しておく」といった表面的な話ではありません。

大事なのは、

  • ハッシュ化で平文を持たない
  • ソルトで使い回し攻撃を難しくする
  • ストレッチングで総当たりコストを上げる
  • さらに bcrypt / Argon2id の特徴を理解して選ぶ

ことだと思っています。

Rails では bcrypt がとても扱いやすいですし、今も実務上は十分現役です。
一方で、新規選定を広く考えるなら Argon2id を第一候補に置きたい、という流れも理解しておきたいです。

AI時代だからこそ、認証実装のような重要部分ほど「フレームワークがそうしているから」で終わらせず、AIが何かを落としても気づけるくらいには理解しておきたいですね。

まずは、自分のプロダクトが今どの方式でパスワードを保存していて、その方式の弱点や制約を把握できているか確認するところから始めてみてはいかがでしょうか。
自分も引き続き、便利さだけでなく、未来の攻撃にも耐えやすい選択を学んでいきたいと思います。

この記事をシェア