我有一个django项目,我想转移到烧瓶。 问题是以与django相同的方式加密和解密密码。 这是可能的,实现与django 1.10相同的加密和解密。这就是我想在烧瓶中以与django相同的方式创建和验证密码。谷歌搜索给了我passlib,但文档不清楚django版本(1.10)。谢谢。
答案 0 :(得分:2)
让我们挖一点:
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_password
和check_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.py
PASSWORD_HASHERS
中找到所有代码
让我们检查一下.verify
和.encode
对Hasher对象的行为。
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
密码检查,编码是一个字符串(db存储密码的格式为: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
不过,请记住,在输入密码时,您的盐应该是随机的。自己检查haser上的.salt
方法。 ;)
快乐的编码!
答案 1 :(得分:2)
(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()来哈希密码,自动生成盐:
>>> hash = pwd_context.hash("foo")
>>> hash
'pbkdf2_sha256$29000$uzyeK0HKJIBR$XQtpjc9nfTdteF1fpk1Jk7FCePwB7S2JLuggiE8UBE4='
接下来验证哈希:
>>> pwd_context.verify("foo", hash)
True
>>> pwd_context.verify("bar", hash)
False
如果需要,可以在passlib的CryptContext tutorial中提供更多详细信息。