一覧SD-05-2 · 認証機能の安全な実装

アカウント復旧と認証器の変更

復旧と重要操作を、対象・期限・消費・再認証の条件で制御します。

説明

パスワード復旧では、トークンの期限だけでなく、対象アカウントと一回性を確認します。認証器を変更する操作では、古いログインセッションを持つだけで実行できないよう、最近の再認証を求める設計も必要です。

固定の復旧トークンを使い、別アカウント、期限境界、正しい復旧、再利用を比較します。続いて認証器の変更を再認証前後で試し、既存セッションと保存状態を調べます。

復旧と重要操作を、対象・期限・消費・再認証の条件で制御します。

1トークンと対象の保存記録を読む

復旧トークンの平文ではなくSHA-256を保存します。この演習用の公開トークンはalice向け、期限60、未使用です。

from sd05 import * db = accounts() token = reset_token(db)対象と期限を持つ復旧試料を用意する print(db.execute("SELECT subject,expires,used,length(hash) FROM reset").fetchall()) print(db.execute("SELECT * FROM sessions").fetchall())

aliceに既存セッションが1件あります。復旧後にこのセッションをどう扱うかも決めます。

2アカウント差替えと期限境界を拒否する

同じトークンをbobへ使う場合と、aliceで時刻60へ達した場合です。

print(reset_password(db,token,"bob","new-public-password",59対象を別アカウントへ変える)) print(reset_password(db,token,"alice","new-public-password",60期限境界で利用する)) print(db.execute("SELECT used FROM reset").fetchone())

両方Falseで未使用状態を保ちます。失敗した要求が先にトークンを消費してしまう処理を避けています。

3期限内に復旧し既存セッションを失効させる

正しい対象と時刻59で実行します。トークン消費・パスワード更新・既存セッション失効を同じトランザクションで処理します。

print(reset_password(db,token,"alice","new-public-password",59)対象と期限が合う復旧を確定する) print(db.execute("SELECT used FROM reset").fetchone()) print(db.execute("SELECT active FROM sessions").fetchall())

True、使用済み1、セッションactive=0です。復旧後も以前の認証状態で操作を続けさせるかを明示的に決めています。

4再利用を拒否し新しい保存値を照合する

同じトークンで別のパスワードへ変更しようとします。

print(reset_password(db,token,"alice","another-password"使用済みトークンで再変更を試す,59)) salt,saved = db.execute("SELECT salt,hash FROM users WHERE name='alice'").fetchone() print(hmac.compare_digest最初に確定した保存値を照合する(saved,password_hash("new-public-password",salt)))

再利用はFalseで、最初の復旧で設定した値が保たれます。

5新しいセッションだけでは認証器を変えない

復旧後のログイン済み状態を固定資料として追加します。最近の再認証記録はまだありません。

db.execute("INSERT INTO sessions VALUES('new-session','alice',1)") db.commit() print(change_mfa(db,"new-session",100)再認証なしで変更を要求する) print(db.execute("SELECT mfa FROM users WHERE name='alice'").fetchone())

更新0でold-keyです。ログイン済みであることと、重要操作の直前に本人を再確認したことを分けています。

6再認証して変更する

現在のパスワードを照合し、300秒間だけ再認証済みとする例です。

print(reauthenticate現在の認証情報を再照合する(db,"new-session","new-public-password",100)) print(change_mfa(db,"new-session",101)有効な再認証状態で変更する) print(db.execute("SELECT mfa FROM users WHERE name='alice'").fetchone())

再認証True、更新1、new-keyです。適切な再認証手段は認証器や操作のリスクに応じて設計します。

7再認証の有効期限を確認する

再認証時刻100+300で、時刻400は期限境界です。

print(change_mfa(db,"new-session",400)再認証の期限境界で要求する) print(db.execute("SELECT name,mfa FROM users ORDER BY name").fetchall())

更新0です。aliceの変更結果を保ち、bobのold-keyは変わっていません。長いセッションの全期間へ重要操作の権限を広げない設計です。

まとめ

復旧トークンを対象・期限・一回性で検証します。

復旧時のトークン消費・保存値更新・セッション失効を整合させます。

認証器変更などの重要操作には、適切な再認証の条件を設けます。

操作履歴と解説を振り返れます。

Python 3.15 / cryptography 50.0.2 / 公開試料と固定時刻

1
from sd05 import * db = accounts() token = reset_token(db) print(db.execute("SELECT subject,expires,used,length(hash) FROM reset").fetchall()) print(db.execute("SELECT * FROM sessions").fetchall())
[('alice', 60, 0, 64)]
[('old-session', 'alice', 1)]
2
print(reset_password(db,token,"bob","new-public-password",59)) print(reset_password(db,token,"alice","new-public-password",60)) print(db.execute("SELECT used FROM reset").fetchone())
False
False
(0,)
3
print(reset_password(db,token,"alice","new-public-password",59)) print(db.execute("SELECT used FROM reset").fetchone()) print(db.execute("SELECT active FROM sessions").fetchall())
True
(1,)
[(0,)]
4
print(reset_password(db,token,"alice","another-password",59)) salt,saved = db.execute("SELECT salt,hash FROM users WHERE name='alice'").fetchone() print(hmac.compare_digest(saved,password_hash("new-public-password",salt)))
False
True
5
db.execute("INSERT INTO sessions VALUES('new-session','alice',1)") db.commit() print(change_mfa(db,"new-session",100)) print(db.execute("SELECT mfa FROM users WHERE name='alice'").fetchone())
0
('old-key',)
6
print(reauthenticate(db,"new-session","new-public-password",100)) print(change_mfa(db,"new-session",101)) print(db.execute("SELECT mfa FROM users WHERE name='alice'").fetchone())
True
1
('new-key',)
7
print(change_mfa(db,"new-session",400)) print(db.execute("SELECT name,mfa FROM users ORDER BY name").fetchall())
0
[('alice', 'new-key'), ('bob', 'old-key')]

演習完了