密码加盐(Salt)实战:从哈希到BCrypt的安全存储指南
2026/8/30 13:09:06 网站建设 项目流程

如果你负责过任何一个带账号体系的业务系统,多半对下面这个场景不陌生:某天运维突然通知你,生产环境的用户表被拖库了,几百万条记录流了出去。你心里慌了一下,但转念一想:“还好,密码都做了 MD5 加密,黑客拿到也解不出来。”这个想法到底对不对?

直接说结论:如果密码只用 MD5 做了一次哈希,那在真实攻击者面前,基本等于把明文写在压缩包里,破解只是时间问题。很多人以为“存密码 = 做一次哈希”,这个认知在十年前也许还能及格,但在彩虹表、GPU 暴力破解和撞库攻击都高度成熟的今天,已经把无数中小系统送上了新闻头条。

本文要聊的就是密码存储里一个看似不起眼、却能直接决定系统存亡的技术细节:盐(Salt)——什么是盐、为什么必须加盐、怎么加才算安全、实际项目中应该选什么方案,以及最常见的错误写法。读完你能直接拿这套标准去检查自己负责的系统,也能照着代码迁移旧的密码存储方案。

1. 密码存储安全:为什么一个简单的 salt 能决定系统生死

先澄清一个概念:盐(salt)不是加密,也不是密钥,它只是拼在原始密码后面的一段随机数据。它的作用是让同一个密码在不同用户、不同时间、不同盐值下,哈希出来的结果完全不同。

举个直观例子。用户 A 和用户 B 的密码都是123456

  • 不加盐:两个人存的哈希完全一样,比如都是e10adc3949ba59abbe56e057f20f883e(这是123456的 MD5)。
  • 加盐:用户 A 用的是123456 + 随机盐A,用户 B 用的是123456 + 随机盐B,最终哈希结果完全不同。

看到这里你可能会问:密码哈希结果不一样,除了让数据库里看起来更整齐,还有什么实际价值?

价值比你想象的大得多。密码存储的安全模型从来不是“数据库永远不被拖走”,而是“即使数据库被拖走,攻击者也无法在合理时间内还原出用户的真实密码”。盐的存在,让攻击者手里最常用的三把武器全部失灵:

  1. 彩虹表失效:攻击者预先算好一批常见密码的哈希表,直接查表就能反推密码。加盐之后,每个用户的哈希都带上了自己的随机盐,预计算完全失去意义。
  2. 批量破解失效:不加盐时,攻击者破解出用户 A 的密码,发现用户 B、用户 C 的哈希值和自己手里这张表完全一致,等于“一次破解,全站通行”。加盐后,每个用户必须单独破解,成本成倍增加。
  3. 撞库效率下降:攻击者从其他网站泄露库里拿到一批手机号+密码,想拿到你网站上撞一下。如果你的哈希方式和其他网站相同且没加盐,撞库几乎是秒成功;如果加盐且算法合理,撞库的成功率会显著下降。

所以,盐不是一个“锦上添花”的设计,而是密码存储安全的最低门槛。如果一个系统明文存储密码,它连安全的最低门槛都没碰到;如果一个系统只用无盐哈希,它也只是从“裸奔”变成了“穿了一条透明短裤”。

2. 什么是盐(Salt):先理解哈希、彩虹表与暴力破解

要真正理解盐,先得搞清楚三个基础概念:哈希函数、彩虹表和暴力破解。

2.1 哈希函数:单向的“数据指纹”

哈希函数(Hash Function)是一种单向映射:输入任意长度的数据,输出固定长度的字符串。常见的哈希函数有 MD5、SHA-1、SHA-256 等。

import hashlib print(hashlib.md5("123456".encode()).hexdigest()) # e10adc3949ba59abbe56e057f20f883e print(hashlib.sha256("123456".encode()).hexdigest()) # 8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92

哈希函数有两个关键特性:

  • 确定性:同样的输入,一定得到同样的输出。
  • 单向性:从输出反推输入,在计算上不可行。

但“不可行”是理论上的。现实中,攻击者根本不需要从哈希值“反推”密码,他只需要。把常见密码、生日、键盘序列、字典词条全部哈希一遍,再和你数据库里的哈希比对,就能判断你有没有存123456

这就是哈希本身不足以保护密码的核心原因:哈希不是加密,它不隐藏“密码是否常见”这个信息

