#8
scrypt
加密
安全 加密 scrypt Django
2021-10-03
在 Python 标准库 hashlib
中见过 scrypt
,说是 3.6 引入。
然后,这两天看到的资讯说是 Django 4 将加入了 scrypt
做密码哈希,据说安全性比之前的 PBKDF2
更好。
PS: 由于需要 OpenSSL 1.1+ 的支持,以及会消耗更多的内存,所以不是默认选项。
coding in a complicated world
scrypt
加密
在 Python 标准库 hashlib
中见过 scrypt
,说是 3.6 引入。
然后,这两天看到的资讯说是 Django 4 将加入了 scrypt
做密码哈希,据说安全性比之前的 PBKDF2
更好。
PS: 由于需要 OpenSSL 1.1+ 的支持,以及会消耗更多的内存,所以不是默认选项。
之前写过一篇,Python 2.7 + Django 1.x 版本的(链接地址),看 Django 的时候,想起来,拿出来跑一下,发现跑不起来,这里更新一下,用 Python3.8 + Django2.2 / Django 3.2 试试。
PS: 依然没有什么实际意义,只是玩玩而已。
Django 2.2 到 Django 3.2 的变更对这个单文件中使用的地方完全没有影响,代码公用。
和之前那份代码基本上相同,就不贴出来了,如果感兴趣可以点开:代码
Django 项目组试图在保持向后兼容的基础之上,对阻塞部分进行改造,使之支持异步(通过装饰器的方式)。
包括 Session、Auth、ORM 与 Handlers 等。
ASGI 模式将 Django 作为原生异步应用程序运行,原有的 WSGI 模式将围绕每个 Django 调用运行单个事件循环,以使异步处理层与同步服务器兼容。
在这个改造的过程中,每个特性都会经历以下三个实现阶段:
Django 3.0 开始提供运行 ASGI 应用支持,让 Django 逐渐具备异步功能。做了这一改动后,Django 现在会感知到异步事件循环,并将阻止从异步上下文调用标记为 “异步不安全” 的代码(例如 ORM 操作),如果开发者之前使用的是异步代码,则可能会触发。如果看到 SynchronousOnlyOperation 错误,可以仔细检查代码并将数据库操作移到同步子线程中。
其它方面,Django 现在支持 MariaDB 10.1 及更高版本;新的 ExclusionConstraint 类可以在 PostgreSQL 上添加排除约束;输出 BooleanField 的表达式现在可以直接在 QuerySet 过滤器中使用,而无需先注解然后对注解进行过滤;自定义枚举类型 TextChoices、IntegerChoices 和 Choices 现在可用作定义 Field.choices 的方法。
需要特别注意的是:自从 2.2 之后,Django 将不再支持 Python 3.5。
Ver | Date | Notes |
---|---|---|
1.0 | 2008-09-03 | |
1.1 | 2009-07-29 | |
1.2 | 2010-05-17 | |
1.3 | 2011-03-23 | |
1.4 | 2012-03-23 | LTS |
1.5 | 2013-02-26 | |
1.6 | 2013-11-06 | |
1.7 | 2014-09-02 | |
1.8 | 2015-04-01 | LTS 2018-04-01 |
1.9 | 2015-12-01 | |
1.10 | 2016-08-01 | |
1.11 | 2017-04-04 | LTS 2020-04-01 |
2.0 | 2017-12-02 | |
2.1 | 2018-08-01 | |
2.2 | 2019-04-01 | LTS 2022-04-01 |
3.0 | 2019-12-02 | |
3.1 | 2020-08-04 | |
3.2 | 2021-04-06 | LTS 2024-04-01 |
4.0 | 2021-12-07 | |
4.1 | 2022-08 | |
4.2 | 2023-04 | LTS 2026-04-01 |
5.0 | 2023-12 |
没有什么实际意义,只是玩玩而已。
在 Python 2.7.8 (July 2, 2014),或 Python 3.4 (March 17, 2014) 以上版本,标准库 hashlib 有 pbkdf2_hmac
:
import base64
import hashlib
import random
import secrets
def pbkdf2(password, salt, iterations, dklen=0, digest=None):
if digest is None:
digest = hashlib.sha256
if not dklen:
dklen = None
password = password.encode('utf-8')
salt = salt.encode('utf-8')
return hashlib.pbkdf2_hmac(
digest().name, password, salt, iterations, dklen)
### pbkdf2_sha256 ###
ALGORITHM = 'pbkdf2_sha256'
ITERATIONS = 30000
DIGEST = hashlib.sha256
def encode(password, salt, iterations=None):
assert password is not None
assert salt and '$' not in salt
if not iterations:
iterations = ITERATIONS
hash = pbkdf2(password, salt, iterations, digest=DIGEST)
hash = base64.b64encode(hash).decode('ascii').strip()
return "%s$%d$%s$%s" % (ALGORITHM, iterations, salt, hash)
def decode(encoded):
algorithm, iterations, salt, hash = encoded.split('$', 3)
assert algorithm == ALGORITHM
return {
'algorithm': algorithm,
'hash': hash,
'iterations': int(iterations),
'salt': salt,
}
def verify(password, encoded):
algorithm, iterations, salt, hash = encoded.split('$', 3)
assert algorithm == ALGORITHM
encoded_2 = encode(password, salt, int(iterations))
return secrets.compare_digest(encoded.encode('utf-8'),
encoded_2.encode('utf-8'))
salt_length = 32
# salt = bytes([random.randint(0, 255) for i in range(salt_length + 20)])
salt = random.randbytes(salt_length + 20)
salt = salt.replace(b'$', b'').decode('latin')[:salt_length]
# print(repr(salt))
a = encode('123456', salt)
print(repr(a))
print(decode(a))
# 'pbkdf2_sha256$30000$x\x82¨\x07Ó[Ám¬9V¬\x13·sÉ\x1eEܱ3\x04Ü\x07\x05BÁfM\x9fV×$/jqvasjfZX6c9xCytBVqddAJFve2DXcLWClTAsZvl48='
print(verify('234567', a))
print(verify('123456', a))
https://docs.djangoproject.com/en/2.0/topics/auth/passwords/
在 Django 中,密码哈希存储在 auth_user 表中的 password 字段中。该字段的值包含了算法名称、迭代次数、盐值和哈希值,它们都是使用特定的格式进行编码的。例如,一个密码哈希的值可能如下所示:
pbkdf2_sha256$150000$V7v0fjbhMIhq$Gd/0XuOqK3ib6NBNGVwIKGXcFTUiNbTzdTNN8RiW24E=
用美元符号切割,得到 4 部分:
pbkdf2_sha256
算法名称150000
迭代次数V7v0fjbhMIhq
盐Gd/0XuOqK3ib6NBNGVwIKGXcFTUiNbTzdTNN8RiW24E=
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
'django.contrib.auth.hashers.BCryptPasswordHasher',
]
默认使用的是 PBKDF2PasswordHasher
:
PBKDF2 是基于密钥的密码派生函数,它使用一个伪随机函数(PRF)和一个盐值来从给定密码派生出一个密钥。
PBKDF2 算法的核心是迭代的使用 PRF 函数,将每一次迭代的输出与前一次迭代的输出进行异或,生成最终的派生密钥。
大概逻辑如下:
import hashlib
import binascii
import os
class PBKDF2PasswordHasher:
algorithm = 'pbkdf2_sha256'
iterations = 100000
def salt(self):
return binascii.hexlify(os.urandom(16)).decode()
def encode(self, password, salt):
dk = hashlib.pbkdf2_hmac('sha256', password.encode(), salt.encode(), self.iterations)
return '%d$%s$%s' % (self.iterations, salt, binascii.hexlify(dk).decode())
def verify(self, password, encoded):
iterations, salt, dk = encoded.split('$')
iterations = int(iterations)
hashed_password = self.encode(password, salt)
return hashed_password == encoded
from django.contrib.auth.hashers import make_password
password = '123456'
hashed_password = make_password(password)
from django.contrib.auth.hashers import check_password
is_correct_password = check_password(password, hashed_password)
runserver
端口占用,报错:Errno 10013
运行 django runserver
的时候,出现 Error 10013,即 8000 端口被占用。