OTPについて調べたがWinAuthの代わりは見つけられず。じつは前回までのライブラリ調査コードは、今回の検討をしたからGoogleサーバから時刻を取得したのだった。
前回まで
WinAuthで二要素認証のワンタイムパスワードを生成させてみた - やってみる
GitHubで二要素認証してみた - やってみる
二要素認証でGitHubAPIを叩いてみた - やってみる
http://ytyaru.hatenablog.com/entry/2017/02/19/000000
http://ytyaru.hatenablog.com/entry/2017/02/21/000000
WinAuth
WinAuthのコードを見ればOTP生成の仕組みがわかるかと思ったが、よくわからなかった。
private const string TIME_SYNC_URL = "http://www.google.com";
この一行がポイントかもしれない。
TOTPはOTPを生成するときに時間を使うらしい。WinAuthでGoogleを選択すると、"http://www.google.com"のサーバー時刻を使用してOTPを生成するのだろう。"http://www.google.com"はおそらくUTC協定世界時刻と思われる。日本時刻の9時間前だった。
PyOTP
これに対して、PyOTPはローカル時刻を使っている。
return self.generate_otp(self.timecode(datetime.datetime.now()))
datetime.now()
はローカルの現在時刻を取得する関数のはず。私のPCは日本時刻。WinAuthはおそらくUTC。異なる時刻を用いているから、異なるOTPが生成されるのではないか。
WinAuthで生成したOTPならGitHubのTwoFactor認証に成功する。UTCで生成すればWinAuthと同じOTPを出力するはず。
onetimepass
onetimepassもローカル時刻を使っていると思う。
if clock is None: clock = time.time()
time.time()はエポック時刻(1970/01/01 00:00:00から現在までの経過秒)を返す。UTCでなくローカル時刻までの経過秒と思われる。
Googleサーバの時刻
GoogleAuthenticator.csを見ると、http://www.google.com
サーバのHTTPヘッダDate
から時刻を取得している。
curlコマンドでHTTPヘッダを取得してみた。
curl -I http://www.google.com HTTP/1.1 302 Found Cache-Control: private Content-Type: text/html; charset=UTF-8 Location: http://www.google.co.jp/?gfe_rd=cr&ei=gUdzWJLXKOfC8gfc3KWQCg Content-Length: 261 Date: Mon, 09 Jan 2017 08:19:13 GMT
私のPC(日本)では2017/01/09 17:19時点での結果。日本からみて9時間前の時間になるらしい。
これをPyOTPに反映させたいのだが、引数で渡せないため、コードを変更する必要がある。
PyOTPコードの所在
C:\Python34\Lib\site-packages\pyotp
にあった。
pipでインストールすると、(Pythonルート)/Lib/site-packages/(パッケージ)
に保存されるらしい。
とりあえず別の適当なところにコピペする。
importしたとき同名のモジュールが複数あると、ローカルかPython配下、どちらが優先されるのだろうか。
コード修正
時刻渡しAPI追加
totp.py
def now(self): """ Generate the current time OTP @return [Integer] the OTP as an integer """ return self.generate_otp(self.timecode(datetime.datetime.now()))
をベースにして、時刻を引数にとれる関数を追加する。
def now(self, base_datetime): """ Generate the current time OTP @param [datetime] base datetime @return [Integer] the OTP as an integer """ return self.generate_otp(self.timecode(base_datetime))
呼出元で、米国時間(http://www.google.com サーバ時刻)を擬似的に生成して渡す。
import pyotp two_factor_secret = 'secret_value' totp = pyotp.TOTP(two_factor_secret) n = datetime.datetime.now() - datetime.timedelta(hours=9) otp = totp.now(n) print("otp = {0}".format(otp))
失敗。やはりWinAuthと異なるOTPを吐き出す。
Googleサーバから時刻取得する
#!python3 #encoding:utf-8 import pyotp import datetime import requests def get_datetime(): r = requests.get('http://www.google.com') print(r.headers['Date']) # Mon, 09 Jan 2017 08:59:36 GMT format = '%a, %d %b %Y %H:%M:%S GMT' dt = datetime.datetime.strptime(r.headers['Date'], format) print(dt) return dt two_factor_secret = 'dubueq7po5mb4wjf' totp = pyotp.TOTP(two_factor_secret) #n = datetime.datetime.now() - datetime.timedelta(hours=9) n = get_datetime() #otp = totp.now() otp = totp.now(n) print("otp = {0}".format(otp)) print("time = {0:%Y-%m-%d %H:%M:%S}".format(n))
失敗。WinAuthとは違うOTPが出る。
SHA
OTPを算出するとき、SHAというハッシュ関数を用いているらしい。
これにはいくつかあって、セキュリティ強度に違いがある。OTPの仕様のデフォルトはSHA1らしい。
もしかすると、WinAuthとPyOTPでは、SHAが違うのかもしれない。コードを見てみた。
PyOTP
otp.pyをみると、デフォルトはSHA1
のようだ。
class OTP(object): def __init__(self, s, digits=6, digest=hashlib.sha1):
呼出時もとくに指定していないはずだからSHA1
だろう。
onetimepass
def get_totp( secret, as_string=False, digest_method=hashlib.sha1, token_length=6, interval_length=30, clock=None, ):
呼出時にhashlib.sha1
を変更していないはずだからSHA1
だろう。
WinAuth
Authenticator.csをみると、デフォルトはSHA1
のようだ。
protected virtual string CalculateCode(bool resync = false, long interval = -1) HMac hmac = new HMac(new Sha1Digest());
Sha1Digest()
とあるから、おそらくSHA1固定なのだろう。
ハッシュコード生成に違いはなさそう。
なぜ?
結局わからず。
これ以上は、OTPの詳しい仕様を見て、WinAuthとPyOTPの実装も見るしかない。それは本題から大きく外れる。
OTPの仕様
ググってみた。
参考にさせていただいた。感謝。残念ながら私はWindowsXP環境なのでコードは試せないが、OTPの仕様に沿って実装できるらしい。
OTP生成仕様にはHOTPとTOTPの2種類あるらしい。
仕様 | 違い | 規格 |
---|---|---|
HOTP | 回数ベース | RFC4226 |
TOTP | 時間ベース | RFC6238 |
google-authenticator
google-authenticatorはGoogleが提供するOTP生成用スマホアプリと思われる。上記規格に基づいて生成されるはず。WinAuth, PyOTP, onetimepassと同じくコードが参考になるはず。
所感
WinAuthの代わりになるライブラリがあれば自動化できると思ったが、代わりになるライブラリがない。
仕様を詳しく把握すればできるのかもしれないが、本筋から外れるためOTPの調査はここまでとする。自動化の目処が立たず無念。