2016-12-03 70 views
1

我有一个django项目,我想搬到烧瓶。 问题是以与django相同的方式加密和解密密码。 这是可能的,实现相同的加密和解密,如在Django 1.10。那就是我想在烧瓶中以相同的方式创建和验证密码,就像使用django一样。谷歌搜索给了我passlib,但文档不清楚django版本(1.10)。谢谢。django 1.10密码与烧瓶配合使用

回答

1

(Passlib开发商在这里)

Passlib绝对应该能够处理这种情况下,让我知道你的文档的部分不清晰,我可以尝试清除它们! (最新文档是http://passlib.readthedocs.io/en/stable/

这应该有助于开始(假设passlib> = 1.7)。

处理事情的最简单方法是创建一个CryptContext实例,并使用数据库中的所有哈希格式进行配置。它会照顾从那里验证哈希&。

Django的1.10,你可能要像下面这样:

>>> 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"]) 

您可以调整“默认”上面你想新的哈希使用的任何方案 - 即使插入非Django的哈希格式像“bcrypt”进入列表。您还可以删除数据库中不存在的任何内容。

一旦上下文对象存在,只需要调用.hash()来散列密码,W /自动盐代:

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

然后将下面来验证散列:

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

如果需要,passlib的CryptContext tutorial中有更多的细节。

+0

谢谢@ @ @欢呼! –

1

让我们深入一点:

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) 

基本上,我们需要检查如何make_passwordcheck_password的作品,让我们做到这一点:

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) 

和校验密码:

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 

Aaand这只是太多:)让我们专注于haser!

Django的默认散列器是:django.contrib.auth.hashers.PBKDF2PasswordHasher - 如果你的代码不使用默认的,你可以找到所有的人都在django/conf/global_settings.pyPASSWORD_HASHERS

让我们检查什么.verify.encode做散列器对象。

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) 

而这基本上是raw密码核对,编码是一个字符串(在这种格式的数据库存储的密码:pbkdf2_sha256 $$$(不记得确切此)

反正这里发生了什么 - 。 Django的创建的新编码的密码(从原始密码),并检查结果是一样提供一个

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) 

这是从一个原始密码创建密码的方法。基本上你只需要执行pbkdf2它可以在django/utils/crypto.py中找到,据我所知它只使用标准的hashlib库。由于Django是开源的 - 你可以,因为它是:)借这个代码(可能))

所以总结以上所有:

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 

顺便说一句,请记住,做一个你的密码时盐应是随机的。你自己检查.salt方法。 ;) 快乐编码!