2.2 彩虹表:用空间换时间的预计算攻击

彩虹表(Rainbow Table)是“预计算哈希链”的经典实现。攻击者提前把几十亿个常见密码的哈希值算好,建成一张大表。拿到目标哈希后,只需要查表,几秒甚至毫秒级就能还原明文。

彩虹表之所以可怕,是因为它把“破解密码”从“实时计算”变成了“查字典”。一个123456,查表命中就是命中,没有任何犹豫。

2.3 盐如何破坏彩虹表和暴力破解

盐的加入,让哈希函数的输入从密码变成了密码 + 盐

hash = 哈希函数(密码 + 盐)

因为盐是随机生成且每个用户都不一样的,攻击者无法为“所有用户”预计算一张统一的表。他面对的每一个哈希,都是一个独立的、带随机扰动的计算问题。

至于暴力破解,盐并不会让单个密码的破解速度变慢——123456 + 盐A依然可以被 GPU 高速计算。所以,盐解决的是“批量破解”和“预计算”问题,真正对抗暴力破解的是慢哈希算法(比如 BCrypt、scrypt、Argon2)。这两者不是替代关系,而是组合关系。这也是为什么工程上成熟的方案都是“盐 + 慢哈希”一起上。

2.4 一个容易混淆的概念:盐(Salt)与密钥(Key)

盐不需要保密,它可以和哈希值一起存储在数据库里;密钥必须保密,一旦泄露,整个加密体系就崩了。这是两者最本质的区别。

新手容易把盐当成密钥来保护,总想着“盐不能写进数据库,要放配置中心”。实际上,盐的职责是“让同一个密码在不同地方产生不同结果”,它本身没有秘密可言。攻击者即使从数据库里同时拿到盐和哈希值,依然无法快速还原密码。真正需要保密的是后端的应用密钥、签名密钥、加密密钥,而不是盐。

3. 盐的设计原则:随机性、唯一性与长度

盐的设计并不复杂,但细节里全是坑。下面三条原则是工程上必须满足的底线。

3.1 每个用户、每次密码设置都用独立随机盐

一个常见的错误是“全局只配置一个盐”,比如很多老项目在代码里写死SALT = "my-app-salt"。这种方案的危害在于:如果所有用户的盐都相同,攻击者依然可以针对这一份盐提前预计算彩虹表,然后一次性批量破解所有用户。

正确的做法是:每个用户、每一次密码创建或修改,都生成一个全新的随机盐。即使两个用户密码相同,他们的哈希值也完全不同。

3.2 盐必须足够长,且来自密码学安全随机源

盐的长度至少应该达到 16 字节(128 位),更稳妥的是 32 字节。长度太短,比如只有 4 位数字,攻击者可以针对所有可能的盐值做预计算,等于直接绕过了盐的防护。

随机源也必须使用密码学安全的伪随机数生成器(CSPRNG),而不是普通的random函数。普通随机数可能受种子影响,存在可预测性。以 Python 为例:

import secrets # 推荐:密码学安全随机盐,32 字节 salt = secrets.token_hex(32) print(salt)

3.3 盐的存储:可以和哈希值放一起,但要有完整性保护

盐不需要加密存储,可以和密码哈希放在同一行记录中,比如单独一列salt字段,也可以按固定格式拼进哈希字符串,比如$格式$盐$哈希。这种做法本身没有问题,因为攻击者即使拿到了盐,也无法快速还原密码。

但要注意:盐可以被读取,不能被篡改。如果攻击者修改了数据库中的盐和哈希,理论上可以替换成一个他自己知道的密码对应的值,从而实现账户劫持。因此,在有条件的系统里,建议对整条用户记录做完整性校验,或者至少对关键字段做签名保护。当然,这在大多数内部业务系统里不是最高优先级,但架构设计时要留出这个意识。

4. 代码实现:从零写一个加盐哈希方案

先动手写一个最小可用方案。这里使用 Python 标准库,不依赖任何第三方包。目标不是让你在生产环境手写这套逻辑,而是通过代码理解“加盐哈希”的完整流程。

4.1 完整代码

