Python密码盐渍和胡椒

2024-09-28 22:35:28 发布

您现在位置:Python中文网/ 问答频道 /正文

我目前正在创建一个处理密码相关函数(哈希和验证)的类。我在这个领域的知识非常基础

经过一些研究,我很明显应该使用一个已经很好的哈希库。我选择了bycrypt。还建议我为每个密码使用一个唯一的salt,以及一个不存储在数据库中的全局pepper。我的代码运行良好,并且完成了它应该做的事情

我的问题是,我是否正确地添加了密码?现在,我首先用sha256在密码上加胡椒,然后用一种独特的盐在密码上运行bycrypt。我已经读到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"))

Tags: theimport密码returnifdefpasswordutf
1条回答
网友
1楼 · 发布于 2024-09-28 22:35:28

据我所知,你所做的是正确的

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的部分密码

如果使用更高的哈希算法,则需要更多的计算。假设您可能会添加其他功能,如不应使用旧密码或与旧密码类似的功能,这样会浪费更多的计算

相关问题 更多 >