在 Python 中加盐和散列密码

Posted

技术标签:

【中文标题】在 Python 中加盐和散列密码【英文标题】:Salt and hash a password in Python 【发布时间】:2012-03-24 13:07:25 【问题描述】:

此代码应该使用盐对密码进行哈希处理。盐和散列密码被保存在数据库中。密码本身不是。

鉴于操作的敏感性,我想确保一切都符合犹太教规。

import hashlib
import base64
import uuid

password = 'test_password'
salt     = base64.urlsafe_b64encode(uuid.uuid4().bytes)


t_sha = hashlib.sha512()
t_sha.update(password+salt)
hashed_password =  base64.urlsafe_b64encode(t_sha.digest())

【问题讨论】:

你为什么要对盐进行 b64 编码?直接使用盐然后 b64 将两者一起编码t_sha.digest() + salt 会更简单。当您解码盐分哈希密码后,您可以再次拆分盐分,因为您知道解码后的哈希密码正好是 32 个字节。 @Duncan - 我对 salt 进行了 base64 编码,因此我可以对其进行强大的操作,而不必担心奇怪的问题。 “字节”版本会作为字符串工作吗?如果是这种情况,那么我也不需要 base64 编码 t_sha.digest() 。我可能不会将散列密码和 salt 保存在一起,因为这看起来有点复杂且可读性差一些。 如果您使用的是 Python 2.x,那么 bytes 对象将作为字符串很好地工作。 Python 对字符串中可以包含的内容没有任何限制。但是,如果您将字符串传递给任何外部代码(例如数据库),则可能不适用。 Python 3.x 区分了字节类型和字符串,因此在这种情况下,您不希望在 salt 上使用字符串操作。 我不能告诉你如何在 python 中做到这一点,但普通的 SHA-512 是一个糟糕的选择。使用慢速散列,例如 PBKDF2、bcrypt 或 scrypt。 旁注:我建议不要使用 UUID 作为加密随机性的来源。是的,CPython is cryptographically secure 使用的实现,但这不是由 Python 的规范 nor the UUID spec 和 vulnerable implementations exist 规定的。如果您的代码库在没有安全 UUID4 的情况下使用 Python 实现运行,您将削弱安全性。这可能不太可能发生,但改用secrets 并不需要任何成本。 【参考方案1】:

根据这个问题的其他答案,我使用 bcrypt 实现了一种新方法。

为什么要使用 bcrypt

如果我理解正确,使用bcrypt 而不是SHA512 的论点是bcrypt 的设计速度很慢。 bcrypt 还可以选择在第一次生成散列密码时调整您希望它有多慢:

# The '12' is the number that dictates the 'slowness'
bcrypt.hashpw(password, bcrypt.gensalt( 12 ))

缓慢是可取的,因为如果恶意方获得了包含散列密码的表,那么暴力破解它们会困难得多。

实施

def get_hashed_password(plain_text_password):
    # Hash a password for the first time
    #   (Using bcrypt, the salt is saved into the hash itself)
    return bcrypt.hashpw(plain_text_password, bcrypt.gensalt())

def check_password(plain_text_password, hashed_password):
    # Check hashed password. Using bcrypt, the salt is saved into the hash itself
    return bcrypt.checkpw(plain_text_password, hashed_password)

注意事项

我可以很容易地在 linux 系统中安装这个库:

pip install py-bcrypt

但是,我在 Windows 系统上安装它时遇到了更多麻烦。看来需要补丁了。请参阅此 Stack Overflow 问题:py-bcrypt installing on win 7 64bit python

【讨论】:

12 是 gensalt 的默认值 根据pypi.python.org/pypi/bcrypt/3.1.0,bcrypt 的最大密码长度为 72 字节。 除此之外的任何字符都将被忽略。 因此,他们建议首先使用加密散列函数进行散列,然后对散列进行 base64 编码(有关详细信息,请参阅链接)。旁注:py-bcrypt 似乎是旧的 pypi 包,此后已重命名为 bcrypt 散列是可以的,但 check_password 在 djano 中总是给出 false 这在 Python 3.8.5 中对我有用,但我必须将密码编码为字节,如下所示:plain_text_password.encode('utf-8')【参考方案2】:

