2016-12-03 28 views
1

Ich habe ein Django-Projekt, das ich in Kolben verschieben möchte. Das Problem ist, das Passwort auf die gleiche Weise zu verschlüsseln und zu entschlüsseln wie Django. Wird das möglich sein, die gleiche Ver- und Entschlüsselung wie in Django 1.10 zu implementieren. Das ist, ich möchte Passwort in der gleichen Weise in Flask erstellen und überprüfen, wie es mit Django getan wird. Googeln gab mir Passlib, aber der Doc ist nicht klar über die Django-Version (1.10). Danke.django 1.10 Passwort mit Kolben verwendet werden

Antwort

1

(Passlib Entwickler hier)

Passlib sollte definitiv in der Lage sein, diesen Fall zu behandeln, lassen Sie mich wissen, welche Teile der Dokumentation nicht klar waren, und ich kann versuchen, sie zu reinigen! (Neueste Dokumente sind unter http://passlib.readthedocs.io/en/stable/)

Dies sollte Ihnen den Einstieg (PassLib> = 1.7 vorausgesetzt) ​​helfen.

Der einfachste Weg, um Dinge zu handhaben, ist eine CryptContext-Instanz zu erstellen, die mit allen Hash-Formaten konfiguriert ist, die Sie in Ihrer Datenbank haben.Es wird sich um Hashing & Verifikation von dort kümmern.

Für Django 1.10, möchten Sie wahrscheinlich so etwas wie die folgenden:

>>> from passlib.context import CryptContext 
>>> pwd_context = CryptContext([ 
...  default="django_pbkdf2_sha256", 
...  schemes=["django_argon2", "django_bcrypt", "django_bcrypt_sha256", 
...    "django_pbkdf2_sha256", "django_pbkdf2_sha1", 
...    "django_disabled"]) 

Sie können die ‚default‘ anpassen oben auf je nachdem, was Schema, das Sie wollen neue Hashes verwenden - auch Einfügen eines Nicht-django Hash-Format wie "bcrypt" in die Liste. Sie können auch alle entfernen, die nicht in Ihrer Datenbank vorhanden sind.

Sobald das Kontext-Objekt vorhanden ist, rufen Sie einfach .hash(), um das Passwort hash, w/automatische Salz Generation:

>>> hash = context.hash("foo") 
>>> hash 
'pbkdf2_sha256$29000$uzyeK0HKJIBR$XQtpjc9nfTdteF1fpk1Jk7FCePwB7S2JLuggiE8UBE4=' 

Und dann die folgenden einen Hash zu überprüfen:

>>> context.verify("foo", hash) 
True 
>>> context.verify("bar", hash) 
False 

Es gibt mehr Details in passlib CryptContext tutorial, wenn Sie brauchen.

+0

danke @eli prost !! –

1

Lässt ein wenig graben:

django/contrib/auth/base_user.py:

class AbstractBaseUser(models.Model): 
... 

def set_password(self, raw_password): 
    self.password = make_password(raw_password) 
    self._password = raw_password 

def check_password(self, raw_password): 
    """ 
    Return a boolean of whether the raw_password was correct. Handles 
    hashing formats behind the scenes. 
    """ 
    def setter(raw_password): 
     self.set_password(raw_password) 
     # Password hash upgrades shouldn't be considered password changes. 
     self._password = None 
     self.save(update_fields=["password"]) 
    return check_password(raw_password, self.password, setter) 

Grundsätzlich müssen wir überprüfen, wie make_password und check_password Werke, auf diese Weise können tun:

def make_password(password, salt=None, hasher='default'): 
""" 
Turn a plain-text password into a hash for database storage 

Same as encode() but generates a new random salt. 
If password is None then a concatenation of 
UNUSABLE_PASSWORD_PREFIX and a random string will be returned 
which disallows logins. Additional random string reduces chances 
of gaining access to staff or superuser accounts. 
See ticket #20079 for more info. 
""" 
    if password is None: 
     return UNUSABLE_PASSWORD_PREFIX + get_random_string(UNUSABLE_PASSWORD_SUFFIX_LENGTH) 
    hasher = get_hasher(hasher) 

    if not salt: 
     salt = hasher.salt() 

    return hasher.encode(password, salt) 

Und das Kontroll Passwort:

def check_password(password, encoded, setter=None, preferred='default'): 
""" 
Returns a boolean of whether the raw password matches the three 
part encoded digest. 

If setter is specified, it'll be called when you need to 
regenerate the password. 
""" 
    if password is None or not is_password_usable(encoded): 
     return False 

    preferred = get_hasher(preferred) 
    hasher = identify_hasher(encoded) 

    hasher_changed = hasher.algorithm != preferred.algorithm 
    must_update = hasher_changed or preferred.must_update(encoded) 
    is_correct = hasher.verify(password, encoded) 

    # If the hasher didn't change (we don't protect against enumeration if it 
    # does) and the password should get updated, try to close the timing gap 
    # between the work factor of the current encoded password and the default 
    # work factor. 
    if not is_correct and not hasher_changed and must_update: 
     hasher.harden_runtime(password, encoded) 

    if setter and is_correct and must_update: 
     setter(password) 
return is_correct 

Aa und das ist einfach zu viel :) Lasst uns auf den Haser konzentrieren!

Der django Standard Hasher ist: django.contrib.auth.hashers.PBKDF2PasswordHasher - wenn Ihr Code Verwendung nicht einen Standard, Sie alle von ihnen in django/conf/global_settings.py unter den PASSWORD_HASHERS

finden Lets überprüfen, was .verify und .encode auf dem Objekt Hasher tun.

def verify(self, password, encoded): 
    algorithm, iterations, salt, hash = encoded.split('$', 3) 
    assert algorithm == self.algorithm 
    encoded_2 = self.encode(password, salt, int(iterations)) 
    return constant_time_compare(encoded, encoded_2) 

Und das ist im Grunde raw Kennwortprüfmerker, ist die codierte ein String (db gespeicherte Passwort in einem solchen Format: pbkdf2_sha256 $$$ (erinnere mich nicht genau)

Wie dem auch sei, was hier geschieht -. die django erstellt neues verschlüsseltes Passwort (aus rohem Passwort) und prüfen, ob die Ergebnisse die gleichen wie eine bereitgestellt

def encode(self, password, salt, iterations=None): 
    assert password is not None 
    assert salt and '$' not in salt 
    if not iterations: 
     iterations = self.iterations 
    hash = pbkdf2(password, salt, iterations, digest=self.digest) 
    hash = base64.b64encode(hash).decode('ascii').strip() 
    return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash) 

und das ist ein Verfahren, das ein Passwort aus einem rohen Passwort erstellen wird;. Im Grunde brauchen Sie nur die Implementierung von pbkdf2, die in django/utils/crypto.py gefunden werden kann und soweit ich weiß verwendet es nur die Standard-hashlib-Bibliothek. Als django Open Source ist - können Sie diesen Code ausleihen, wie es ist :) (wahrscheinlich;))

