技術 約10分で読めます

パスワード定期変更を確率で見る

いけさん目次

仕事の仕様で、パスワードを定期的に変える機能を入れるか揉めた。
相手は「定期変更できたほうが安全」と言う。俺は「確率的にほぼいらん」と思っている。

もちろん、漏れたパスワードを変えないのはまずい。
でもそれは「漏洩や乗っ取りの兆候があるから変える」であって、「90日経ったから全員変える」とは別の話だ。

NIST SP 800-63B は、パスワードを定期的に変えさせるな、ただし認証器が侵害された証拠があるなら強制変更しろ、という立場を取っている。
Microsoft 365 の管理者向け推奨も、クラウド専用アカウントではパスワードを期限切れにしない設定を推している。理由は現場っぽくて、ルールを増やすほど人間は弱いパスワードに寄せるからだ。

定期変更が効く条件は狭い

パスワード定期変更が防ぐのは、だいたいこのケースだけだ。

flowchart TD
    A[パスワードが漏れる] --> B[まだ検知できない]
    B --> C[攻撃者がまだ使っていない]
    C --> D[定期変更日が来る]
    D --> E[古いパスワードが無効になる]

つまり、攻撃者がパスワードを手に入れたあと、ログインに使う前に、ちょうどユーザーが変更してくれた場合だけ助かる。

逆に、以下のケースでは定期変更はあまり効かない。

状況定期変更の効き方
フィッシング直後にログインされる変更日を待つ前に突破される
パスワードを使い回している別サービス側の漏洩でまた入られる
端末がマルウェアに感染している新しいパスワードも取られる
セッションCookieを盗まれているパスワード変更とは別経路で入られる
漏洩が検知済み定期日ではなく即時変更が必要

ここで「定期変更には意味がない」と言い切ると少し雑で、意味はある。
ただし効く範囲が「漏れているが、まだ使われておらず、まだ検知もできていない」という細いところに限られる。

定期変更が攻撃者より先にパスワードを無効化できる確率は、3つの条件の掛け算になる。

Psave=Pleak×Pundetected×Punused until rotationP_{\text{save}} = P_{\text{leak}} \times P_{\text{undetected}} \times P_{\text{unused until rotation}}

仮に90日あたりのパスワード漏洩確率を1%、漏洩が検知されない確率を50%、攻撃者が変更日まで使わない確率を10%とする。

0.01×0.5×0.1=0.00050.01 \times 0.5 \times 0.1 = 0.0005

1アカウントあたり90日サイクル1回で0.05%。
前提を倍にしても0.1%にしかならず、桁が変わるほどにはならない。

1万アカウントの組織で年4回の定期変更を回したとして、定期変更が漏洩被害を未然に防げるアカウント数の期待値を出す。

10,000×0.0005×4=2010{,}000 \times 0.0005 \times 4 = 20

年間20件。
ゼロではないが、パスワードリセットに伴うヘルプデスク問い合わせやロックアウトのコストを考えると、組織として割に合うかは怪しい。

90日ごとに変更させるなら、漏洩から無効化までの待ち時間は平均45日になる。
30日ごとなら平均15日。
この数字だけ見ると短いほうが強そうに見える。

でも攻撃者が盗んだパスワードを使うまでの時間が数分から数時間なら、平均45日を15日に縮めてもほとんど間に合わない。
「いつか使われるかもしれない古い漏洩リスト」には効くが、「いま盗っていま使う」攻撃には弱い。

Google の研究では、フィッシングで盗まれた認証情報はほぼ即座にログイン試行されている。
定期変更の単位が90日や30日だとすると、攻撃者の動きと2〜3桁ずれている。

ユーザー行動の悪化も確率に入れる

定期変更の議論で抜けがちなのが、変更を強制したことでユーザーが弱くなる確率だ。

人間は90日ごとに完全に新しい強いパスワードを考えない。
それなりの割合で、末尾の数字を増やす、季節名を入れる、過去パターンを再利用する、管理しきれずメモに残す、同じものを別サービスにも使う。

UNC Chapel Hill の研究(Zhang et al., 2010)では、定期変更を強制された環境で直前のパスワードから新パスワードの41%を3秒以内にクラックできたと報告されている。
変形パターンの大半は末尾の数字のインクリメント、大文字位置の移動、記号の差し替えといった機械的なもので、攻撃者がルールを知っていれば簡単に絞り込める。
数式にすると、旧パスワードを知っている攻撃者が新パスワードを推測する条件付き確率は P(crackold_pw)0.41P(\text{crack} \mid \text{old\_pw}) \approx 0.41(オフライン3秒以内)。
オンライン攻撃でも5回以内の試行で P0.17P \approx 0.17 になる。
定期変更で古いパスワードを無効化しても、攻撃者が旧パスワードを持っていれば4割はすぐ突破される。

