Triển khai Google Authenticator bằng Python


104

Tôi đang cố gắng sử dụng mật khẩu dùng một lần có thể được tạo bằng ứng dụng Google Authenticator .

Google Authenticator làm gì

Về cơ bản, Google Authenticator triển khai hai loại mật khẩu:

  • HOTP - Mật khẩu dùng một lần dựa trên HMAC, có nghĩa là mật khẩu được thay đổi với mỗi cuộc gọi, tuân theo RFC4226
  • TOTP - Mật khẩu dùng một lần dựa trên thời gian, thay đổi trong khoảng thời gian 30 giây một lần (theo tôi biết).

Google Authenticator cũng có sẵn dưới dạng Nguồn mở tại đây: code.google.com/p/google-authenticator

Mã hiện tại

Tôi đã tìm kiếm các giải pháp hiện có để tạo mật khẩu HOTP và TOTP, nhưng không tìm thấy nhiều. Mã tôi có là đoạn mã sau chịu trách nhiệm tạo 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

Vấn đề tôi đang gặp phải là mật khẩu tôi tạo bằng mã trên không giống với mật khẩu được tạo bằng ứng dụng Google Authenticator dành cho Android. Mặc dù tôi đã thử nhiều intervals_nogiá trị (chính xác là 10000 đầu tiên, bắt đầu bằng intervals_no = 0), secretbằng với khóa được cung cấp trong ứng dụng GA.

Câu hỏi tôi có

Câu hỏi của tôi là:

  1. Tôi đang làm gì sai?
  2. Làm cách nào để tạo HOTP và / hoặc TOTP bằng Python?
  3. Có thư viện Python nào hiện có cho việc này không?

Tóm lại: vui lòng cung cấp cho tôi bất kỳ manh mối nào sẽ giúp tôi triển khai xác thực Google Authenticator trong mã Python của tôi.

Câu trả lời:


152

Tôi muốn đặt một phần thưởng cho câu hỏi của mình, nhưng tôi đã thành công trong việc tạo ra giải pháp. Vấn đề của tôi dường như được kết nối với giá trị của secretkhóa không chính xác (nó phải là tham số chính xác cho base64.b32decode()chức năng).

Dưới đây tôi đăng giải pháp làm việc đầy đủ với giải thích về cách sử dụng nó.

Mã sau là đủ. Tôi cũng đã tải nó lên GitHub dưới dạng mô-đun riêng biệt được gọi là onetimepass (có sẵn tại đây: 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)

Nó có hai chức năng:

  • get_hotp_token() tạo mã thông báo một lần (mã này sẽ mất hiệu lực sau khi sử dụng một lần),
  • get_totp_token() tạo mã thông báo dựa trên thời gian (thay đổi trong khoảng thời gian 30 giây),

Thông số

Khi nói đến các thông số:

  • secret là một giá trị bí mật mà máy chủ (tập lệnh ở trên) và máy khách (Google Authenticator, bằng cách cung cấp nó làm mật khẩu trong ứng dụng),
  • intervals_no là số được tăng lên sau mỗi lần tạo mã thông báo (điều này có thể được giải quyết trên máy chủ bằng cách kiểm tra một số số nguyên hữu hạn sau lần kiểm tra thành công cuối cùng trong quá khứ)

