การใช้งาน Google Authenticator ใน Python


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

ปัญหาที่ฉันพบคือรหัสผ่านที่ฉันสร้างโดยใช้รหัสด้านบนไม่เหมือนกับที่สร้างขึ้นโดยใช้แอป Google Authenticator สำหรับ Android แม้ว่าฉันจะลองหลายintervals_noค่า (10,000 แรกเริ่มต้นด้วยintervals_no = 0) โดยsecretให้เท่ากับคีย์ที่ให้ไว้ในแอป GA

คำถามที่ฉันมี

คำถามของฉันคือ:

  1. ผมทำอะไรผิดหรือเปล่า?
  2. ฉันจะสร้าง HOTP และ / หรือ TOTP ใน Python ได้อย่างไร
  3. มีไลบรารี Python สำหรับสิ่งนี้หรือไม่?

สรุป: โปรดแจ้งเบาะแสที่จะช่วยให้ฉันใช้การตรวจสอบสิทธิ์ Google Authenticator ภายในรหัส Python ของฉัน

คำตอบ:


153

ฉันต้องการกำหนดค่าตอบแทนสำหรับคำถามของฉัน แต่ฉันประสบความสำเร็จในการสร้างโซลูชัน ปัญหาของฉันดูเหมือนจะเชื่อมต่อกับค่า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)

มีสองฟังก์ชั่น:

  • get_hotp_token() สร้างโทเค็นแบบครั้งเดียว (ซึ่งควรทำให้เป็นโมฆะหลังจากใช้ครั้งเดียว)
  • get_totp_token() สร้างโทเค็นตามเวลา (เปลี่ยนในช่วงเวลา 30 วินาที)

พารามิเตอร์

เมื่อพูดถึงพารามิเตอร์:

  • secret เป็นค่าลับที่เซิร์ฟเวอร์ (สคริปต์ด้านบน) และไคลเอนต์ (Google Authenticator ระบุเป็นรหัสผ่านภายในแอปพลิเคชัน)
  • intervals_no คือจำนวนที่เพิ่มขึ้นหลังจากการสร้างโทเค็นแต่ละรุ่น (สิ่งนี้ควรได้รับการแก้ไขบนเซิร์ฟเวอร์โดยการตรวจสอบจำนวนเต็มจำนวน จำกัด หลังจากที่ตรวจสอบครั้งสุดท้ายที่ประสบความสำเร็จในอดีต)

วิธีการใช้งาน

  1. สร้างsecret(ต้องเป็นพารามิเตอร์ที่ถูกต้องสำหรับbase64.b32decode()) - ควรเป็น 16-char (ไม่มี=เครื่องหมาย) เนื่องจากใช้ได้กับทั้งสคริปต์และ Google Authenticator
  2. ใช้get_hotp_token()หากคุณต้องการให้รหัสผ่านแบบใช้ครั้งเดียวไม่ถูกต้องหลังการใช้งานแต่ละครั้ง ใน Google Authenticator รหัสผ่านประเภทนี้ที่ฉันกล่าวถึงตามตัวนับ สำหรับการตรวจสอบบนเซิร์ฟเวอร์คุณจะต้องตรวจสอบค่าต่างๆintervals_no(เนื่องจากคุณไม่มีการกักกันว่าผู้ใช้ไม่ได้สร้างการส่งผ่านระหว่างคำขอด้วยเหตุผลบางประการ) แต่ต้องไม่น้อยกว่าintervals_noค่าการทำงานล่าสุด(ดังนั้นคุณควรจัดเก็บไว้ บางแห่ง).
  3. ใช้get_totp_token()ถ้าคุณต้องการให้โทเค็นทำงานในช่วงเวลา 30 วินาที คุณต้องตรวจสอบให้แน่ใจว่าทั้งสองระบบตั้งเวลาที่ถูกต้อง (หมายความว่าทั้งสองระบบสร้างการประทับเวลา Unix เดียวกันในช่วงเวลาใดเวลาหนึ่ง)
  4. อย่าลืมป้องกันตัวเองจากการโจมตีที่ดุร้าย หากใช้รหัสผ่านตามเวลาการลอง 1000000 ค่าในเวลาน้อยกว่า 30 วินาทีจะมีโอกาสเดารหัสผ่านได้ 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 ตัวอักษร)


3
@burhan: หากคุณต้องการรหัสฉันได้อัปโหลดไปยัง GitHub ด้วย (ที่นี่: https://github.com/tadeck/onetimepass ) ดังนั้นจึงควรใช้งานภายในโครงการเป็นโมดูลแยกต่างหาก สนุก!
Tadeck

1
ฉันมีปัญหากับรหัสนี้เนื่องจาก 'ความลับ' ที่ฉันได้รับจากบริการที่ฉันพยายามเข้าสู่ระบบเป็นตัวพิมพ์เล็กไม่ใช่ตัวพิมพ์ใหญ่ การเปลี่ยนบรรทัดที่ 4 เพื่ออ่าน "key = base64.b32decode (secret, True)" ช่วยแก้ปัญหาให้ฉัน
Chris Moore

1
@ChrisMoore: ฉันได้อัปเดตโค้ดด้วยcasefold=Trueดังนั้นผู้คนไม่ควรมีปัญหาที่คล้ายกันในตอนนี้ ขอบคุณสำหรับข้อมูลของคุณ
Tadeck

3
ฉันเพิ่งได้รับความลับ 23 ตัวจากไซต์ รหัสของคุณล้มเหลวด้วยข้อความ "TypeError: Incorrect padding" เมื่อฉันให้ความลับนั้น การเติมความลับเช่นนี้แก้ไขปัญหา: key = base64.b32decode (secret + '====' [: 3 - ((len (secret) -1)% 4)], True)
Chris Moore

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)

น่าสนใจ แต่คุณอาจต้องการให้ผู้อ่านเข้าใจมากขึ้น โปรดตั้งชื่อตัวแปรให้มีความหมายมากขึ้นหรือเพิ่ม docstrings นอกจากนี้การติดตาม PEP8 อาจทำให้คุณได้รับการสนับสนุนมากขึ้น คุณเปรียบเทียบประสิทธิภาพระหว่างสองโซลูชันนี้หรือไม่ คำถามสุดท้าย: โซลูชันของคุณเข้ากันได้กับ Google Authenticator หรือไม่ (เนื่องจากคำถามเกี่ยวกับโซลูชันเฉพาะนี้)
Tadeck

@Tadeck ฉันได้เพิ่มความคิดเห็น และฉันก็ทำสิ่งต่างๆให้เสร็จโดยใช้สคริปต์นี้ ใช่มันควรจะทำงานได้อย่างสมบูรณ์
Anish Shah
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.