
ワンタイムパスワード(TOTP)をPythonで実装|Google Authenticatorのしくみ
2026-09-13 ・ 実践
Google Authenticatorなどが表示する「30秒ごとに変わる6桁コード」——あれが TOTP(時間ベースのワンタイムパスワード) です。多要素認証の主役を、Pythonの標準ライブラリだけで実装します。仕組みは驚くほどシンプルで、作ると腑に落ちます。
しくみ:秘密鍵 × 時刻
TOTPは「サーバーとアプリで共有した秘密鍵」と「現在時刻(30秒単位)」をHMACにかけ、そこから6桁を取り出すだけ。同じ鍵と同じ時間帯なら、両者は同じコードに必ずたどり着きます。
鍵と時刻から6桁
共有の秘密鍵
登録時に配布
30秒枠の時刻
両者で一致
6桁コード
HMACから抽出
準備
標準ライブラリ(hmac・hashlib・struct・time・base64)だけ。
① TOTPを実装
RFC 6238 の手順そのままです。
import hmac, hashlib, struct, time, base64
def totp(secret_base32: str, interval=30, digits=6, t=None):
key = base64.b32decode(secret_base32.upper())
counter = int((t or time.time()) // interval) # 30秒枠の番号
msg = struct.pack(">Q", counter) # 8バイトに
h = hmac.new(key, msg, hashlib.sha1).digest()
offset = h[-1] & 0x0F # 動的トランケーション
code = (struct.unpack(">I", h[offset:offset+4])[0] & 0x7FFFFFFF) % (10 ** digits)
return str(code).zfill(digits)
secret = "JBSWY3DPEHPK3PXP" # 登録時に共有する鍵
print("いまのコード:", totp(secret))
30秒待って実行すると、コードが変わります。アプリの表示とまったく同じ計算です。
② サーバー側の検証
ユーザーが入力したコードが正しいか照合します。時計のズレを考慮して前後の枠も許容するのが実務のコツ。
def verify(secret, code, window=1):
now = time.time()
for drift in range(-window, window + 1):
if hmac.compare_digest(totp(secret, t=now + drift * 30), code):
return True
return False
code = totp(secret)
print("検証:", verify(secret, code)) # True
print("誤りコード:", verify(secret, "000000"))
正しいコードだけTrue。window=1 で前後30秒のズレを許すので、端末の時計が多少ずれていても通ります。
QRコードの正体
アプリ登録時のQRコードは、otpauth://totp/...?secret=JBSWY3DPEHPK3PXP という文字列です。中身はこの秘密鍵。だから鍵さえ共有できれば、QRなしでも登録できます。
秘密鍵の扱いが命
TOTPの安全性は秘密鍵の秘匿にかかっています。サーバー側では暗号化して保存し、漏洩したら即再発行を。TOTPはパスワードと組み合わせて初めて意味がある「2つ目の要素」です。
まとめ
- TOTPは「共有秘密鍵 × 30秒枠の時刻」をHMACにかけて6桁を作る
- サーバーとアプリが同じ計算をするので、通信なしで一致する
- 検証は時計ズレを考慮して前後の枠も許容する
- 秘密鍵の秘匿が命。パスワードと組み合わせる2つ目の要素
もう少し詳しく(背景と理論)
TOTP(時刻ベースのワンタイムパスワード)は RFC 6238 で標準化され、その土台は RFC 4226 の HOTP(カウンタベース)です1。共有した秘密鍵と「現在時刻を30秒単位で割った値」を HMAC にかけ、そこから6桁の数字を切り出します。サーバーとアプリが同じ鍵と時刻を持つため、通信なしで同じコードを再現できるのがしくみの核です2。時計のズレに備えて前後1ステップ(±30秒)を許容するのが一般的。フィッシングには弱い(コードを盗んで即使われる)ため、より強い耐フィッシング認証として公開鍵ベースの FIDO2/WebAuthn が普及しつつあります3。
次の一歩 🌸
多要素認証の全体像はMFA、パスワード不要のパスキー、鍵の暗号化保存はAES実装へどうぞ。