在 Python 标准库 hashlib
中见过 scrypt
,说是 3.6 引入。
然后,这两天看到的资讯说是 Django 4 将加入了 scrypt
做密码哈希,据说安全性比之前的 PBKDF2
更好。
PS: 由于需要 OpenSSL 1.1+ 的支持,以及会消耗更多的内存,所以不是默认选项。
关于 PBKDF2
可以参考之前的文章:Djanog 密码算法:PBKDF2。
PS: 使用 openssl 创建自签名证书 中关于 PKCS 部分(PKCS#5
)介绍也提到了 PBKDF2
。
特点
需要大量计算从而计算速度慢,不易被攻击,而且由于需要消耗大量内存来存储中间数据,所以大规模并行计算的攻击成本大幅提升。
在密码学中,scrypt(念作“ess crypt”)是 Colin Percival 于 2009 年所发明的密钥派生函数,当初设计用在他所创立的 Tarsnap 服务上。设计时考虑到大规模的客制硬件攻击而刻意设计需要大量内存运算。2016 年,scrypt 算法发布在 RFC 7914。scrypt 的简化版被用在数个密码货币的工作量证明(Proof-of-Work)上。
实现
import hashlib
import base64
import secrets
import random
algorithm = 'scrypt'
block_size = 8
maxmem = 0
parallelism = 1
work_factor = 2 ** 14
def encode(password, salt, n=None, r=None, p=None):
n = n or work_factor
r = r or block_size
p = p or parallelism
hash_ = hashlib.scrypt(
password.encode(),
salt=salt.encode(),
n=n,
r=r,
p=p,
maxmem=maxmem,
dklen=64,
)
hash_ = base64.b64encode(hash_).decode('ascii').strip()
return '%s$%d$%s$%d$%d$%s' % (algorithm, n, salt, r, p, hash_)
def decode(encoded):
algorithm, work_factor, salt, block_size, parallelism, hash_ = encoded.split('$', 6)
assert algorithm == algorithm
return {
'algorithm': algorithm,
'work_factor': int(work_factor),
'salt': salt,
'block_size': int(block_size),
'parallelism': int(parallelism),
'hash': hash_,
}
def verify(password, encoded):
decoded = decode(encoded)
encoded_2 = encode(
password,
decoded['salt'],
decoded['work_factor'],
decoded['block_size'],
decoded['parallelism'],
)
return secrets.compare_digest(encoded.encode('utf-8'),
encoded_2.encode('utf-8'))
salt_length = 32
salt = random.randbytes(salt_length + 20)
salt = salt.replace(b'$', b'').decode('latin')[:salt_length]
a = encode('123456', salt)
print(repr(a))
# 'scrypt$16384$GçÎu\nË\x02Ûx=õôàHì°°ßûáMái\x9fe\x1d`lCk\x8cz$8$1$q/2PGtei89q0ETI+dfC+ExlCtWWJcPoW7tsJLiY4FdqzS8wlyaD7XxJQEoLf/R/XCOltLtAJzVKLDSIx9f4Qdg=='
print(decode(a))
print(verify('234567', a))
print(verify('123456', a))
参考资料与拓展阅读
- 维基百科(en),scrypt
中文词条:scrypt - IETF, RFC 7914: The scrypt Password-Based Key Derivation Function