Làm thế nào để sử dụng nó

  1. Tạo secret(nó phải là tham số chính xác cho base64.b32decode()) - tốt nhất là 16 ký tự (không có =dấu), vì nó chắc chắn hoạt động cho cả script và Google Authenticator.
  2. Sử dụng get_hotp_token()nếu bạn muốn mật khẩu dùng một lần hết hiệu lực sau mỗi lần sử dụng. Trong Google Authenticator, loại mật khẩu tôi đã đề cập là dựa trên bộ đếm. Để kiểm tra nó trên máy chủ, bạn sẽ cần phải kiểm tra một số giá trị intervals_no(vì bạn không có người cách ly mà người dùng đã không tạo pass giữa các yêu cầu vì lý do nào đó), nhưng không nhỏ hơn intervals_nogiá trị làm việc cuối cùng (vì vậy bạn có thể nên lưu trữ nó một vài nơi).
  3. Sử dụng get_totp_token(), nếu bạn muốn mã thông báo hoạt động trong khoảng thời gian 30 giây. Bạn phải đảm bảo rằng cả hai hệ thống đều có thời gian đặt chính xác (nghĩa là cả hai đều tạo cùng một dấu thời gian Unix trong bất kỳ thời điểm nhất định nào).
  4. Đảm bảo bảo vệ bạn khỏi cuộc tấn công vũ phu. Nếu mật khẩu dựa trên thời gian được sử dụng, thì việc thử 1000000 giá trị trong vòng chưa đầy 30 giây sẽ cho 100% cơ hội đoán được mật khẩu. Trong trường hợp mật khẩu dựa trên HMAC (HOTP), nó có vẻ còn tồi tệ hơn.

Thí dụ

Khi sử dụng mã sau cho mật khẩu dùng một lần HMAC:

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

bạn sẽ nhận được kết quả sau:

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

tương ứng với các mã được tạo bởi ứng dụng Google Authenticator (ngoại trừ nếu ngắn hơn 6 ký tự, ứng dụng sẽ thêm các số 0 vào đầu để đạt độ dài 6 ký tự).


3
@burhan: Nếu bạn cần mã, tôi cũng đã tải nó lên GitHub (tại đây: https://github.com/tadeck/onetimepass ), vì vậy sẽ khá dễ dàng để sử dụng nó trong các dự án dưới dạng mô-đun riêng biệt. Thưởng thức!
Tadeck

1
Tôi gặp sự cố với mã này vì 'bí mật' mà dịch vụ tôi đang cố gắng đăng nhập cung cấp là chữ thường, không phải chữ hoa. Thay đổi dòng 4 thành "key = base64.b32decode (secret, True)" đã khắc phục sự cố cho tôi.
Chris Moore

1
@ChrisMoore: Tôi đã cập nhật mã casefold=Trueđể mọi người không gặp vấn đề tương tự bây giờ. Cảm ơn vì đầu vào của bạn.
Tadeck

3
Tôi vừa được một trang web cung cấp bí mật về 23 ký tự. Mã của bạn không thành công với "TypeError: Incorrect padding" khi tôi đưa ra bí mật đó. Chèn bí mật, như thế này, đã khắc phục sự cố: key = base64.b32decode (secret + '====' [: 3 - ((len (secret) -1)% 4)], True)
Chris Moore

3
cho python 3: thay đổi: ord(h[19]) & 15thành: o = h[19] & 15 Cảm ơn BTW
Orville

6

Tôi muốn một tập lệnh python để tạo mật khẩu TOTP. Vì vậy, tôi đã viết script python. Đây là cách thực hiện của tôi. Tôi có thông tin này trên wikipedia và một số kiến ​​thức về HOTP và TOTP để viết tập lệnh này.

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)

Thú vị, nhưng bạn có thể muốn làm cho người đọc dễ hiểu hơn. Vui lòng đặt tên biến có ý nghĩa hơn hoặc thêm docstrings. Ngoài ra, theo dõi PEP8 có thể giúp bạn được hỗ trợ nhiều hơn. Bạn đã so sánh hiệu suất giữa hai giải pháp này? Câu hỏi cuối cùng: giải pháp của bạn có tương thích với Google Authenticator không (như câu hỏi về giải pháp cụ thể này)?
Tadeck

@Tadeck Tôi đã thêm một số nhận xét. Và tôi đã hoàn thành công việc của mình bằng cách sử dụng script này. vì vậy, nó sẽ hoạt động hoàn hảo.
Anish Shah
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.