では、Pythonでの実装についてみていきましょう。 HMAC関数に渡す引数3つを順に準備していきます。
まず、秘密鍵からです。 秘密鍵は16文字の半角英大文字+6種類の半角数字でした。 この文字列をPythonの標準ライブラリでBase32のデコードを行います。 関数はBase32ですが、この種類の関数はbase64モジュールに含まれているので base64をインポートします。
import base64
Python2だとstr
もバイト列も同じ(極端に言うと、です)なのでbase64.b32decode
関数にそのまま渡すことができます。
しかし、渡される文字列が8文字刻みでないとエラーになってしまいます。
汎用的にするために8文字の倍数でないときにパディングとして8文字の倍数になるように
"="
を追加します。
今回は16文字とわかっているので不要ですが、デコードで最低限エラーにならないように
パディングをします。
式は以下のとおりです。
secret_key += '=' * (-len(secret_key) % 8)
モジュロ(%
)の計算での符号の関係はこちらの説明を参照してください。
バイナリに戻した秘密鍵をリターンするコードは下記のようになります。
return base64.b32decode(secret_key)
関数名はdecode_secret_key
とでもしましょう。
次に時刻情報です。
Pythonでtimestamp
を求めるにはdatetime
モジュールのdatetime
オブジェクトで取得できます。
datetime
モジュールからdatatime
オブジェクトをインポートしておきましょう。
from datetime import datetime
しかし、Python2ではdatetime
オブジェクトにはtimestamp
関数はありません。
この関数はPython3のVersion3.3から使えるようになります。
ではどうやって計算することができるのでしょうか。
epochタイムをdatetime(1970, 1, 1, 0, 0)
として、基準の日時を求めます。
UTCの現在時刻からこの基準日時を引くことでタイムスタンプに相当する時間間隔を計算します。
utcnow = datetime.utcnow()
epoch = datetime(1970, 1, 1, 0, 0)
utctimedelta = utcnow - epoch
datetime
オブジェクトどおしで引き算するとtimedelta
オブジェクトが得られるのでそこから
total_seconds()
関数で秒数を算出します。得られる秒数は浮動小数なので整数に変換します。
timestamp = int(utctimedalta.total_seconds())
こうしてUTCのタイムスタンプが得られました。
あとはtime-step sizeで整数除算をすれば、HMAC
に与えるmessage
(任意のデータ)の準備が
できます。
HMAC
に与える時刻情報(time-step sizeで割った値)をtimecode
と呼ぶことにします。
必須ではありませんが、この時割り算をしたあまりはtime-step sizeの中で経過した時間と言えます。
つまり、time-step sizeからこのあまりの秒数を引けば残り時間となります。
整数除算の商とあまりを一度に計算する関数がPythonにはあります。divmod
です。
商とあまりはタプルとして返されます。
(quo, rem) = divmod(timestamp, period)
ここで使用するperiodは引数として受け取るようにしますが、デフォルトとして30を指定しておきます。
def get_timecode(period=30):
この結果をタプルとしてtimecodeとremaining(残り時間)を返すようにします。
return (quo, (period - rem))
TOTP
を計算する関数に渡す引数としてはデコード前のsecret_key
と、timestamp
をperiod
で割った
整数のtimecodeを渡すようにします。
そのため、HMAC関数にこれらの情報を渡す前にもうひと手間かける必要があります。
一つはsecret_key
をデコード関数でデコードすること、もう一つはtimecode
を
HMAC関数がメッセージとして受け取れるバイト列にすることです。
TOTPを求める関数をget_totpとしましょう。
def get_totp(secret_key, timecode, digits=6):
secret_keyをデコードします。
secret = decode_secret_key(secret_key)
整数のtimecode
をバイト列のtimecode_array
に変換します。timecode_array
は8バイトです。
変換には標準ライブラリのstruct
モジュールを使用します。
struct
モジュールはint
やfloat
といった抽象化されたオブジェクトとメモリ上での配列に
相互変換するパッケージです。TOTPの計算ではtimecode
をlonglong
の8バイトに変換するところと
のちに出てくるダイジェストを4バイトのlong
に変換するところで使用します。
エンディアンはどちらもビッグエンディアンです。
struct
モジュールのpack
関数は与えられたフォーマットで整数をバイト列に変換します。
フォーマットの'>'
はビッグエンディアンを意味し、'Q'
はlonglong
(8バイト)を指定します。
timecode_array = struct.pack('>Q', timecode)
最後の引数であるハッシュ関数の種類はhashlib
モジュールから取得します。
とりあえずhashlib
モジュールをインポートします。
import hashlib
sha1
はhashlib.sha1
で取得できます。
ここまででHMACの準備ができました。 ではHMACの計算をしていきましょう。 HMACモジュールをインポートします。
import hmac
hm = hmac.new(secret, timecode_array, hashlib.sha1)
hm
オブジェクトからダイジェストを求めます。
digest = hm.digest()
Python3では下記のように直接ダイジェストを求めることができます。
また、この関数ではhashlib
を使わずにアルゴリズムの名称(文字列)で指定することができます。
digest = hmac.digest(secret, timecode_array, 'SHA1')
得られたダイジェストは前述のとおりSHA1
なので20バイト(160ビット)のバイナリデータです。