web-dev-qa-db-ja.com

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

Google Authenticator application を使用して生成できるワンタイムパスワードを使用しようとしています。

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値(intervals_no = 0で始まる最初の10000個)を試しましたが、secretはGA app 。

私が持っている質問

私の質問は:

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

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

97
Tadeck

私は自分の質問に報奨金を設定したかったのですが、ソリューションの作成に成功しました。私の問題は、secretキーの誤った値に関連しているように見えました(base64.b32decode()関数の正しいパラメーターでなければなりません)。

以下に、使用方法に関する説明を含む完全な実用的なソリューションを投稿します。

コード

次のコードで十分です。また、GitHubにonetimepassという別のモジュールとしてアップロードしました(ここで入手可能: 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認証システム)が知っている秘密の値です。
  • _intervals_no_は、トークンの各生成後にインクレメントされた数です(これはおそらく、最後に成功した整数を過去にチェックした後、有限数の整数をチェックすることでサーバー上で解決されるはずです)

それを使用する方法

  1. secretbase64.b32decode()の正しいパラメーターである必要があります)-16文字(_=_記号なし)を生成します。これは、スクリプトとGoogle認証システムの両方で確実に機能するためです。
  2. 使用するたびにワンタイムパスワードを無効にする場合は、get_hotp_token()を使用します。 Google Authenticatorで、このタイプのパスワードはカウンターに基づいていると述べました。サーバー上でチェックするには、いくつかの_intervals_no_の値をチェックする必要があります(何らかの理由でユーザーがリクエスト間のパスを生成しなかったことを確認できないため)。ただし、最後に機能する_intervals_no_値(したがって、おそらくどこかに格納する必要があります)。
  3. トークンを30秒間隔で機能させる場合は、get_totp_token()を使用します。両方のシステムに正しい時刻が設定されていることを確認する必要があります(つまり、両方のシステムが特定の瞬間に同じ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文字の長さに到達します)。

146
Tadeck

TOTPパスワードを生成するためのpythonスクリプトが必要でした。そこで、pythonスクリプトを作成しました。これが私の実装です。私はウィキペディアでこの info と、このスクリプトを書くための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)
6
Anish Shah