编辑:这个答案是错误的。 SHA512 的单次迭代是fast,这使得它不适合用作密码散列函数。请改用此处的其他答案之一。


我觉得很好。但是,我很确定您实际上并不需要 base64。你可以这样做:

import hashlib, uuid
salt = uuid.uuid4().hex
hashed_password = hashlib.sha512(password + salt).hexdigest()

如果不造成困难,您可以通过将盐和散列密码存储为原始字节而不是十六进制字符串,在数据库中获得更有效的存储。为此,请将hex 替换为bytes,将hexdigest 替换为digest

【讨论】:

是的,十六进制可以正常工作。我更喜欢 base64,因为字符串更短一些。在较短的字符串上传递和执行操作更有效。 你不能反转它,你永远不能反转密码。这就是为什么我们散列它而不加密它。如果您需要将输入密码与存储的密码进行比较,请对输入进行哈希处理并比较哈希值。如果您对密码进行加密,任何拥有该密钥的人都可以解密并看到它。不安全 uuid.uuid4().hex 每次生成都不一样。如果您无法取回相同的 uuid,您将如何比较密码以进行检查? @LittleBobbyTables 我认为salt 存储在数据库中,并且也存储了咸散列密码。 @LittleBobbyTables ,盐值与用户名以及他/她相应密码的哈希值一起存储在数据库中。当用户键入密码时,它实际上是经过哈希处理的,这个哈希值与盐值连接起来,存储在数据库中。如果匹配,则认为登录成功。【参考方案3】:

编辑:

这个答案中建议的库现在已经过时了,这个答案中提到的hashlib key derivation functionality:https://***.com/a/56915300/893857是现在使用的一个很好的建议。

原始答案 明智的做法是不要自己编写加密货币,而是使用 passlib 之类的东西:https://passlib.readthedocs.io/en/stable/#

以安全的方式编写加密代码很容易搞砸。令人讨厌的是,对于非加密代码,您经常会在程序崩溃后不工作时立即注意到它。使用加密代码时,您通常只有在为时已晚并且您的数据已被泄露时才发现。因此,我认为最好使用由其他人编写的包,该包由熟悉该主题并基于实战测试协议的人编写。

passlib 也有一些不错的功能,这些功能使其易于使用,并且如果旧协议被破坏,也可以轻松升级到更新的密码哈希协议。

此外,单轮 sha512 更容易受到字典攻击。 sha512 的设计速度很快,当尝试安全地存储密码时,这实际上是一件坏事。其他人已经对所有此类问题进行了长期而认真的思考,因此您最好利用这一点。

【讨论】:

我认为使用 crypo 库的建议很好,但是 OP 已经在使用 hashlib,这是一个也在 Python 标准库中的加密库(与 passlib 不同)。如果我处于 OP 的情况,我会继续使用 hashlib。 @dghubble hashlib 用于加密哈希函数。 passlib 用于安全存储密码。它们不是一回事(尽管很多人似乎都这么认为……然后他们的用户密码就被破解了)。 如果有人想知道:passlib 生成自己的盐,存储在返回的哈希字符串中(至少对于某些方案,例如 BCrypt+SHA256) - 所以你不需要担心它。【参考方案4】:

为了在 Python 3 中工作,您需要使用 UTF-8 编码,例如:

hashed_password = hashlib.sha512(password.encode('utf-8') + salt.encode('utf-8')).hexdigest()

否则你会得到:

回溯(最近一次通话最后一次): 文件“”,第 1 行,在 hashed_pa​​ssword = hashlib.sha512(密码 + salt).hexdigest() TypeError: Unicode 对象必须在散列之前进行编码

【讨论】:

没有。不要使用任何 sha 散列函数来散列密码。使用类似 bcrypt 的东西。原因见其他问题的 cmets。 @josch 哪个 cmets? @CoolCloud ctrl+f "为什么要使用 bcrypt" @josch 太好了,谢谢 :)【参考方案5】:

从 Python 3.4 开始,标准库中的 hashlib 模块包含 key derivation 函数,这些函数“为安全密码散列而设计”

所以使用其中一个,例如 hashlib.pbkdf2_hmac,使用 os.urandom 生成的盐:

