Implementasi Google Authenticator dengan Python

104

Saya mencoba menggunakan sandi sekali pakai yang dapat dibuat menggunakan aplikasi Google Authenticator .

Apa yang dilakukan Google Authenticator

Pada dasarnya, Google Authenticator menerapkan dua jenis kata sandi:

  • HOTP - Kata Sandi Satu Kali Berbasis HMAC, yang berarti kata sandi diubah dengan setiap panggilan, sesuai dengan RFC4226 , dan
  • TOTP - Kata Sandi Satu Kali Berbasis Waktu, yang berubah untuk setiap periode 30 detik (sejauh yang saya tahu).

Google Authenticator juga tersedia sebagai Open Source di sini: code.google.com/p/google-authenticator

Kode saat ini

Saya sedang mencari solusi yang ada untuk menghasilkan kata sandi HOTP dan TOTP, tetapi tidak menemukan banyak. Kode yang saya miliki adalah potongan berikut yang bertanggung jawab untuk menghasilkan 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

Masalah yang saya hadapi adalah bahwa kata sandi yang saya buat menggunakan kode di atas tidak sama dengan yang dibuat menggunakan aplikasi Google Authenticator untuk Android. Meskipun saya mencoba beberapa intervals_nonilai (tepatnya pertama 10000, dimulai dengan intervals_no = 0), dengan secretsama dengan kunci yang disediakan dalam aplikasi GA.

Pertanyaan yang saya miliki

Pertanyaan saya adalah:

  1. Apa yang saya lakukan salah?
  2. Bagaimana cara membuat HOTP dan / atau TOTP dengan Python?
  3. Apakah ada pustaka Python yang ada untuk ini?

Singkatnya: tolong beri saya petunjuk apa pun yang akan membantu saya menerapkan otentikasi Google Authenticator dalam kode Python saya.

Tadeck
sumber

Jawaban:

152

Saya ingin memberikan hadiah untuk pertanyaan saya, tetapi saya telah berhasil menciptakan solusi. Masalah saya sepertinya terkait dengan nilai secretkunci yang salah (itu harus parameter yang benar untuk base64.b32decode()fungsi).

Di bawah ini saya memposting solusi kerja lengkap dengan penjelasan tentang cara menggunakannya.

Kode

Kode berikut sudah cukup. Saya juga telah mengunggahnya ke GitHub sebagai modul terpisah yang disebut onetimepass (tersedia di sini: 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)

Ini memiliki dua fungsi:

  • get_hotp_token() menghasilkan token satu kali (yang seharusnya menjadi tidak valid setelah penggunaan tunggal),
  • get_totp_token() menghasilkan token berdasarkan waktu (diubah dalam interval 30 detik),

Parameter

Ketika datang ke parameter:

  • secret adalah nilai rahasia yang diketahui server (skrip di atas) dan klien (Google Authenticator, dengan memberikannya sebagai sandi dalam aplikasi),
  • intervals_no adalah jumlah yang bertambah setelah setiap pembuatan token (ini mungkin harus diselesaikan di server dengan memeriksa beberapa bilangan bulat yang terbatas setelah yang terakhir berhasil diperiksa di masa lalu)

Bagaimana cara menggunakannya

  1. Hasilkan secret(harus merupakan parameter yang benar untuk base64.b32decode()) - sebaiknya 16-karakter (tanpa =tanda), karena pasti berfungsi untuk skrip dan Google Authenticator.
  2. Gunakan get_hotp_token()jika Anda ingin kata sandi satu kali tidak valid setelah digunakan. Di Google Authenticator jenis kata sandi ini saya sebutkan berdasarkan penghitung. Untuk memeriksanya di server Anda perlu memeriksa beberapa nilai intervals_no(karena Anda tidak memiliki jaminan bahwa pengguna tidak menghasilkan pass di antara permintaan untuk beberapa alasan), tetapi tidak kurang dari nilai kerja terakhir intervals_no(jadi Anda mungkin harus menyimpannya di suatu tempat).
  3. Gunakan get_totp_token(), jika Anda ingin token berfungsi dalam interval 30 detik. Anda harus memastikan kedua sistem memiliki set waktu yang benar (artinya keduanya menghasilkan stempel waktu Unix yang sama pada saat tertentu).
  4. Pastikan untuk melindungi diri Anda dari serangan brute force. Jika kata sandi berbasis waktu digunakan, kemudian mencoba nilai 1000000 dalam waktu kurang dari 30 detik memberikan 100% kemungkinan menebak kata sandi. Dalam kasus passowrds berbasis HMAC (HOTPs) tampaknya menjadi lebih buruk.

Contoh

Saat menggunakan kode berikut untuk kata sandi berbasis HMAC satu kali:

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

Anda akan mendapatkan hasil sebagai berikut:

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

yang sesuai dengan token yang dihasilkan oleh aplikasi Google Authenticator (kecuali jika lebih pendek dari 6 tanda, aplikasi menambahkan nol ke awal untuk mencapai panjang 6 karakter).

Tadeck
sumber
3
@burhan: Jika Anda memerlukan kodenya, saya telah mengunggahnya juga ke GitHub (di sini: https://github.com/tadeck/onetimepass ), jadi seharusnya cukup mudah menggunakannya dalam proyek sebagai modul terpisah. Nikmati!
Tadeck
1
Saya punya masalah dengan kode ini karena 'rahasia' yang saya berikan oleh layanan yang saya coba masuki adalah huruf kecil, bukan huruf besar. Mengubah baris 4 menjadi "key = base64.b32decode (rahasia, True)" memperbaiki masalah bagi saya.
Chris Moore
1
@ChrisMoore: Saya telah memperbarui kode casefold=Truesehingga orang tidak akan mengalami masalah serupa sekarang. Terima kasih atas masukan Anda.
Tadeck
3
Saya baru saja diberi rahasia 23 karakter oleh sebuah situs. Kode Anda gagal dengan "TypeError: Padding salah" ketika saya memberikan rahasia itu. Melapisi rahasia, seperti ini, memperbaiki masalah: key = base64.b32decode (secret + '====' [: 3 - ((len (secret) -1)% 4)], True)
Chris Moore
3
untuk python 3: ubah: ord(h[19]) & 15menjadi: o = h[19] & 15 Terima kasih BTW
Orville
6

Saya ingin skrip python untuk menghasilkan kata sandi TOTP. Jadi, saya menulis skrip python. Ini adalah implementasi saya. Saya punya info ini di wikipedia dan sedikit pengetahuan tentang HOTP dan TOTP untuk menulis skrip ini.

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)
Anish Shah
sumber
Menarik, tetapi Anda mungkin ingin membuatnya lebih dimengerti oleh pembaca. Harap buat nama variabel lebih bermakna, atau tambahkan docstrings. Selain itu, mengikuti PEP8 mungkin memberi Anda lebih banyak dukungan. Apakah Anda membandingkan kinerja antara kedua solusi ini? Pertanyaan terakhir: apakah solusi Anda kompatibel dengan Google Authenticator (seperti pertanyaannya tentang solusi khusus ini)?
Tadeck
@Tadeck Saya telah menambahkan beberapa komentar. Dan saya menyelesaikan pekerjaan saya menggunakan skrip ini. jadi ya, itu harus bekerja dengan sempurna.
Anish Shah