Also alles summiert die oben:

import hashlib 
import hmac 
import struct 
import binascii 
import base64 

def _long_to_bin(x, hex_format_string): 
    """ 
    Convert a long integer into a binary string. 
    hex_format_string is like "%020x" for padding 10 characters. 
    """ 
    return binascii.unhexlify((hex_format_string % x).encode('ascii')) 


def _bin_to_long(x): 
    """ 
    Convert a binary string into a long integer 

    This is a clever optimization for fast xor vector math 
    """ 
    return int(binascii.hexlify(x), 16) 


def pbkdf2(password, salt, iterations, dklen=0, digest=None): 
    """ 
    Implements PBKDF2 as defined in RFC 2898, section 5.2 

    HMAC+SHA256 is used as the default pseudo random function. 

    As of 2014, 100,000 iterations was the recommended default which took 
    100ms on a 2.7Ghz Intel i7 with an optimized implementation. This is 
    probably the bare minimum for security given 1000 iterations was 
    recommended in 2001. This code is very well optimized for CPython and 
    is about five times slower than OpenSSL's implementation. Look in 
    django.contrib.auth.hashers for the present default, it is lower than 
    the recommended 100,000 because of the performance difference between 
    this and an optimized implementation. 
    """ 
    assert iterations > 0 
    if not digest: 
     digest = hashlib.sha256 
    password = password 
    salt = salt 
    hlen = digest().digest_size 
    if not dklen: 
     dklen = hlen 
    if dklen > (2 ** 32 - 1) * hlen: 
     raise OverflowError('dklen too big') 
    l = -(-dklen // hlen) 
    r = dklen - (l - 1) * hlen 

    hex_format_string = "%%0%ix" % (hlen * 2) 
    inner, outer = digest(), digest() 
    if len(password) > inner.block_size: 
     password = digest(password).digest() 
    password += b'\x00' * (inner.block_size - len(password)) 
    inner.update(password.translate(hmac.trans_36)) 
    outer.update(password.translate(hmac.trans_5C)) 

    def F(i): 
     u = salt + struct.pack(b'>I', i) 
     result = 0 
     for j in range(int(iterations)): 
      dig1, dig2 = inner.copy(), outer.copy() 
      dig1.update(u) 
      dig2.update(dig1.digest()) 
      u = dig2.digest() 
      result ^= _bin_to_long(u) 
     return _long_to_bin(result, hex_format_string) 

    T = [F(x) for x in range(1, l)] 
    return b''.join(T) + F(l)[:r] 

def make_password(password, salt, iterations=2, digest=hashlib.sha256): 
    hash = pbkdf2(password=password, salt=salt, iterations=iterations, digest=digest) 
    hash = base64.b64encode(hash).decode('ascii').strip() 
    return "%s$%d$%s$%s" % ('pbkdf2_sha256', iterations, salt, hash) 

def check_password(raw_password, encoded): 
    algorithm, iterations, salt, hash = encoded.split('$', 3) 
    encoded_2 = make_password(raw_password, salt, int(iterations)) 
    return encoded_2 == encoded 

pwd = make_password(password='test', salt='salt', iterations=2, digest=hashlib.sha256) 
# pbkdf2_sha256$2$salt$paft68X11fyh4GG9uMnHtk6pY9QFojoiDckOvLG6GoI= 

print(check_password('test1', pwd)) 
# False 
print(check_password('test', pwd)) 
# True 

Btw, denken Sie daran, dass, wenn ein Passwort Ihr Salz machen sollte sei zufällig. Überprüfen Sie die .salt Methode auf haser selbst. ;) Glückliche Codierung!