from typing import Tuple
import os
import hashlib
import hmac

def hash_new_password(password: str) -> Tuple[bytes, bytes]:
    """
    Hash the provided password with a randomly-generated salt and return the
    salt and hash to store in the database.
    """
    salt = os.urandom(16)
    pw_hash = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 100000)
    return salt, pw_hash

def is_correct_password(salt: bytes, pw_hash: bytes, password: str) -> bool:
    """
    Given a previously-stored salt and hash, and a password provided by a user
    trying to log in, check whether the password is correct.
    """
    return hmac.compare_digest(
        pw_hash,
        hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 100000)
    )

# Example usage:
salt, pw_hash = hash_new_password('correct horse battery staple')
assert is_correct_password(salt, pw_hash, 'correct horse battery staple')
assert not is_correct_password(salt, pw_hash, 'Tr0ub4dor&3')
assert not is_correct_password(salt, pw_hash, 'rosebud')

注意:

使用 16 字节盐和 100000 次 PBKDF2 迭代符合 Python 文档中建议的最小数量。进一步增加迭代次数将使您的哈希计算速度变慢,因此更安全。 os.urandom 始终使用加密安全的随机源 hmac.compare_digest,在is_correct_password 中使用,基本上只是字符串的== 运算符,但没有短路能力,这使其免受定时攻击。那个probably doesn't really provide any extra security value,但它也没有伤害,所以我继续使用它。

关于什么是好的密码散列的理论以及适合散列密码的其他函数列表,请参阅https://security.stackexchange.com/q/211/29805。

【讨论】:

【参考方案6】: 如果您需要使用现有系统存储的哈希,

passlib 似乎很有用。如果您可以控制格式,请使用现代哈希,例如 bcrypt 或 scrypt。这时候,bcrypt 似乎从 python 中使用起来要容易得多。

passlib 支持 bcrypt,建议安装 py-bcrypt 作为后端:http://pythonhosted.org/passlib/lib/passlib.hash.bcrypt.html

如果您不想安装 passlib,也可以直接使用py-bcrypt。自述文件有基本使用示例。

另见:How to use scrypt to generate hash for password and salt in Python

【讨论】:

【参考方案7】:

我不想恢复旧线程,但是...任何想要使用现代最新安全解决方案的人,请使用 argon2。

https://pypi.python.org/pypi/argon2_cffi

它赢得了密码哈希竞赛。 (https://password-hashing.net/) 比 bcrypt 好用,比 bcrypt 更安全。

【讨论】:

【参考方案8】:

首先导入:-

import hashlib, uuid

然后在你的方法中根据这个改变你的代码:

uname = request.form["uname"]
pwd=request.form["pwd"]
salt = hashlib.md5(pwd.encode())

然后在你的数据库sql查询中传递这个salt和uname,login下面是一个表名:

sql = "insert into login values ('"+uname+"','"+email+"','"+salt.hexdigest()+"')"

【讨论】:

uname = request.form["uname"] pwd=request.form["pwd"] salt = hashlib.md5(pwd.encode()) 然后在你的数据库sql中传递这个盐和uname查询,login 下面是一个表名:- sql = "insert into login values ('"+uname+"','"+email+"','"+salt.hexdigest()+"')" -1 因为 md5 非常快,这使得使用 md5 的单次迭代成为密码散列函数的糟糕选择。 您是否真的能够在“答案”中同时包含糟糕的密码哈希和 SQL 注入?【参考方案9】:

这是我的建议:

for i in range(len(rserver.keys())):
    salt = uuid.uuid4().hex
    print(salt)
    mdp_hash = rserver.get(rserver.keys()[i])
    rserver.set(rserver.keys()[i], hashlib.sha256(salt.encode() + mdp_hash.encode()).hexdigest() + salt)
    rsalt.set(rserver.keys()[i], salt)

【讨论】:

以上是关于在 Python 中加盐和散列密码的主要内容,如果未能解决你的问题,请参考以下文章

如何针对密码的特定字母进行身份验证[重复]

盐和散列,为啥不使用用户名?

MySQL密码功能

明文密码和散列值抓取防范方法

带有休眠和散列密码的 Spring Security DB 身份验证?

用户密码盐的最佳长度是多少? [关闭]