TOC

scrypt 加密

在 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))

参考资料与拓展阅读