やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

ワンタイムパスワード生成ライブラリの調査3

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生成の仕組みがわかるかと思ったが、よくわからなかった。

GoogleAuthenticator.cs

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はローカル時刻を使っている。

totp.py

return self.generate_otp(self.timecode(datetime.datetime.now()))

datetime.now()はローカルの現在時刻を取得する関数のはず。私のPCは日本時刻。WinAuthはおそらくUTC。異なる時刻を用いているから、異なるOTPが生成されるのではないか。

WinAuthで生成したOTPならGitHubのTwoFactor認証に成功する。UTCで生成すればWinAuthと同じOTPを出力するはず。

onetimepass

onetimepassもローカル時刻を使っていると思う。

init.py

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

init.pyをみると、デフォルトはSHA1に見える。

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固定なのだろう。

ハッシュコード生成に違いはなさそう。

なぜ?

WinAuthpyotpの結果が違うのはなぜ?

結局わからず。

これ以上は、OTPの詳しい仕様を見て、WinAuthとPyOTPの実装も見るしかない。それは本題から大きく外れる。

OTPの仕様

ググってみた。

buty4649.hatenablog.com

参考にさせていただいた。感謝。残念ながら私はWindowsXP環境なのでコードは試せないが、OTPの仕様に沿って実装できるらしい。

OTP生成仕様にはHOTPとTOTPの2種類あるらしい。

仕様 違い 規格
HOTP 回数ベース RFC4226
TOTP 時間ベース RFC6238

google-authenticator

google-authenticatorGoogleが提供するOTP生成用スマホアプリと思われる。上記規格に基づいて生成されるはず。WinAuth, PyOTP, onetimepassと同じくコードが参考になるはず。

所感

WinAuthの代わりになるライブラリがあれば自動化できると思ったが、代わりになるライブラリがない。

仕様を詳しく把握すればできるのかもしれないが、本筋から外れるためOTPの調査はここまでとする。自動化の目処が立たず無念。