# 文件路径:salt_demo.py import hashlib import secrets import hmac def generate_salt(length: int = 32) -> str: """生成密码学安全随机盐。""" return secrets.token_hex(length) def hash_password(password: str, salt: str) -> str: """使用 SHA-256 对密码和盐进行哈希。""" # 将密码和盐拼接,进行哈希 digest = hashlib.pbkdf2_hmac( "sha256", password.encode("utf-8"), salt.encode("utf-8"), iterations=100_000 ) return digest.hex() def store_password(password: str) -> tuple[str, str]: """生成盐和哈希,用于存储到数据库。""" salt = generate_salt() password_hash = hash_password(password, salt) return salt, password_hash def verify_password(password: str, salt: str, expected_hash: str) -> bool: """校验密码是否正确。""" actual_hash = hash_password(password, salt) # 恒定时间比较,避免时序攻击 return hmac.compare_digest(actual_hash, expected_hash) # 示例使用 if __name__ == "__main__": user_password = "my_secure_password" salt, stored_hash = store_password(user_password) print("生成的盐:", salt) print("存储的哈希:", stored_hash) # 正确密码 print("校验正确密码:", verify_password("my_secure_password", salt, stored_hash)) # 错误密码 print("校验错误密码:", verify_password("wrong_password", salt, stored_hash))

4.2 代码关键点解释

  • secrets.token_hex(32)生成 64 个字符的十六进制盐字符串,来源是操作系统提供的密码学安全随机源。
  • hashlib.pbkdf2_hmac("sha256", ...)是 PBKDF2 算法,本质上是“加盐 + 多次迭代哈希”。这里的iterations=100_000表示重复计算 10 万次,目的就是拖慢暴力破解速度。
  • hmac.compare_digest用于恒定时间比较,避免通过比较耗时推测哈希前缀是否正确。

4.3 运行与验证

运行上面的脚本,输出类似:

生成的盐: 5f2b9c8e1e9f9a41b8d4c7d1a2e6f9a9e5b3c4d5e6f7a8b9c0d1e2f3a4b5c6 存储的哈希: 8a3b9d4f6c7e1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5 校验正确密码: True 校验错误密码: False

需要确认三点:

  1. 每次运行的盐都不同,因此同一个密码每次生成的哈希都不同。
  2. “校验正确密码”返回True,说明验证流程走通。
  3. “校验错误密码”返回False,说明盐和哈希的匹配逻辑有效。

这个示例演示了最基本的“加盐哈希”模式,但必须明确:生产环境不建议自己维护 PBKDF2 参数和哈希格式,直接用成熟的库是更稳妥的选择。

5. 工程级方案:为什么更推荐 BCrypt / Argon2

手写 PBKDF2 虽然逻辑正确,但工程上仍然有几个问题很难做好:

  • 参数(迭代次数、盐长度、输出长度)需要自己管理。
  • 哈希格式需要自己设计,不同版本迁移麻烦。
  • 迭代次数随着硬件发展需要定期调整,人工维护容易遗漏。
  • 密码学实现自己写,容易在细节上犯错。

成熟方案一般直接选择BCryptArgon2。它们把“盐生成、哈希计算、参数编码、验证比较”打包成了一个完整格式,开发者不需要关心内部细节。

5.1 一个直观对比

方案是否自动加盐是否抗 GPU 暴力破解是否需要自行管理参数典型使用方式
MD5 单次哈希不可用于密码存储
SHA-256 单次哈希不可用于密码存储
PBKDF2是(需手动实现)可接受,但需关注迭代次数
BCrypt推荐
Argon2id否(库内管理)目前最推荐

BCrypt 和 Argon2 都属于“慢哈希”,它们故意设计得计算耗时较长(比如 100ms 级别),从而让攻击者用 GPU 批量破解时成本飙升。对正常用户来说,一次登录多花几十毫秒无感知;对攻击者来说,每秒只能尝试几次到几十次,破解效率断崖式下降。

5.2 Python 使用 BCrypt 示例

pip install bcrypt
# 文件路径:bcrypt_demo.py import bcrypt def hash_password_bcrypt(password: str) -> str: """生成 bcrypt 哈希,bcrypt 内部会自动处理盐。""" # bcrypt 限制密码长度 72 字节,实际使用需注意 password_bytes = password.encode("utf-8") hashed = bcrypt.hashpw(password_bytes, bcrypt.gensalt()) return hashed.decode("utf-8") def verify_password_bcrypt(password: str, hashed: str) -> bool: """校验 bcrypt 哈希。""" password_bytes = password.encode("utf-8") hashed_bytes = hashed.encode("utf-8") return bcrypt.checkpw(password_bytes, hashed_bytes) if __name__ == "__main__": hashed = hash_password_bcrypt("my_secure_password") print("bcrypt 哈希:", hashed) print("正确密码校验:", verify_password_bcrypt("my_secure_password", hashed)) print("错误密码校验:", verify_password_bcrypt("wrong_password", hashed))

