Python'da Google Authenticator uygulaması


104

Google Authenticator uygulaması kullanılarak oluşturulabilen tek seferlik şifreleri kullanmaya çalışıyorum .

Google Authenticator ne yapar?

Temel olarak, Google Authenticator iki tür şifre uygular:

  • HOTP - HMAC tabanlı Tek Kullanımlık Parola, yani parolanın RFC4226'ya uygun olarak her aramada değiştirildiği anlamına gelir ve
  • TOTP - Her 30 saniyede bir değişen (bildiğim kadarıyla) Zamana dayalı Tek Kullanımlık Parola.

Google Authenticator, burada Açık Kaynak olarak da mevcuttur: code.google.com/p/google-authenticator

Mevcut kod

HOTP ve TOTP şifreleri oluşturmak için mevcut çözümler arıyordum, ancak pek bir şey bulamadım. Sahip olduğum kod, HOTP oluşturmaktan sorumlu aşağıdaki kod parçacığıdır:

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

Karşılaştığım sorun, yukarıdaki kodu kullanarak oluşturduğum şifrenin Android için Google Authenticator uygulamasıyla oluşturulanla aynı olmaması. GA uygulamasında sağlanan anahtara eşit olmak üzere birden fazla intervals_nodeğer denemiş olsam da (tam olarak ilk 10000'de intervals_no = 0) secret.

Sahip olduğum sorular

Sorularım:

  1. Neyi yanlış yapıyorum?
  2. Python'da HOTP ve / veya TOTP'yi nasıl oluşturabilirim?
  3. Bunun için mevcut herhangi bir Python kitaplığı var mı?

Özetlemek gerekirse: lütfen bana Google Authenticator kimlik doğrulamasını Python kodum içinde uygulamama yardımcı olacak herhangi bir ipucu verin.

Yanıtlar:


153

Soruma bir ödül koymak istedim ama çözüm üretmeyi başardım. Sorunum yanlış secretanahtar değeriyle bağlantılı görünüyordu ( base64.b32decode()işlev için doğru parametre olmalı ).

Aşağıda, nasıl kullanılacağına dair açıklamalarla birlikte tam çalışma çözümü gönderiyorum.

Kod

Aşağıdaki kod yeterlidir. Ayrıca GitHub'a onetimepass adı verilen ayrı bir modül olarak yükledim (burada mevcuttur: 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)

İki işlevi vardır:

  • get_hotp_token() tek seferlik jeton üretir (bu, tek kullanımdan sonra geçersiz kılınmalıdır),
  • get_totp_token() zamana göre jeton üretir (30 saniyelik aralıklarla değiştirilir),

Parametreler

Parametrelere gelince:

  • secret sunucu (yukarıdaki komut dosyası) ve istemci (Google Authenticator, uygulama içinde şifre olarak sağlayarak) tarafından bilinen gizli bir değerdir,
  • intervals_no jetonun her neslinden sonra artan sayıdır (bu, geçmişte kontrol edilen son başarılı olandan sonra sonlu sayıdaki tam sayıları kontrol ederek muhtemelen sunucuda çözülmelidir)

Bu nasıl kullanılır

  1. Hem komut dosyası hem de Google Authenticator için kesinlikle çalıştığından, oluşturun secret(için doğru parametre olmalıdır base64.b32decode()) - tercihen 16 karakterli ( =işaret yok ).
  2. Kullanım get_hotp_token()Her kullanımdan sonra geçersiz bir kerelik şifreleri istiyorum. Google Authenticator'da bu tür şifrelerden sayaca bağlı olarak bahsetmiştim. Sunucuda kontrol etmek için birkaç değeri kontrol intervals_noetmeniz gerekir (çünkü kullanıcının herhangi bir nedenle istekler arasında geçişi oluşturmadığına dair hiçbir garantiniz yoktur), ancak son çalışma intervals_nodeğerinden daha az olmamak kaydıyla (bu nedenle muhtemelen saklamalısınız) bir yerde).
  3. get_totp_token()30 saniyelik aralıklarla çalışan bir jeton istiyorsanız kullanın . Her iki sistemin de doğru zaman setine sahip olduğundan emin olmalısınız (yani her ikisinin de herhangi bir anda aynı Unix zaman damgasını oluşturması).
  4. Kendinizi kaba kuvvet saldırılarından koruduğunuzdan emin olun. Zaman bazlı şifre kullanılıyorsa, 1000000 değerin 30 saniyeden daha kısa sürede denenmesi, şifreyi tahmin etme şansını% 100 verir. HMAC tabanlı şifreler (HOTP'ler) durumunda daha da kötü görünüyor.

Misal

Tek seferlik HMAC tabanlı parola için aşağıdaki kodu kullanırken:

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

aşağıdaki sonucu alacaksınız:

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

Google Authenticator uygulaması tarafından oluşturulan jetonlara karşılık gelir (6 işaretten kısa olması dışında, uygulama 6 karakter uzunluğa ulaşmak için başa sıfırlar ekler).


3
@burhan: Koda ihtiyacınız varsa onu GitHub'a da yükledim (burada: https://github.com/tadeck/onetimepass ), bu yüzden ayrı bir modül olarak projeler içinde kullanmak oldukça kolay olmalı. Zevk almak!
Tadeck

1
Bu kodla ilgili bir sorun yaşadım çünkü oturum açmaya çalıştığım hizmet tarafından sağlanan 'gizli' büyük harf değil küçük harfliydi. 4. satırı "key = base64.b32decode (gizli, Doğru)" olarak değiştirmek benim için sorunu çözdü.
Chris Moore

1
@ChrisMoore: Kodu ile güncelledim, casefold=Trueböylece insanlar artık benzer problemler yaşamasın . Girdiniz için teşekkürler.
Tadeck

3
Bir site tarafından bana 23 karakterlik bir sır verildi. Bu sırrı verdiğimde kodunuz "TypeError: Yanlış dolgu" ile başarısız oluyor. Sırrı bu şekilde doldurmak sorunu çözdü: anahtar = base64.b32decode (gizli + '====' [: 3 - ((len (gizli) -1)% 4)], Doğru)
Chris Moore

3
değişim: piton 3 ord(h[19]) & 15içine: o = h[19] & 15 teşekkür BTW
Orville

6

TOTP şifresi oluşturmak için bir python betiği istedim. Ben de python betiğini yazdım. Bu benim uygulamam. Bu senaryoyu yazmak için Wikipedia'da bu bilgiye ve HOTP ve TOTP hakkında biraz bilgiye sahibim.

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)

İlginç, ancak okuyucu için daha anlaşılır hale getirmek isteyebilirsiniz. Lütfen değişken isimlerini daha anlamlı hale getirin veya dokümanlar ekleyin. Ayrıca, PEP8'i takip etmek size daha fazla destek sağlayabilir. Bu iki çözümün performansını karşılaştırdınız mı? Son soru: çözümünüz Google Authenticator ile uyumlu mu (soru bu özel çözümle ilgiliydi)?
Tadeck

@Tadeck Bazı yorumlar ekledim. Ve işlerimi bu senaryoyu kullanarak hallettim. yani evet, mükemmel çalışmalı.
Anish Shah
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.