Microsoft の推奨文書も、パスワード変更要件を含むルールはパスワード品質を弱める方向に働く、と書いている。
NIST が「定期変更させるな」に寄せたのも、単にユーザーに優しくしたいからではなく、強制変更が攻撃者に読まれやすいパターンを作るからだ。

確率で見るなら、比較するのはこの2つになる。

項目増えるもの減るもの
定期変更あり弱い派生パスワード、問い合わせ、ロックアウト、使い回し未検知漏洩の有効期間
定期変更なし古いパスワードが残る期間変更疲れ、安易な派生、メモ化

定期変更を入れるなら、「未検知漏洩の有効期間が短くなる利益」が「ユーザーが弱い運用に寄る損失」を上回る必要がある。
ここを見積もらずに「安全そうだから入れる」は、仕様として危ない。

変えるべきタイミングはある

定期変更は薄いが、パスワード変更機能自体は普通に必要だ。

必要なのは、カレンダー駆動ではなくイベント駆動の変更だ。

トリガー対応
サービス側の漏洩対象ユーザーへ強制変更
漏洩済みパスワードリストとの一致ログイン時や変更時に拒否
不審なログイン追加認証、通知、必要なら変更要求
ユーザー本人の申告既存セッション破棄と変更
管理者によるアカウント復旧一時パスワードではなく再設定フロー

OWASP Authentication Cheat Sheet も、漏洩や侵害判明時の認証情報ローテーションは求めつつ、周期的な変更要求は避ける方向だ。
ここはかなり一貫している。

パスワード以外に寄せたほうが期待値が高い

同じ工数を使うなら、定期変更画面より先にやることがある。

パスワードの長さを十分に許す。
よくあるパスワードや漏洩済みパスワードを弾く。
ログイン試行をレート制限する。
パスワードマネージャーを邪魔しない。
MFA(多要素認証)を入れる。
不審ログインを通知する。
パスワード変更時に既存セッションをどう扱うか決める。

MFAについては、以前 TOTP認証を自社サービスに実装する記事を書いた。
TOTP自体はフィッシング耐性が強い方式ではないが、パスワード単体よりはかなりマシになる。
Microsoft の統計では、MFAを有効にしたアカウントへの自動攻撃の99.9%以上がブロックされている。
定期変更の寄与が0.05%オーダーだったのと比べると、同じ工数ならMFAに回したほうが3桁以上効率がいい。
さらに強くするなら、パスキーやWebAuthnのようなフィッシング耐性のある方式に寄せる。

この手の仕様は「破られない仕組み」を作る話ではなく、攻撃コストと利用者の負担をどこに置くかの話になる。
投票システムの記事でも書いたように、本人確認できても不正をゼロにはできない。認証も同じで、ゼロにできないリスクをどの確率まで下げるかを決めるしかない。

2FAと2SA(二段階認証)は別物

MFAの話が出ると、2FA(二要素認証)と2SA(二段階認証)を同じものとして扱っているケースをよく見る。
実際にはこの2つは防御の質が違う。

2FAは認証の「要素」が2種類あることを指す。
認証要素は3つに分類される。

要素内容
知識(Something you know)本人だけが知っている情報パスワード、PIN
所持(Something you have)本人が持っている物理的なものスマホ、セキュリティキー、ICカード
生体(Something you are)本人の身体的特徴指紋、顔、虹彩

2FAはこのうち異なる2種類を組み合わせる。
パスワード(知識)+ TOTPアプリ(所持)なら、パスワードが漏れただけでは突破できない。
攻撃者はスマホも手に入れる必要がある。

一方、2SAは「認証ステップが2回ある」ことを意味するが、要素が2種類とは限らない。
たとえばパスワードを入力した後にSMSで確認コードを送る方式は、見た目は2段階だが微妙なところがある。
SIMスワップ攻撃でSMSを横取りされた場合、所持要素としての強度が低い。
NIST SP 800-63B も、SMSによるOTPは「制限付きの認証器」として位置づけており、推奨の優先度は低い。

現実的な強度で並べるとこうなる。

flowchart LR
    A["パスワードのみ"] --> B["パスワード +<br/>SMS OTP"]
    B --> C["パスワード +<br/>TOTP アプリ"]
    C --> D["パスワード +<br/>セキュリティキー"]
    D --> E["パスキー<br/>(パスワードレス)"]

パスワード+SMS OTPは2SAとしてはよくある構成だが、2FAとしてはSMSの所持要素がSIMスワップ(携帯番号の乗っ取り)やSS7傍受(電話網の信号プロトコルの脆弱性を突いたSMS盗聴)で崩れるリスクがある。
TOTPアプリやセキュリティキーなら、攻撃者が物理的にデバイスを奪わない限り要素が独立したまま保たれる。

