Python 密码加盐和加胡椒
Python Password Salting and Peppering
我目前正在创建一个 class 来处理与密码相关的功能(散列和验证)。我在这方面的知识很基础。
经过一些研究,我很明显应该使用一个已经很好的哈希库。我选择了bycrypt。还建议我应该为每个密码使用唯一的盐,以及不存储在数据库中的全局胡椒。我的代码 运行 没问题,可以正常工作。
我的问题是,我是否正确地添加了密码?现在我首先用 sha256 加密密码,然后我 运行 用独特的盐对其进行加密。我读过 sha256 不是为密码散列而设计的,因此在我们的情况下它不安全,但我应该使用什么来代替它?作为不了解密码散列的人这样安全吗,还是我应该更改一些内容?
编辑:
如果我的代码太长,这是我质疑的浓缩部分:
import bycrypt
import hashlib
import hmac
password = "vEryC0mPl3X!!!"
PEPPER = "randompepperstring"
salt = bycrypt.gensalt()
peppered_password = hmac.new(PEPPER.encode("utf-8"), password.encode("utf-8"), hashlib.sha256).hexdigest()
salted_peppered_password = bycrypt.hashpw(peppered_password.encode("utf-8"), salt)
heashed_password = salted_peppered_password.decode("utf-8")
完整代码:
from bcrypt import hashpw, checkpw, gensalt
from hashlib import sha256
from hmac import new
class EmailNotFoundError(Exception):
pass
class PasswordError(Exception):
pass
class PasswordLengthError(PasswordError):
pass
class PasswordNotComplexError(PasswordError):
pass
class PasswordManager:
PEPPER = "randompepperstring"
def _GenerateSalt() -> bytes:
return gensalt()
def _PepperPassword(password: str) -> str:
return new(PasswordManager.PEPPER.encode("utf-8"), password.encode("utf-8"), sha256).hexdigest()
def _SaltPassword(password: str, salt: bytes) -> bytes:
password_bytes = password.encode('utf-8')
return hashpw(password_bytes, salt)
def _GetPasswordOfEmail(email: str) -> str:
# hashed_pw is for testing purposes currently
# result should come from and sql select, eg: SELECT CASE WHEN COUNT(1) > 0 THEN HashedPassword ELSE NULL END AS [HashedPassword] FROM Users WHERE Email LIKE email
# result will select SQL NULL if no e-mail is found, which becomes a None in Python
result = hashed_pw
if result == None:
raise EmailNotFoundError("The provided e-mail address does not exist in our database.")
return result.encode("utf-8")
def _ValidPasswordFormat(password: str) -> None:
if 8 > len(password) >= 72:
raise PasswordLengthError(f"The password should be between 8 and 72 characters long. The provided password '{password}' is {len(password)} characters long.")
if not any(map(str.isdigit, password)):
raise PasswordNotComplexError("The password contains no numbers. The password should contain at least 1 number, symbol, uppercase letter and lowercase letter.")
if not any(map(str.islower, password)):
raise PasswordNotComplexError("The password contains no lowercase letters. The password should contain at least 1 number, symbol, uppercase letter and lowercase letter.")
if not any(map(str.isupper, password)):
raise PasswordNotComplexError("The password contains no uppercase letters. The password should contain at least 1 number, symbol, uppercase letter and lowercase letter.")
if not any(char in '!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~' for char in password):
raise PasswordNotComplexError("The password contains no symbols (for example ! or * or = ...). The password should contain at least 1 number, symbol, uppercase letter and lowercase letter.")
def HashPassword(password: str) -> str:
PasswordManager._ValidPasswordFormat(password)
salt = PasswordManager._GenerateSalt()
peppered_password = PasswordManager._PepperPassword(password)
salted_peppered_password = PasswordManager._SaltPassword(peppered_password, salt)
return salted_peppered_password.decode("utf-8")
def VerifyLogin(email: str, input_password: str) -> bool:
stored_password = PasswordManager._GetPasswordOfEmail(email)
return checkpw(PasswordManager._PepperPassword(input_password).encode('utf-8'), stored_password)
# TESTING
hashed_pw = PasswordManager.HashPassword("vEryC0mPl3X!!!")
print(PasswordManager.VerifyLogin("john@gmail.com", "vEryC0mPl3X!!!"))
print(PasswordManager.VerifyLogin("john@gmail.com", "not"))
print(PasswordManager.VerifyLogin("john@gmail.com", "good"))
print(PasswordManager.VerifyLogin("john@gmail.com", "at all"))
据我所知,你做的是对的。
I've read that sha256 is not made for password hashing so it is not secure
这意味着,您不应使用 SHA256 散列密码并将其存储在数据库中。
不代表不能用来做辣椒
我在这里可以推荐的一点是不要使用普通的 SHA-256,而是使用胡椒的组合。也许像该密码的 SHA-256 + MD-5 或 SHA-1 的一部分。
如果您使用更高的哈希算法,则需要更多的计算。假设您可能会添加更多功能,例如不应使用旧密码或类似于旧密码,更多的计算会被浪费。
我目前正在创建一个 class 来处理与密码相关的功能(散列和验证)。我在这方面的知识很基础。
经过一些研究,我很明显应该使用一个已经很好的哈希库。我选择了bycrypt。还建议我应该为每个密码使用唯一的盐,以及不存储在数据库中的全局胡椒。我的代码 运行 没问题,可以正常工作。
我的问题是,我是否正确地添加了密码?现在我首先用 sha256 加密密码,然后我 运行 用独特的盐对其进行加密。我读过 sha256 不是为密码散列而设计的,因此在我们的情况下它不安全,但我应该使用什么来代替它?作为不了解密码散列的人这样安全吗,还是我应该更改一些内容?
编辑: 如果我的代码太长,这是我质疑的浓缩部分:
import bycrypt
import hashlib
import hmac
password = "vEryC0mPl3X!!!"
PEPPER = "randompepperstring"
salt = bycrypt.gensalt()
peppered_password = hmac.new(PEPPER.encode("utf-8"), password.encode("utf-8"), hashlib.sha256).hexdigest()
salted_peppered_password = bycrypt.hashpw(peppered_password.encode("utf-8"), salt)
heashed_password = salted_peppered_password.decode("utf-8")
完整代码:
from bcrypt import hashpw, checkpw, gensalt
from hashlib import sha256
from hmac import new
class EmailNotFoundError(Exception):
pass
class PasswordError(Exception):
pass
class PasswordLengthError(PasswordError):
pass
class PasswordNotComplexError(PasswordError):
pass
class PasswordManager:
PEPPER = "randompepperstring"
def _GenerateSalt() -> bytes:
return gensalt()
def _PepperPassword(password: str) -> str:
return new(PasswordManager.PEPPER.encode("utf-8"), password.encode("utf-8"), sha256).hexdigest()
def _SaltPassword(password: str, salt: bytes) -> bytes:
password_bytes = password.encode('utf-8')
return hashpw(password_bytes, salt)
def _GetPasswordOfEmail(email: str) -> str:
# hashed_pw is for testing purposes currently
# result should come from and sql select, eg: SELECT CASE WHEN COUNT(1) > 0 THEN HashedPassword ELSE NULL END AS [HashedPassword] FROM Users WHERE Email LIKE email
# result will select SQL NULL if no e-mail is found, which becomes a None in Python
result = hashed_pw
if result == None:
raise EmailNotFoundError("The provided e-mail address does not exist in our database.")
return result.encode("utf-8")
def _ValidPasswordFormat(password: str) -> None:
if 8 > len(password) >= 72:
raise PasswordLengthError(f"The password should be between 8 and 72 characters long. The provided password '{password}' is {len(password)} characters long.")
if not any(map(str.isdigit, password)):
raise PasswordNotComplexError("The password contains no numbers. The password should contain at least 1 number, symbol, uppercase letter and lowercase letter.")
if not any(map(str.islower, password)):
raise PasswordNotComplexError("The password contains no lowercase letters. The password should contain at least 1 number, symbol, uppercase letter and lowercase letter.")
if not any(map(str.isupper, password)):
raise PasswordNotComplexError("The password contains no uppercase letters. The password should contain at least 1 number, symbol, uppercase letter and lowercase letter.")
if not any(char in '!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~' for char in password):
raise PasswordNotComplexError("The password contains no symbols (for example ! or * or = ...). The password should contain at least 1 number, symbol, uppercase letter and lowercase letter.")
def HashPassword(password: str) -> str:
PasswordManager._ValidPasswordFormat(password)
salt = PasswordManager._GenerateSalt()
peppered_password = PasswordManager._PepperPassword(password)
salted_peppered_password = PasswordManager._SaltPassword(peppered_password, salt)
return salted_peppered_password.decode("utf-8")
def VerifyLogin(email: str, input_password: str) -> bool:
stored_password = PasswordManager._GetPasswordOfEmail(email)
return checkpw(PasswordManager._PepperPassword(input_password).encode('utf-8'), stored_password)
# TESTING
hashed_pw = PasswordManager.HashPassword("vEryC0mPl3X!!!")
print(PasswordManager.VerifyLogin("john@gmail.com", "vEryC0mPl3X!!!"))
print(PasswordManager.VerifyLogin("john@gmail.com", "not"))
print(PasswordManager.VerifyLogin("john@gmail.com", "good"))
print(PasswordManager.VerifyLogin("john@gmail.com", "at all"))
据我所知,你做的是对的。
I've read that sha256 is not made for password hashing so it is not secure
这意味着,您不应使用 SHA256 散列密码并将其存储在数据库中。
不代表不能用来做辣椒
我在这里可以推荐的一点是不要使用普通的 SHA-256,而是使用胡椒的组合。也许像该密码的 SHA-256 + MD-5 或 SHA-1 的一部分。
如果您使用更高的哈希算法,则需要更多的计算。假设您可能会添加更多功能,例如不应使用旧密码或类似于旧密码,更多的计算会被浪费。