Python에서 Google Authenticator 구현


104

Google Authenticator 애플리케이션을 사용하여 생성 할 수있는 일회용 비밀번호를 사용하려고 합니다 .

Google Authenticator의 기능

기본적으로 Google Authenticator는 두 가지 유형의 비밀번호를 구현합니다.

  • 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 Authenticator 앱을 사용하여 생성 한 비밀번호와 동일하지 않다는 것입니다. 여러 intervals_no값 (정확히 처음 10000,으로 시작 intervals_no = 0)을 시도했지만 secretGA 앱에서 제공된 키 와 동일합니다.

내가 가진 질문

내 질문은 다음과 같습니다.

  1. 내가 뭘 잘못하고 있죠?
  2. 파이썬에서 HOTP 및 / 또는 TOTP를 어떻게 생성 할 수 있습니까?
  3. 이에 대한 기존 Python 라이브러리가 있습니까?

요약하자면, Python 코드 내에서 Google Authenticator 인증을 구현하는 데 도움이되는 단서를 제공해주세요.

답변:


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)

두 가지 기능이 있습니다.

  • get_hotp_token() 일회용 토큰 생성 (1 회 사용 후 무효화되어야 함)
  • get_totp_token() 시간에 따라 토큰을 생성합니다 (30 초 간격으로 변경됨).

매개 변수

매개 변수와 관련하여 :

  • secret 서버 (위 스크립트)와 클라이언트 (Google Authenticator, 애플리케이션 내에서 비밀번호로 제공)에 알려진 비밀 값입니다.
  • intervals_no 토큰이 생성 될 때마다 증가하는 숫자입니다 (과거에 마지막으로 성공한 정수를 확인한 후 한정된 정수 수를 확인하여 서버에서 해결해야 함).

사용 방법

  1. 생성 secret(올바른 매개 변수 여야 함 base64.b32decode())- =스크립트와 Google Authenticator 모두에서 작동하므로 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 Authenticator 앱에서 생성 한 토큰에 해당합니다 (6 개 미만의 기호 인 경우 앱은 6 자 길이에 도달하기 위해 시작 부분에 0을 추가합니다).


3
@burhan : 코드가 필요한 경우 GitHub (여기 : https://github.com/tadeck/onetimepass ) 에도 업로드 했으므로 프로젝트 내에서 별도의 모듈로 사용하기가 매우 쉽습니다. 즐겨!
Tadeck

1
로그인하려는 서비스에서 제공 한 '비밀'이 대문자가 아닌 소문자이기 때문에이 코드에 문제가있었습니다. 4 번 줄을 "key = base64.b32decode (secret, True)"로 변경하면 문제가 해결되었습니다.
크리스 무어

1
@ChrisMoore : casefold=True사람들이 지금 비슷한 문제를 겪지 않도록 코드를 업데이트했습니다 . 귀하의 의견에 감사드립니다.
Tadeck 2011

3
사이트에서 방금 23 자 비밀을 받았습니다. 암호를 지정하면 "TypeError : Incorrect padding"과 함께 코드가 실패합니다. 비밀을 패딩하면 다음과 같은 문제가 해결되었습니다. key = base64.b32decode (secret + '===='[: 3-((len (secret) -1) % 4)], True)
Chris Moore

3
for python 3 : change : ord(h[19]) & 15into : o = h[19] & 15 Thanks BTW
Orville

6

TOTP 비밀번호를 생성하는 파이썬 스크립트를 원했습니다. 그래서 저는 파이썬 스크립트를 작성했습니다. 이것은 내 구현입니다. 위키피디아에 대한 이 정보 와이 스크립트를 작성하기위한 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을 따르면 더 많은 지원을받을 수 있습니다. 이 두 솔루션의 성능을 비교 했습니까? 마지막 질문 : 귀하의 솔루션이 Google Authenticator와 호환됩니까 (이 특정 솔루션에 대한 질문 이었으므로)?
Tadeck 2014

@Tadeck 몇 가지 의견을 추가했습니다. 이 스크립트를 사용하여 작업을 완료했습니다. 네, 완벽하게 작동합니다.
Anish Shah
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.