Salt and hash a password in Python

这段代码应该使用 salt 来散列密码。Salt 和散列密码保存在数据库中。密码本身不是。

鉴于这次行动的敏感性,我想确保一切正常。

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())
178275 次浏览

EDIT: This answer is wrong. A single iteration of SHA512 is fast, which makes it inappropriate for use as a password hashing function. Use one of the other answers here instead.


Looks fine by me. However, I'm pretty sure you don't actually need base64. You could just do this:

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

如果它不会造成困难,那么通过将 salt 和散列密码存储为原始字节而不是十六进制字符串,可以在数据库中获得稍微高效一些的存储。为此,用 bytes代替 hex,用 digest代替 hexdigest

编辑:

这个答案中提到的库现在已经过时了,这个答案中提到的 Hashlib 密钥派生功能: https://stackoverflow.com/a/56915300/893857是一个很好的建议,现在可以使用。

Original Answer 聪明的做法不是自己编写加密,而是使用 passlib 之类的 https://passlib.readthedocs.io/en/stable/#

以安全的方式编写加密代码很容易出错。令人讨厌的是,对于非加密代码,当程序崩溃后它不工作时,您通常会立即注意到它。虽然使用加密代码,你往往只有在发现后,为时已晚,你的数据已被破坏。因此,我认为最好使用一个由其他知识丰富的人编写的包,它是基于战斗测试协议的。

Passlib 还有一些很好的特性,这些特性使得它很容易使用,而且如果一个旧协议被破坏,它也很容易升级到一个新的密码哈希协议。

而且仅仅一轮 sha512就更容易受到字典攻击。Sha512的设计目标是快速,而当试图安全地存储密码时,这实际上是一件坏事。其他人对这类问题已经深思熟虑了很久,所以你最好利用这一点。

要在 Python 3中实现这一点,你需要 UTF-8编码,例如:

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

否则你会得到:

追溯(最近一次通话) :
“文件”第一行
Hash _ password = hashlib.sha512(password + salt)
TypeError: 在散列之前必须对 Unicode 对象进行编码

如果需要使用现有系统存储的散列,passlib 似乎很有用。如果可以控制格式,请使用现代散列,如 bcrypt 或 script。此时,从 python 中使用 bcrypt 似乎要容易得多。

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

如果不想安装 passlib,也可以直接使用 比-比-密

参见: 如何使用脚本生成密码和 Python 中的 salt 散列

Based on the other answers to this question, I've implemented a new approach using bcrypt.

为什么要用 Bcrypt

如果我理解正确的话,在 SHA512上使用 bcrypt的论点是,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 安装在 win 764位 Python 上

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

Https://pypi.python.org/pypi/argon2_cffi

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

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

请注意:

  • The use of a 16-byte salt and 100000 iterations of PBKDF2 match the minimum numbers recommended in the Python docs. Further increasing the number of iterations will make your hashes slower to compute, and therefore more secure.
  • os.urandom始终使用加密安全的随机性源
  • is_correct_password中使用的 hmac.compare_digest 基本上只是字符串的 ==操作符,但是没有短路的能力,这使得它不受定时攻击的影响。那是 可能不会提供任何额外的安全价值,但也不疼,所以我用了它。

有关如何制作一个好的密码散列以及其他适合用来散列密码的函数列表的理论,请参见 https://security.stackexchange.com/q/211/29805

I did the same thing in NodeJs before:

 echo "console.log(require('crypto').createHmac('sha256', 'salt').update('password').digest('hex'))" | node

它在 Python 中的等价物是:

python3 -c 'import hashlib;import base64;import hmac;print(hmac.new(b"salt", "password".encode(), hashlib.sha256).hexdigest())'

相应的 shell 命令是:

echo -n "password" | openssl sha256 -hmac "salt"