如何在下一个应用程序中安全地散列和存储密码

2025-06-07

如何在下一个应用程序中安全地散列和存储密码

您是否对用户的密码进行了哈希处理?更重要的是,您的处理方式正确吗?关于密码哈希处理的信息有很多,而且有很多不同的哈希算法可供您使用。

作为一名全栈工程师,我花了大量时间构建基于密码的身份验证机制。作为一名道德黑客,我花了大量时间尝试破解这些机制并破解密码哈希值。

在本文中,我将简要概述安全密码散列和存储,然后向您展示如何为下一个应用程序安全地散列密码。

概述

密码哈希:30 秒摘要

关于哈希算法是什么,已经有很多文章进行了阐述,所以我就不浪费大家的时间重复了。简而言之,哈希算法是一个单向“陷门”函数。我们把哈希函数称为H。给定一些数据d,计算 很容易H(d)。但仅给定哈希值H(d),计算 几乎是不可能的d。同样需要注意的是,即使 中只有一个字节的差异d也会导致完全不同的哈希值H(d)

与其以“明文”形式存储密码(即直接将密码存储在数据库中),不如先对密码进行哈希处理,然后再将其存储到数据库中,这样更安全。给定一个密码p,我们计算H(p),并将该值存储在数据库中。当用户尝试登录时,我们会对用户尝试登录的密码进行哈希处理,并将其与数据库中的哈希值进行比较。如果两者匹配,则密码有效,我们将帮助用户登录。如果不匹配,则说明用户提供的密码错误。

我们为什么要这样做?因为它可以保护密码免遭黑客、懒惰或恶意的系统管理员以及数据泄露的侵害。如果数据库泄露或被黑客入侵,黑客就无法仅凭一眼就能轻松确定所有用户的密码。考虑到许多人的大多数帐户都使用相同或相似的密码,这一点就显得尤为重要。如果没有密码哈希处理,一个帐户被黑客入侵可能会导致该用户在多个服务上的所有帐户都遭到入侵。

一个简单的例子

下面我提供了一些 python 伪代码,让您了解使用密码散列的登录功能可能是什么样子。

def login(username, password):
    user = Users.get(username) # fetch the user record from the database

    # if no user matches the username, don't log them in
    if not user:  
        return False

    # hash the supplied password
    supplied_hash = some_hash_function(password)

    # see if that hash matches the user's hash
    if supplied_hash == user.password_hash:
        return True
    else:  
        return False
Enter fullscreen mode Exit fullscreen mode

上面的代码稍微简化了一点,因为它取决于你如何在数据库中存储和检索用户数据。在这个例子中,我也没有提到你应该使用的实际哈希函数,所以让我们更深入地了解一下细节。

哈希函数

市面上有各种各样的哈希函数,它们各有优缺点。例如,有些哈希函数速度很快,有些则很慢。快速哈希算法非常适合构建哈希表之类的数据结构,但我们希望使用慢速哈希函数来处理密码哈希,因为慢速哈希函数会使暴力破解变得更加困难。让我们来看看一些常见的哈希函数。

一些常见的哈希函数

哈希函数 描述
MD5 常用于密码哈希处理,但由于其中发现的一些漏洞,现在被认为对于加密目的而言是不安全的
SHA-1 最初由美国国家安全局为各种目的而设计,现在被认为已弃用且不安全
SHA-3 比 SHA-1 更好,既安全又灵活
NTLM 常用于 Windows 活动目录,但易被破解。请使用 NTLMv2。
加密 一种能够抵御暴力破解的慢速哈希函数。在一些 Linux 发行版中很常用。被认为非常安全。
氩2 一种复杂但极其安全的哈希函数,能够抵御暴力破解攻击。但实现起来可能比较困难。

盐到底是什么?

“盐”是一段随机数据,通常在实际进行哈希处理之前添加到要哈希的数据中。在对数据进行哈希处理之前添加盐,会使哈希函数的输出与仅对数据进行哈希处理时的输出不同。

当用户设置密码时(通常是注册时),应该生成一个随机盐值,并用它来计算密码哈希值。然后,该盐值应该与密码哈希值一起存储。当用户尝试登录时,将盐值与输入的密码组合,对两者进行哈希处理,然后将其与数据库中的哈希值进行比较。

为什么要使用盐?