运行输出类似:

bcrypt 哈希: $2b$12$e4oM8mJxGPyklVr0mWYQnupH9w1dQb0FQ/KuRtbNxpJgA7dO0CR0e 正确密码校验: True 错误密码校验: False

注意$2b$12$这个前缀。$2b$表示 BCrypt 版本,12是成本因子(cost factor)。成本因子越高,计算越慢,越安全。一般建议 10 到 12,具体根据服务器性能调整。

5.3 Python 使用 Argon2 示例

pip install argon2-cffi
# 文件路径:argon2_demo.py from argon2 import PasswordHasher from argon2.exceptions import VerifyMismatchError ph = PasswordHasher() def hash_password_argon2(password: str) -> str: """生成 Argon2id 哈希。""" return ph.hash(password) def verify_password_argon2(password: str, hashed: str) -> bool: """校验 Argon2id 哈希。""" try: return ph.verify(hashed, password) except VerifyMismatchError: return False if __name__ == "__main__": hashed = hash_password_argon2("my_secure_password") print("Argon2 哈希:", hashed) print("正确密码校验:", verify_password_argon2("my_secure_password", hashed)) print("错误密码校验:", verify_password_argon2("wrong_password", hashed))

Argon2 哈希自带完整的参数信息,比如:

$argon2id$v=19$m=65536,t=3,p=4$...盐...$...哈希...

这些参数表示内存成本、迭代次数、并行度,库在验证时自动解析,开发者不需要手动管理。

5.4 Java / Spring 场景

如果你在 Java 生态里,最常见的做法是使用 Spring Security 的BCryptPasswordEncoder

// 文件路径:src/main/java/com/example/demo/PasswordUtil.java import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; public class PasswordUtil { private static final BCryptPasswordEncoder ENCODER = new BCryptPasswordEncoder(12); public static String encode(String rawPassword) { return ENCODER.encode(rawPassword); } public static boolean matches(String rawPassword, String encodedPassword) { return ENCODER.matches(rawPassword, encodedPassword); } }

调用方式:

String hash = PasswordUtil.encode("my_secure_password"); boolean ok = PasswordUtil.matches("my_secure_password", hash);

BCryptPasswordEncoder内部已包含随机盐和成本因子,本质上和上面 Python 的 BCrypt 示例是一回事。

6. 常见误区:这几种写法会让盐等于没有

理解了正确方案之后,再看常见错误。下面这些写法在真实项目里出现过,而且大多出现在已经“有点安全意识”的系统里。

6.1 误区一:全局固定盐,写在配置文件里

// 错误示例 private static final String GLOBAL_SALT = "my-fixed-salt-2024"; String hash = md5(password + GLOBAL_SALT);

全局固定盐等同于给所有用户共用一把钥匙,攻击者只要针对GLOBAL_SALT提前算一次彩虹表,就能批量破解所有用户。这个问题在 3.1 已经强调过,这里再重复一次:盐必须是每个用户独立的

6.2 误区二:盐太短,或者用自增 ID 当盐

# 错误示例 salt = "10001" # 太短,枚举所有盐值成本很低 salt = str(userId) # 可预测,攻击者能推断

盐的安全性依赖于“不可预测性”和“空间大小”。用自增 ID 当盐,等于把盐写在了门牌号上。攻击者只要按userId=1,2,3...的顺序就能构造出所有盐值。

6.3 误区三:使用普通随机数生成盐

// 错误示例:使用 Random 而不是 SecureRandom Random random = new Random(); byte[] salt = new byte[32]; random.nextBytes(salt);

java.util.Random是线性同余生成器,可预测性很强。在某些攻击模型下,攻击者可以从少量输出反推出种子,进而预测出所有盐值。密码学场景必须使用SecureRandom,Python 中则使用secrets

6.4 误区四:把哈希当加密,把加密当哈希