仕様を決めるときに「二段階認証を入れる」と言われたら、ステップ数ではなく要素の独立性で判断したほうがいい。
SMS OTPを2FAと呼んで安心するのが一番危ない。

文字種の強制もエントロピーを削る

「大文字・小文字・数字・記号をすべて含むこと」という要件もよく入る。
理論上は文字種が増えれば探索空間が広がるが、実際のユーザー行動ではそうならない。

8文字のパスワードを95種の印刷可能ASCII文字から完全ランダムに選ぶと、組み合わせは 9586.6×101595^8 \approx 6.6 \times 10^{15} 通りで、エントロピーは約52.6ビットになる。
文字種を4種すべて含む制約を加えても、ランダム生成なら理論上の空間はそこまで狭くならない。

ところが文字種を強制されたユーザーの多くは、英単語の先頭だけ大文字にし、末尾に2桁の数字を足し、最後に !@ を付ける。
この場合の実質的な探索空間は、辞書の英単語数 × 数字の組み合わせ × 記号の選択肢になる。
よく使われる英単語を2万語、末尾の数字を00〜99の100通り、記号を33種とする。

20,000×100×33=6.6×10720{,}000 \times 100 \times 33 = 6.6 \times 10^{7}

エントロピーは約26ビット。
制約なしで小文字8文字をランダムに選んだ 2682.1×101126^8 \approx 2.1 \times 10^{11}(約37.6ビット)より11ビット以上弱い。
2112,0002^{11} \approx 2{,}000 倍の差がある。

文字種を強制することで理論上の空間は維持されるが、人間が実際に選ぶ分布が偏るので実質エントロピーが下がる。
NIST SP 800-63B もこの点で「文字種の混合を要求すべきではない」と明記している。

パスワードの強度を確実に上げるなら、文字種より最低文字数を伸ばすほうが効く。

条件組み合わせ数エントロピー
文字種4種強制 8文字(パターン化)6.6×1076.6 \times 10^{7}約26ビット
小文字のみランダム 8文字2.1×10112.1 \times 10^{11}約37.6ビット
小文字のみランダム 12文字9.5×10169.5 \times 10^{16}約56.4ビット
小文字のみランダム 16文字4.4×10224.4 \times 10^{22}約75.2ビット
95種ランダム 8文字6.6×10156.6 \times 10^{15}約52.6ビット

文字種4種を強制された8文字のパターン化パスワードは、ランダムな小文字12文字より 2301092^{30} \approx 10^{9} 倍弱い。
「大文字と記号を入れろ」より「12文字以上にしろ」のほうが、攻撃者の探索コストは桁違いに上がる。

GPUでのクラック時間

エントロピーの差が実際の攻撃でどう効くか、Hashcatの実測値をもとに換算する。
RTX 4090 1枚でのハッシュ探索速度は、MD5で毎秒約1,640億回、bcrypt (cost=10) で毎秒約5,750回。
アルゴリズムの選択だけで速度が7桁変わる。

この速度で各パスワードを全探索したときの所要時間がこうなる。

パスワードの種類エントロピーMD5bcrypt (cost=10)
文字種4種強制パターン化 8文字約26ビット0.0004秒約3時間
小文字のみランダム 8文字約37.6ビット約1.3秒約1.2年
小文字のみランダム 12文字約56.4ビット約6.7日約52万年
95種ランダム 8文字約52.6ビット約11時間約3.7万年

「大文字・数字・記号を全部入れろ」と言われたユーザーが Password1! を作った場合、bcryptで保存されていても3時間で割れる。
vkrmxjqwpbtf のような小文字12文字ランダムなら、MD5で保存されていても1週間近くかかる。

文字種の数ではなく、パスワードが攻撃者の予測可能なパターンに従っているかどうかが効いている。
文字種の強制は理論上の空間を維持するが、人間が選ぶ分布を偏らせる。
結果として、空間を広げるはずの制約が攻撃者の仕事を楽にしている。

仕様として落とすならこうする

自分なら、パスワード周りの仕様はこう切る。

仕様判断
ユーザーによる任意変更入れる
漏洩・不審ログイン時の強制変更入れる
パスワード期限切れ原則入れない
管理者の一括強制変更インシデント対応用に入れる
変更時の全セッション破棄選択式か、リスク時は強制
漏洩済みパスワードのブロック入れる
文字種の混合強制入れない。最低文字数で制御
MFAできるだけ入れる

「パスワードを変えさせる機能」をゼロにするのではなく、「時間が来たから変えさせる」をやめる。
ここを分けると話がかなり通しやすくなる。

規制や契約で周期変更が明示されているなら、その範囲では従うしかない。
ただ、仕様判断として自由があるなら、確率的には定期変更より検知・通知・MFA・漏洩パスワードブロックに寄せたほうがいい。

参照