Skip to main content

防止“彩虹表攻击”:计算 hash 前加 salt, 完整的流程是怎样的,这个 salt 是如何存放的

为了防止“彩虹表攻击”,在计算哈希值之前添加一个随机的盐(salt)是常见的做法。盐是一种随机数据,它与密码结合后再进行哈希运算,使得相同的密码每次生成的哈希值都不同,从而有效防止彩虹表攻击。下面是完整的流程以及盐的存放方式。

完整的流程

  1. 生成随机盐

    • 为每个用户生成一个唯一的随机盐。盐的长度通常为 16 字节或更长。
  2. 将盐与密码结合

    • 将生成的盐与用户的密码结合在一起。通常是将盐附加在密码的前面或后面。
  3. 计算哈希值

    • 使用密码学哈希函数(例如 SHA-256、SHA-512、bcrypt、scrypt 等)对盐和密码的组合进行哈希运算,得到哈希值。
  4. 存储盐和哈希值

    • 将生成的盐和哈希值一起存储在数据库中。通常将盐和哈希值存储在同一个字段中,或者分别存储在两个字段中。

具体实现示例

以下是一个伪代码示例,展示如何生成盐、计算哈希值以及存储盐和哈希值:

import os
import hashlib
import binascii

def generate_salt(length=16):
"""生成指定长度的随机盐"""
return os.urandom(length)

def hash_password(password, salt):
"""计算密码的哈希值"""
# 将盐与密码结合
salted_password = salt + password.encode('utf-8')
# 计算哈希值
hash_value = hashlib.sha256(salted_password).digest()
return binascii.hexlify(hash_value).decode('utf-8')

def store_password(username, password):
"""存储用户名、盐和哈希值"""
# 生成随机盐
salt = generate_salt()
# 计算哈希值
hashed_password = hash_password(password, salt)
# 将盐和哈希值存储在数据库中
# 假设使用一个简单的字典来模拟数据库
database[username] = {
'salt': binascii.hexlify(salt).decode('utf-8'),
'hashed_password': hashed_password
}

def verify_password(username, password):
"""验证用户输入的密码"""
# 从数据库中获取存储的盐和哈希值
salt = binascii.unhexlify(database[username]['salt'])
stored_hashed_password = database[username]['hashed_password']
# 计算输入密码的哈希值
hashed_password = hash_password(password, salt)
# 比较哈希值
return hashed_password == stored_hashed_password

# 模拟数据库
database = {}

# 存储用户密码
store_password('user1', 'my_secure_password')

# 验证用户密码
is_valid = verify_password('user1', 'my_secure_password')
print(f"Password is valid: {is_valid}")

盐的存放方式

  • 与哈希值一起存储:最常见的方法是将盐和哈希值一起存储在数据库的同一个字段中,使用分隔符将它们分开。例如,存储格式可以是 salt:hashed_password
  • 分别存储:也可以将盐和哈希值分别存储在数据库的两个独立字段中。例如,数据库表可以有 salthashed_password 两个字段。

存储示例

假设我们使用一个 SQL 数据库来存储用户信息,表结构可能如下:

CREATE TABLE users (
username VARCHAR(255) PRIMARY KEY,
salt VARCHAR(255) NOT NULL,
hashed_password VARCHAR(255) NOT NULL
);

在存储用户信息时:

INSERT INTO users (username, salt, hashed_password)
VALUES ('user1', 'generated_salt', 'hashed_password');

在验证用户密码时:

SELECT salt, hashed_password FROM users WHERE username = 'user1';

总结

通过在计算哈希值之前添加随机盐,可以有效防止彩虹表攻击。完整的流程包括生成随机盐、将盐与密码结合、计算哈希值,以及将盐和哈希值一起存储在数据库中。这样,即使攻击者获取了哈希值,也难以通过彩虹表进行逆向破解。

数据库被盗,salt 和 hashed_password 岂不是同时丢失了

确实,如果数据库被盗,攻击者可能会同时获得盐(salt)和哈希值(hashed_password)。尽管如此,使用盐仍然是防止彩虹表攻击的有效方法,因为每个密码都有不同的盐,这使得攻击者无法使用预计算的彩虹表来破解哈希值。

然而,为了进一步提升安全性,可以采取以下措施:

1. 使用强哈希算法

选择强大的哈希算法,如 bcrypt、scrypt 或 Argon2。这些算法专为密码哈希设计,具有以下特点:

  • 慢速计算:增加破解时间。
  • 内存消耗:增加破解难度。

2. 增加盐的长度

使用更长的盐(例如 32 字节或更长),增加攻击者生成对应彩虹表的难度。

3. 使用多重加密

在盐和密码组合后,使用多次哈希(例如,迭代 1000 次哈希),增加破解难度。

4. 分离存储

将盐和哈希值分开存储,以减少攻击者同时获取两者的可能性。例如,将盐存储在一个单独的安全位置(如不同的数据库或文件系统)。

5. 使用密钥推导函数(KDF)

使用密钥推导函数(如 PBKDF2、bcrypt、scrypt 或 Argon2)来生成哈希值,这些函数设计用于安全地处理密码。

示例实现

以下是一个使用 bcrypt 的示例,它结合了上面的建议:

import bcrypt

def hash_password(password):
"""计算密码的哈希值"""
# 生成盐并计算哈希值
salt = bcrypt.gensalt()
hashed_password = bcrypt.hashpw(password.encode('utf-8'), salt)
return salt, hashed_password

def verify_password(stored_salt, stored_hashed_password, password):
"""验证用户输入的密码"""
# 使用存储的盐和输入的密码计算哈希值
hashed_password = bcrypt.hashpw(password.encode('utf-8'), stored_salt)
return hashed_password == stored_hashed_password

# 存储用户密码
salt, hashed_password = hash_password('my_secure_password')

# 模拟存储在数据库中
database = {
'user1': {
'salt': salt,
'hashed_password': hashed_password
}
}

# 验证用户密码
is_valid = verify_password(database['user1']['salt'], database['user1']['hashed_password'], 'my_secure_password')
print(f"Password is valid: {is_valid}")

分离存储示例

假设我们使用两个不同的数据库来存储盐和哈希值:

-- 数据库1:存储用户名和哈希值
CREATE TABLE user_hashes (
username VARCHAR(255) PRIMARY KEY,
hashed_password VARCHAR(255) NOT NULL
);

-- 数据库2:存储用户名和盐
CREATE TABLE user_salts (
username VARCHAR(255) PRIMARY KEY,
salt VARCHAR(255) NOT NULL
);

在存储用户信息时:

-- 数据库1
INSERT INTO user_hashes (username, hashed_password)
VALUES ('user1', 'hashed_password');

-- 数据库2
INSERT INTO user_salts (username, salt)
VALUES ('user1', 'generated_salt');

在验证用户密码时:

-- 从数据库2获取盐
SELECT salt FROM user_salts WHERE username = 'user1';

-- 从数据库1获取哈希值
SELECT hashed_password FROM user_hashes WHERE username = 'user1';

总结

虽然数据库被盗可能导致盐和哈希值同时丢失,但通过使用强哈希算法、增加盐的长度、多重加密、分离存储和使用密钥推导函数等方法,可以进一步提升密码存储的安全性,增加攻击者破解密码的难度。