PythonでのGoogle認証システムの実装


104

Google Authenticatorアプリケーションを使用して生成できるワンタイムパスワードを使用しようとしています

Google認証システムの機能

基本的に、Google認証システムは2種類のパスワードを実装します。

  • HOTP -パスワードはに準拠して、各呼び出しで変更されることを意味HMACベースのワンタイムパスワード、RFC4226、および
  • TOTP-時間ベースのワンタイムパスワード。(私の知る限り)30秒ごとに変更されます。

Google Authenticatorはオープンソースとしても利用できます:code.google.com/p/google-authenticator

現在のコード

HOTPおよびTOTPパスワードを生成するための既存のソリューションを探していましたが、あまり見つかりませんでした。私が持っているコードは、HOTPの生成を担当する次のスニペットです。

import hmac, base64, struct, hashlib, time

def get_token(secret, digest_mode=hashlib.sha1, intervals_no=None):
    if intervals_no == None:
        intervals_no = int(time.time()) // 30
    key = base64.b32decode(secret)
    msg = struct.pack(">Q", intervals_no)
    h = hmac.new(key, msg, digest_mode).digest()
    o = ord(h[19]) & 15
    h = (struct.unpack(">I", h[o:o+4])[0] & 0x7fffffff) % 1000000
    return h

私が直面している問題は、上記のコードを使用して生成したパスワードが、Android用のGoogle認証システムアプリを使用して生成したものと同じではないことです。私は複数のintervals_no値を試しましたが(GA で始まる正確に最初の10000 intervals_no = 0)、secretGAアプリ内で提供されるキーと同じです。

私が持っている質問

私の質問は:

  1. 何が悪いのですか?
  2. PythonでHOTPやTOTPを生成するにはどうすればよいですか?
  3. このための既存のPythonライブラリはありますか?

まとめると、Pythonコード内にGoogle認証システム認証を実装するのに役立つ手がかりを教えてください。

回答:


152

私は自分の質問に報奨金を設定したかったのですが、解決策を作成することに成功しました。私の問題は、secretキーの誤った値に関連しているようです(base64.b32decode()関数の正しいパラメーターである必要があります)。

以下に私はそれを使用する方法の説明を含む完全な作業ソリューションを投稿します。

コード