这两个概念一旦混淆,安全设计就会出大问题:

  • 哈希:单向、无密钥、不可逆。用于密码存储、数据完整性校验。
  • 加密:双向、有密钥、可逆。用于数据机密性保护,比如传输加密、字段加密。

很多系统为了“能找回密码”,用 AES 等对称加密算法加密密码,然后把密钥放在代码里。一旦密钥泄露,所有密码直接还原成明文。正确的是:密码只能哈希存储,不应该能被任何人“解密”出来。忘记密码的正确做法是让用户重置,而不是找回原密码。

6.5 误区五:忽略恒定时间比较

即使盐和哈希都正确,比较方式也可能泄露信息。普通字符串比较一旦发现第一个字符不同就立即返回,攻击者可以通过测量响应时间,逐步猜测哈希前缀。虽然利用难度大,但这是安全实现的基本素养。

正确做法使用恒定时间比较函数:

  • Python:hmac.compare_digest
  • Java:MessageDigest.isEqual
  • 使用BCryptPasswordEncoder.matches等库方法,它们内部已经处理了这个问题。

6.6 误区六:密码长度无限,直接用 BCrypt 截断

BCrypt 的经典实现只使用输入密码的前 72 字节,超过部分被忽略。如果用户密码超过 72 字节(比如允许用户使用很长很长的密码),必须先在应用层做一次预处理,比如用 SHA-256 对密码做一次哈希,再交给 BCrypt。但要注意,这种“预哈希”需要用prehash兼容模式处理,不能盲目拼接。最稳妥的做法是,在应用层做一个长度限制(比如 128 位以内),并用真实的 BCrypt 库版本进行校验测试。

7. 常见问题与排查思路

问题现象可能原因排查方式解决方案
同一个密码多次生成哈希不同,导致登录校验失败可能是在“生成哈希”和“校验哈希”时使用了不同的盐检查存储的盐是否在登录时被正确读取确保 salt 与 hash 成对存储,验证时使用同一盐值
使用random生成的盐,安全评审不通过普通随机函数不具备密码学安全性检查代码中随机源是否为Random改用SecureRandom/secrets/ 平台 CSPRNG
旧系统存的是 MD5,无法直接升级到 BCrypt旧哈希格式和新格式不兼容查看用户表哈希字段的前缀和长度采用“登录时渐进迁移”,下次登录成功自动升级为新哈希
校验 BCrypt 哈希时速度极慢成本因子设置过高,或服务器 CPU 较弱查看$2b$12$中的成本因子,评估响应耗时在 10-12 之间调整,平衡安全性和性能
数据库泄露后,发现攻击者已经还原了大量密码密码哈希算法弱,或盐设计不当分析泄露哈希格式,确认是否单次 MD5、是否全局固定盐立即强制用户重置密码,并升级为 BCrypt/Argon2
用户超过 72 字节的密码导致 BCrypt 失败BCrypt 内部截断,或库限制了长度检查输入长度和库的报错信息应用层限制密码长度,或使用预哈希方案并做兼容测试
登录时校验通过,但审计日志显示校验耗时差异明显未使用恒定时间比较检查校验代码是否使用普通字符串相等改用hmac.compare_digest或库自带matches方法

8. 密码迁移与渐进升级实践

大部分系统不是从零开始,而是在已有用户表上做改造。这里给出一个可落地的渐进升级路径。

假设当前系统是老方案:

users 表: id, username, password_hash, salt

其中password_hash是老的 MD5 哈希,salt字段暂时为空。新的方案是 BCrypt。为了避免让所有用户重新注册,可以采用“双格式兼容”策略。

8.1 添加新字段

ALTER TABLE users ADD COLUMN password_hash_bcrypt VARCHAR(255) NULL;

8.2 登录验证逻辑

def login(username, password): user = db.query("SELECT * FROM users WHERE username = ?", username) if not user: return "用户不存在" # 如果已经是 BCrypt 哈希,直接验证 if user.password_hash_bcrypt: if verify_password_bcrypt(password, user.password_hash_bcrypt): return "登录成功" else: return "密码错误" # 旧方案兼容:验证 MD5 if not user.salt: old_hash = hashlib.md5(password.encode()).hexdigest() else: old_hash = hashlib.md5((password + user.salt).encode()).hexdigest() if old_hash == user.password_hash: # 认证成功,立即升级为 BCrypt new_hash = hash_password_bcrypt(password) db.execute("UPDATE users SET password_hash_bcrypt = ? WHERE id = ?", new_hash, user.id) return "登录成功,密码已升级" return "密码错误"

