logo

如何安全地存储密码在数据库

Published on

在现代应用开发中,保护用户密码的安全至关重要。如果密码被以明文形式存储,一旦数据库泄露,黑客可以轻松获取所有用户的密码。因此,安全地存储密码是开发者的必备技能。本文将为你讲解三种常用的技术:哈希、加盐延展,并带你逐步理解它们如何抵御黑客的攻击。

一、哈希(Hashing)

首先,我们绝不能直接存储用户的明文密码,而是需要使用哈希函数将密码转换成唯一的指纹。

什么是哈希函数?

哈希函数是一种单向的加密算法,它能够将输入(如密码)转换成固定长度的唯一字符串,即指纹。这一过程是不可逆的,这意味着通过指纹无法还原出原始密码。

Maple

推荐使用的哈希算法是 bcrypt,因为:

  1. 它的计算速度较慢,可以有效抵御暴力破解。
  2. 它需要大量的计算资源,增加了破解的成本。
  3. 它需要较多内存,进一步限制了黑客并行攻击的能力。

我们可以用一个简单的比喻来理解哈希:将不同颜色混合成新颜色。混合后的颜色很难还原回原来的颜色组合。

Maple

哈希的实现流程

用户在创建账户时:

  1. 服务器从用户提供的密码生成一个指纹。
  2. 数据库中只存储这个指纹,不存储原始密码。

用户登录时:

  1. 服务器从数据库中检索存储的指纹。
  2. 将用户输入的密码重新哈希后生成新的指纹。
  3. 比较这两个指纹,只有匹配时用户才能登录。
例子:
- 用户密码:`mypassword`
- 哈希后存储:`$2b$10$EixZaYVK1fsbw1ZfbX3OXe...`
Maple

二、加盐(Salting)

虽然哈希函数很强大,但仍可能被黑客利用彩虹表(Rainbow Table)破解。

什么是彩虹表?

彩虹表是一个预计算的哈希值与明文密码的映射表,黑客通过查表可以快速反推出密码。

Maple

如何抵御彩虹表攻击?

我们通过加盐来防御彩虹表。

(Salt)是一个随机字符串,每个用户的盐都不同。 加盐后,即使两个用户使用相同的密码,其生成的指纹也会不同,从而使彩虹表失效。

Maple

加盐的实现流程

用户在创建账户时:

  1. 服务器为用户生成一个唯一的盐
  2. 将密码与盐结合后进行哈希运算。
  3. 数据库中同时存储盐和生成的指纹。
Maple

用户登录时:

  1. 服务器从数据库中检索用户的盐。
  2. 将用户输入的密码与盐结合后重新哈希,生成新的指纹。
  3. 比较新的指纹与存储的指纹是否匹配。
例子:
- 用户密码:`mypassword`
- 随机盐:`randomsalt123`
- 哈希存储:`$2b$10$randomsalt123...`
Maple

三、延展(Stretching)

即使加盐了,黑客仍可能通过**暴力破解(Brute Force Attack)**尝试所有可能的密码组合来破解密码。

什么是延展?

延展的本质是多次重复哈希操作,通过增加计算量来减慢每次哈希的速度,从而显著增加暴力破解的时间成本。

Maple

延展的最佳实践

  1. 使用经过优化的哈希算法(如 bcryptargon2 等),它们内置了延展功能。
  2. 要求用户使用随机生成且长度超过12个字符的密码。随机性越强、长度越大,破解难度越高。

小结

存储密码的三个关键点

  1. 哈希:不可逆的加密,防止明文泄露。
  2. 加盐:为每个用户生成独特的盐,抵御彩虹表攻击。
  3. 延展:增加计算成本,延缓暴力破解。

以下是一个综合的密码存储实现示例:

import bcrypt

# 创建账户时
def hash_password(password):
    salt = bcrypt.gensalt()
    hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
    return salt, hashed

# 登录时
def verify_password(input_password, stored_salt, stored_hashed):
    hashed = bcrypt.hashpw(input_password.encode('utf-8'), stored_salt)
    return hashed == stored_hashed