Python 从数据库中存储和检索密码的最安全方法
Posted
技术标签:
【中文标题】Python 从数据库中存储和检索密码的最安全方法【英文标题】:Python's safest method to store and retrieve passwords from a database 【发布时间】:2011-02-04 01:15:13 【问题描述】:希望将用户名和密码存储在数据库中,并且想知道这样做最安全的方法是什么。我知道我必须在某处使用盐,但不确定如何安全地生成它或如何应用它来加密密码。一些示例 Python 代码将不胜感激。谢谢。
【问题讨论】:
这里有很多信息:***.com/questions/1183161/… 【参考方案1】:将密码+盐存储为哈希和盐。看看 Django 是如何做到的:basic docs 和 source。
在数据库中,他们将<type of hash>$<salt>$<hash>
存储在单个字符字段中。您还可以将这三个部分存储在单独的字段中。
设置密码的功能:
def set_password(self, raw_password):
import random
algo = 'sha1'
salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5]
hsh = get_hexdigest(algo, salt, raw_password)
self.password = '%s$%s$%s' % (algo, salt, hsh)
get_hexdigest 只是一些散列算法的薄包装。您可以为此使用 hashlib。类似hashlib.sha1('%s%s' % (salt, hash)).hexdigest()
以及检查密码的功能:
def check_password(raw_password, enc_password):
"""
Returns a boolean of whether the raw_password was correct. Handles
encryption formats behind the scenes.
"""
algo, salt, hsh = enc_password.split('$')
return hsh == get_hexdigest(algo, salt, raw_password)
【讨论】:
这真的很有帮助。非常感谢。 可能会更好 - 您不使用恒定时间字符串比较(这里不是很重要,但如果用户进行散列,例如安全 cookie,那么您必须使用恒定时间字符串比较) , 并且 sha1 可以被强制用于可预测的(即大多数人的)密码。 这些函数是类函数,但是OP没有要求类函数。 请注意,此答案中的代码使用 random.random 作为盐的来源,根据文档docs.python.org/3/library/random.html,出于安全相关目的,盐被认为是不安全的。此外,它使用单轮 sha1,这对于密码散列是不安全的,并且根据 Django 文档,默认情况下 Django 不再这样做。正如我在回答中提到的,最好使用来自 docs.python.org/3/library/hashlib.html#key-derivation 的 PBKDF2 功能。 PBKDF2也是目前Django使用的默认算法。【参考方案2】:我认为最好为此使用专门用于散列密码的函数。我在这里解释了一些原因:https://***.com/a/10948614/893857
现在标准库有一个dedicated section in the documentation for hashing password。它甚至提到你应该从像os.urandom()
这样的加密安全随机源获取你的盐。
【讨论】:
谢谢! passlib - 快速简单。 :-)【参考方案3】:我在这里回答了这个问题:https://***.com/a/18488878/1661689,@Koffie 也是如此。
我不知道如何强调接受的答案并不安全。它比纯文本更好,也比未加盐的哈希更好,但它仍然极易受到字典甚至暴力攻击。相反,请使用 SLOW KDF,例如 bcrypt(或至少 PBKDF2 迭代 10,000 次)
【讨论】:
您是否建议存储正确的加盐、散列密码是一种极易受到攻击的做法?您不能对加盐哈希使用字典攻击。 没有。我建议 SHA-1 的单次迭代,如上面的代码所示,不是“正确加盐和散列”。您可以对加盐散列使用字典攻击,散列越快,每个 pw 的尝试速度就越快。您希望攻击成功需要 1 天还是 64,000 天? SHA-x 散列在自定义硬件 (ASIC) 上非常快。 owasp.org/index.php/…***.com/questions/13545677/… @rz 说“看看 Django 是如何做到的”,然后显示完全不同的代码。 Django 确实 做对了:github.com/django/django/blob/master/django/contrib/auth/…【参考方案4】:如果您对应用程序的两个端点都有足够的控制权,那么绝对最好的方法是使用PAK-Z+。
(编辑:原版推荐SRP,但PAK-Z+有安全证明。)
【讨论】:
【参考方案5】:对于烧瓶应用程序或任何 python 应用程序,您可以使用 werkzeug WSGI Web 应用程序库,它为您提供了使用盐和不同类型的算法生成和解码密码的选项,格式如下:method$salt$hash
默认盐长度为 16,使用的算法为 sha256
例如。
from werkzeug.security import generate_password_hash, check_password_hash
raw_pwd = 'mySecurePassword'
# genreates the encrypted password
hashed_pwd = generate_password_hash(raw_pwd)
# to verify the password
print(check_password_hash(hashed_pwd, raw_pwd)) # return boolean value after validating password
您可以在此处阅读有关 werkzeug 安全性的更多信息:https://werkzeug.palletsprojects.com/en/2.0.x/utils/#module-werkzeug.security
【讨论】:
【参考方案6】:这是一种更简单的方法(取自 effbot),只要长度大于 8 的密码不会有问题*:
import crypt
import random, string
def getsalt(chars = string.letters + string.digits):
# generate a random 2-character 'salt'
return random.choice(chars) + random.choice(chars)
用于生成密码:
crypt.crypt("password", getsalt())
*:长度大于 8 的密码从右到下被剥离为 8 个字符长
【讨论】:
crypt 我认为加密超过 8 个字符的密码有问题以上是关于Python 从数据库中存储和检索密码的最安全方法的主要内容,如果未能解决你的问题,请参考以下文章