概要
MicrosoftやGoogle、AmazonやTwitterのログインで使える6桁の二要素認証用のパスワードですが、ふと思い立ったのでAuthenticator(↓こんなやつ)をPythonでアルゴリズムだけ実装してみました。
仕組み
ワンタイムパスワードが30秒ごとに切り替わっていることからも推察できるように、アルゴリズムは時間ベースのワンタイムパスワード(TOTP)です。
なおシークレットと時間の処理にはHMACベースのワンタイムパスワード(HOTP)を使います。
すみませんが、日本語による詳細な説明は参考文献に任せます。
コード
- #!/usr/bin/env python3
- import base64
- import time
- import hmac
- from hashlib import sha1
- def truncate( hash_20, digit):
- offset = hash_20[19] & 15
- P = bytearray(4)
- P[0] = hash_20[offset+0] & 0x7f
- P[1] = hash_20[offset+1]
- P[2] = hash_20[offset+2]
- P[3] = hash_20[offset+3]
- trunc = int.from_bytes(P, 'big') % 10 ** digit
- zerofill = str(trunc).zfill(digit)
- return zerofill
- #secret_b32 = 'YOUT_16_DIGITKEY'
- #secret_bin = base64.b32decode(secret_b32)
- secret_bin = '12345678901234567890'.encode()
- unixtime = int(time.time())
- #time_counter = int( unixtime / 30)
- time_counter = 0
- count = time_counter.to_bytes(8,byteorder='big')
- m = hmac.new(secret_bin, count, sha1)
- sig_20 = m.digest()
- totp = truncate( sig_20, 6)
- print("Secret:",secret_bin)
- print("Counter:",time_counter)
- print("HMAC-SHA-1:",m.hexdigest())
- print("TOTP:",totp)
- #!/usr/bin/env python3
- import base64
- import time
- import hmac
- from hashlib import sha1
- def truncate( hash_20, digit):
- offset = hash_20[19] & 15
- P = bytearray(4)
- P[0] = hash_20[offset+0] & 0x7f
- P[1] = hash_20[offset+1]
- P[2] = hash_20[offset+2]
- P[3] = hash_20[offset+3]
- trunc = int.from_bytes(P, 'big') % 10 ** digit
- zerofill = str(trunc).zfill(digit)
- return zerofill
- #secret_b32 = 'YOUT_16_DIGITKEY'
- #secret_bin = base64.b32decode(secret_b32)
- secret_bin = '12345678901234567890'.encode()
- unixtime = int(time.time())
- #time_counter = int( unixtime / 30)
- time_counter = 0
- count = time_counter.to_bytes(8,byteorder='big')
- m = hmac.new(secret_bin, count, sha1)
- sig_20 = m.digest()
- totp = truncate( sig_20, 6)
- print("Secret:",secret_bin)
- print("Counter:",time_counter)
- print("HMAC-SHA-1:",m.hexdigest())
- print("TOTP:",totp)
上記のコードはシークレットが”12345678901234567890″かつタイムカウンタが0のときの結果を出力します。これはHOTPについて記述されているRFC4226のAppendix DのTest Valuesにあわせたものです。実行結果は以下。
- $ python3 poc.py
- Secret: b'12345678901234567890'
- Counter: 0
- HMAC-SHA-1: cc93cf18508d94934c64b65d8ba7667fb7cde4b0
- TOTP: 755224
各々の値で試したい場合はBASE32に記述されたシークレットはsecret_b32に、time_counterをunixtimeベースの処理にコメントを付け直してください。
数パターンしか試してないですが、たぶんこれで動きます。不具合あったら教えてください。