无需赘述,黑客通常使用彩虹表攻击字典攻击暴力破解来尝试破解密码哈希值。虽然黑客无法仅凭哈希值计算出原始密码,但他们可以获取一长串可能的密码,并计算哈希值,然后尝试将其与数据库中的密码进行匹配。这实际上是这些攻击类型的工作原理,尽管上述每种攻击的工作原理略有不同。

加盐会使黑客更难执行此类攻击。根据哈希函数的不同,加盐哈希的破解时间几乎比未加盐哈希的破解时间长得多。它们还能使彩虹表攻击几乎不可能发生。因此,在哈希中使用盐至关重要。

您应该使用哪个哈希函数?

很多人会告诉你,这个问题没有“正确”或“错误”的答案,只有权衡。的确如此,但有些哈希函数比其他哈希函数的权衡效果更好。

就我个人而言,我非常喜欢 Bcrypt 和 Argon2,因为它们都非常安全,都需要加盐,而且速度都很慢(正如我们上面讨论过的,这正是我们想要的密码哈希函数的特性)。Argon2 比 Bcrypt 复杂得多,实现起来也更困难。Bcrypt 也更常见,而且更多语言都有相应的库,所以我倾向于使用它。我也推荐你使用这两个中的一个。

整合起来

下面,我提供了两个示例来说明如何实现我们今天讨论的所有内容。第一个例子是伪代码,第二个例子是 Python 代码。

密码哈希认证伪代码

大多数常见语言都应该提供 bcrypt 模块或包,但它的接口总是看起来不同,所以我试图尽可能地与语言无关。

# should be called when a user signs up or changes their password
function calculate_hash(password) 
    salt = random_bytes(14) # or any other length
    hash = bcrypt_hash(password, salt)

    # store this with the user in your database
    return hash 

# called whenever a user tries to login
function login_user(username, password) 
    user = get_user_from_database(username)

    # bcrypt stores the salt with the hash, your library should manage this for you
    salt = get_salt(user.hash) 
    new_hash = bcrypt_hash(password, salt)
    if new_hash == user.hash
        login_user()
    else 
        dont_login_user()

Enter fullscreen mode Exit fullscreen mode

请注意,您的盐至少应为 8 个字节长,但越长越安全。

密码哈希认证Python代码

Python 提供了一个可以通过 Pip 安装的bcrypt 模块,我将在本例中使用它。bcrypt 模块会在后台为您处理计算,因此使用起来非常方便:

import bcrypt

# this will create the hash that you need to store in your database
def create_bcrypt_hash(password):
    # convert the string to bytes
    password_bytes = password.encode()      
    # generate a salt
    salt = bcrypt.gensalt(14)               
    # calculate a hash as bytes
    password_hash_bytes = bcrypt.hashpw(password_bytes, salt)   
    # decode bytes to a string
    password_hash_str = password_hash_bytes.decode()            


    # the password hash string should similar to:
    # $2b$10$//DXiVVE59p7G5k/4Klx/ezF7BI42QZKmoOD0NDvUuqxRE5bFFBLy
    return password_hash_str        

# this will return true if the user supplied a valid password and 
# should be logged in, otherwise false
def verify_password(password, hash_from_database):
    password_bytes = password.encode()
    hash_bytes = hash_from_database.encode()

    # this will automatically retrieve the salt from the hash, 
    # then combine it with the password (parameter 1)
    # and then hash that, and compare it to the user's hash
    does_match = bcrypt.checkpw(password_bytes, hash_bytes)

    return does_match
Enter fullscreen mode Exit fullscreen mode

结论

关键要点:

  • 始终将密码存储为哈希值,而不要存储为纯文本。
  • 使用盐可增加安全性。
  • 使用 Bcrypt 或 Argon2 作为哈希函数

希望这篇文章对你有帮助!请在下方评论区留言,分享你的想法。

如果您正在为云应用程序编写代码,那么当出现问题时,您需要立即采取行动。我参与构建了CodeLighthouse,它能够实时向开发人员发送应用程序错误通知,以便您更快地发现并修复错误。立即在codelighthouse.io免费开始使用!

文章来源:https://dev.to/kmistele/how-to-securely-hash-and-store-passwords-in-your-next-application-4e2f
PREV
有机软件架构
NEXT
useEffect、useMemo、useCallback 钩子之间的区别?