次のコードで十分です。また、onetimepassと呼ばれる個別のモジュールとしてGitHubにアップロードしました(ここから入手できます:https : //github.com/tadeck/onetimepass)。

import hmac, base64, struct, hashlib, time

def get_hotp_token(secret, intervals_no):
    key = base64.b32decode(secret, True)
    msg = struct.pack(">Q", intervals_no)
    h = hmac.new(key, msg, hashlib.sha1).digest()
    o = ord(h[19]) & 15
    h = (struct.unpack(">I", h[o:o+4])[0] & 0x7fffffff) % 1000000
    return h

def get_totp_token(secret):
    return get_hotp_token(secret, intervals_no=int(time.time())//30)

2つの機能があります。

  • get_hotp_token() ワンタイムトークンを生成します(1回使用すると無効になります)。
  • get_totp_token() 時間に基づいてトークンを生成(30秒間隔で変更)、

パラメーター

パラメータに関しては:

  • secret サーバー(上記のスクリプト)とクライアント(Google Authenticator、アプリケーション内でパスワードとして提供すること)が知っている秘密の値です。
  • intervals_no トークンが生成されるたびに増加する数値です(これは、サーバーで最後に成功した整数が過去にチェックされた後で、有限数の整数をチェックすることによって解決される可能性があります)。

どうやって使うのですか

  1. 生成secret(の正しいパラメータである必要がありますbase64.b32decode())- =スクリプトとGoogle認証システムの両方で確実に機能するため、できれば16文字(記号なし)です。
  2. 使用get_hotp_token()するたびにワンタイムパスワードを無効にする場合に使用します。Google Authenticatorでは、このタイプのパスワードはカウンターに基づいて述べました。サーバーでそれをチェックするには、いくつかの値をチェックする必要がありますintervals_no(ユーザーが何らかの理由でリクエスト間のパスを生成しなかったという保証がないため)、最後の有効な値以上である必要がありintervals_noます(したがって、おそらく保存する必要があります)どこかに)。
  3. get_totp_token()トークンを30秒間隔で機能させる場合は、を使用します。両方のシステムで正しい時刻が設定されていることを確認する必要があります(つまり、どちらのシステムでも、常に同じUnixタイムスタンプが生成されます)。
  4. 総当たり攻撃から身を守ってください。時間ベースのパスワードが使用されている場合、30秒未満で1000000の値を試行すると、パスワードを推測する確率が100%になります。HMACベースのパスワード(HOTP)の場合は、さらに悪いようです。

ワンタイムHMACベースのパスワードに次のコードを使用する場合:

secret = 'MZXW633PN5XW6MZX'
for i in xrange(1, 10):
    print i, get_hotp_token(secret, intervals_no=i)

次の結果が得られます。

1 448400
2 656122
3 457125
4 35022
5 401553
6 581333
7 16329
8 529359
9 171710

これは、Google認証システムアプリによって生成されたトークンに対応します(6つの記号より短い場合を除き、アプリは先頭にゼロを追加して6文字の長さにします)。


3
@burhan:コードが必要な場合は、GitHub(ここ:https : //github.com/tadeck/onetimepass)にもアップロードしたので、プロジェクト内で別のモジュールとして使用するのは非常に簡単です。楽しい!
Tadeck

1
ログインしようとしているサービスから提供された「秘密」が大文字ではなく小文字だったため、このコードに問題がありました。「key = base64.b32decode(secret、True)」を読み取るように4行目を変更すると、問題が解決しました。
Chris Moore

1
@ChrisMoore:コードを更新したcasefold=Trueので、今は同様の問題は発生しないはずです。ご入力いただきありがとうございます。
Tadeck

3
私はサイトから23文字の秘密をちょうど与えられました。シークレットを指定すると、コードが「TypeError:Incorrect padding」で失敗します。このように、シークレットをパディングして問題を修正しました:key = base64.b32decode(secret + '====' [:3-((len(secret)-1)%4)]、True)
クリスムーア

3
Python 3の場合:change: ord(h[19]) & 15into:o = h[19] & 15 Thanks BTW
Orville

6

PythonスクリプトでTOTPパスワードを生成する必要がありました。それで、私はpythonスクリプトを書きました。これは私の実装です。ウィキペディアにこの情報があり、このスクリプトを書くためのHOTPとTOTPに関する知識があります。

import hmac, base64, struct, hashlib, time, array

def Truncate(hmac_sha1):
    """
    Truncate represents the function that converts an HMAC-SHA-1
    value into an HOTP value as defined in Section 5.3.

    http://tools.ietf.org/html/rfc4226#section-5.3

    """
    offset = int(hmac_sha1[-1], 16)
    binary = int(hmac_sha1[(offset * 2):((offset * 2) + 8)], 16) & 0x7fffffff
    return str(binary)

def _long_to_byte_array(long_num):
    """
    helper function to convert a long number into a byte array
    """
    byte_array = array.array('B')
    for i in reversed(range(0, 8)):
        byte_array.insert(0, long_num & 0xff)
        long_num >>= 8
    return byte_array

def HOTP(K, C, digits=6):
    """
    HOTP accepts key K and counter C
    optional digits parameter can control the response length

    returns the OATH integer code with {digits} length
    """
    C_bytes = _long_to_byte_array(C)
    hmac_sha1 = hmac.new(key=K, msg=C_bytes, digestmod=hashlib.sha1).hexdigest()
    return Truncate(hmac_sha1)[-digits:]

def TOTP(K, digits=6, window=30):
    """
    TOTP is a time-based variant of HOTP.
    It accepts only key K, since the counter is derived from the current time
    optional digits parameter can control the response length
    optional window parameter controls the time window in seconds

    returns the OATH integer code with {digits} length
    """
    C = long(time.time() / window)
    return HOTP(K, C, digits=digits)

おもしろいですが、読者にとって理解しやすいものにしたいと思うかもしれません。変数名をより意味のあるものにするか、ドキュメント文字列を追加してください。また、PEP8をフォローすると、より多くのサポートを受けることができます。これら2つのソリューションのパフォーマンスを比較しましたか?最後の質問:あなたのソリューションはGoogle Authenticatorと互換性がありますか(質問はこの特定のソリューションに関するものでした)?
Tadeck 14

@Tadeck私はいくつかのコメントを追加しました。そして、私はこのスクリプトを使用して自分の物事を成し遂げました。そうそう、それは完全に動作するはずです。
アニッシュシャー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.