ワンタイムコードはスマートフォン側では完全に正しいのに、同じ秒にサーバーが拒否することがあります。両側は共有の秘密と現在のタイムステップから同じ関数を計算するため、時刻の認識が食い違えばコードも食い違います。そのため時計のずれは、単なる不便ではなくセキュリティに関わる不具合になります。

ここでは二つの時計が重要で、目に見える方を直すのが人が最初にやることです。ふつう注意が必要なのはもう一方です。

コードは時刻の関数であり、パスワードではない

TOTPは共有の秘密と現在の時刻から導いたカウンターを取り、両方を鍵付きハッシュに通し、結果を短い十進文字列に切り詰めます。仕様は RFC 6238 に定められています。カウンターはタイムステップごとに進み、通常は三十秒なので、ステップが変わると同じ秘密でも新しい値になります。検証側は現在のステップの期待値を再計算して比較しますが、それには同じ秘密と同じ「今」の認識が必要です。パスワードは変更するまで再利用できますが、この値はクライアントから見えない境界で失効します。

だから一つのコードは通り、次のコードは数瞬後に失敗します。チャットのメッセージからコードをコピーするのが、これほど頼りない習慣である理由もここにあります。

二つの時計が一致する必要があり、一つでは足りない

登録の素材が第一の共有物で、時刻が第二です。秘密が正しくても時計の差が許容ウィンドウを超えていれば、生成された値はすべて誤ったステップに落ちて拒否されます。時計が一致していても秘密が誤っていれば、どの値も拒否され、どちらの失敗もログイン画面からは同じに見えます。両者を切り分けるには、アカウントの登録内容と時刻を別々に、この順で確認します。

検証側の時計も、デバイスの時計と同じだけ疑う価値があります。時刻源が壊れたサーバーは全利用者の正しいコードを一斉に拒否し、報告はドリフトではなく突然の障害として語られるでしょう。このパターンは覚えておく価値があります。直すべきはサーバーであり、百台のスマートフォンではないからです。

ウィンドウを広げる代償

実装は小さな差を吸収するために隣接ステップのコードを受け入れることがありますが、その許容は設計上の判断であり、無料の調整ではありません。ステップを一つ増やすたびに、ある瞬間に有効な値の数が増えます。それは推測する攻撃者がまさに好む状況です。同じ理由で再利用の規則も重要で、二度受け入れたコードは有益に漏れたコードです。苦情を黙らせるためにウィンドウを広げる管理者は、実質的な性質を表面的な改善と引き換えにしています。

時計を直すことが修理であり、ウィンドウを広げることは鎮静剤です。前者は原因を取り除き、後者はそれを隠します。

再試行する前に両方の時計を確認する

デバイスとサーバーで時刻を読み、表示されたローカル時刻ではなくずれを比較します。以下のコマンドは、Linuxのホストまたはサーバーで同期状態と生のUTC時刻を示します。認証設定を変更する前に、両端で実行してください。

timedatectl
date -u +'%Y-%m-%dT%H:%M:%SZ'
chronyc tracking
timedatectl timesync-status

秒単位のずれは無害で、分単位のずれは拒否を説明し、同期元を何も報告しないシステムはどちらの候補でもあります。二つのUTCタイムスタンプを直接比較してください。ローカル時刻にはアルゴリズムが見ることのないタイムゾーンが含まれるからです。ずれが小さくコードがまだ失敗するなら、時計の調整をやめて登録内容を調べてください。ロックされたアカウントへの繰り返しの試行は、元の不具合を観察しにくくします。

拒否の時刻、ずれ、エラーの文言を記録し、共有の秘密はそこに含めないでください。サポートは最初の三つで作業でき、四つ目を見るべきではありません。

関連記事