这种“验证成功后写回新哈希”的方式,可以在不打扰用户的情况下逐步完成全量升级。等到所有老用户都登录过一次,旧字段就可以下线了。

8.3 后续清理

-- 确认没有遗留老哈希后,可安全下线 ALTER TABLE users DROP COLUMN password_hash; ALTER TABLE users DROP COLUMN salt;

执行下线操作前,一定要先在测试环境验证新登录逻辑覆盖了所有分支,同时检查备份。

9. 最佳实践与工程建议

9.1 选型:能用库就别自己写

最省心、最安全的密码存储方式,是按顺序做三选一:

  1. 如果用了 Spring Security,直接用BCryptPasswordEncoder
  2. 如果用了 Python,优先使用passlibargon2-cffi
  3. 如果对框架有更强掌控需求,使用PBKDF2并合理设置迭代次数。

任何“自己写一个哈希函数 + 自己管理盐”的方案,都是在给未来埋雷。密码学不是“能跑就行”的领域。

9.2 配置建议

  • 盐长度固定为 32 字节。
  • BCrypt 成本因子从 10 起步,根据登录响应时间调整。
  • Argon2 使用argon2id变体,内存设置建议从 64MB 起步(也可根据服务器内存调整)。
  • 密码最大长度统一限制,避免 BCrypt 截断问题,也避免超大输入造成 DoS。
  • 登录接口必须做频率限制,从源头降低暴力破解成功率。

9.3 日志与审计

  • 不要记录用户明文密码和密码哈希的完整值。
  • 登录失败日志只记录username + ip + 时间戳,不要打印密码。
  • 对“密码修改”“密码重置”“权限变更”等操作记录审计日志。

9.4 安全边界意识

密码存储只是账号安全体系的一环,不要以为“加盐 + 慢哈希”就万事大吉。生产环境还应该考虑:

  • 接口层面的重试限制和风控策略。
  • 是否启用多因素认证(MFA),尤其是管理员账号。
  • 数据库账号最小权限原则,避免一次拖库带走整张用户表。
  • 定期做“脱敏数据 + 测试环境”的演练,确保出现泄露事件时有应急预案。

9.5 一个容易混淆的旁支:SaltStack

最后提一个容易混淆的话题。在技术搜索里搜“salt”,除了密码学中的盐,还有一个高频结果叫SaltStack,这是一个自动化运维和配置管理工具,常用于服务器批量管理和状态编排。它和本文讲的密码盐完全是两码事,只是同名而已。如果你在团队里聊“salt”,一定要先确认上下文,否则很可能出现“我在说密码存储,你在说运维集群”的尴尬场面。

10. 总结与后续学习方向

这篇文章从密码存储的真实风险出发,讲清楚了几个核心问题:

  • 为什么“密码哈希”不等于“密码安全”;
  • 盐(Salt)解决的是彩虹表、批量破解和撞库问题;
  • 盐必须随机、唯一、够长,且不需要保密;
  • 工程上优先使用 BCrypt 或 Argon2 这样的成熟方案;
  • 旧系统升级可以通过“登录时渐进迁移”无感完成;
  • 真正容易翻车的不是算法,而是盐的生成、存储和比较细节。

下一步如果你想把这块知识体系补完整,建议按这个顺序深入:

  1. 阅读 OWASP 的 Password Storage Cheat Sheet,这是密码存储领域最权威的工程指南。
  2. 动手把本文的 Python 示例换成你所在语言的方案,比如 Java 的 Spring Security,Go 的golang.org/x/crypto/bcrypt
  3. 研究 Argon2 的argon2id参数到底怎么调,以及在不同硬件上的表现差异。
  4. 再往深一层,可以了解密码学协议中的 KDF(Key Derivation Function),理解 PBKDF2、scrypt、Argon2 在“从密码派生密钥”这件事上的本质。

对实际项目的提醒只有一句话:如果你的用户表里还有任何不带盐的单次哈希,现在就安排改造时间。数据库泄露不一定发生,但一旦发生,你希望自己手里拿的是“破解成本极高”的那一份用户表,而不是给攻击者准备的“明文自助餐”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询