密码认证
输入验证
- 密码至少应为 8 个字符。
- 不要将最大密码长度设置得太低,建议范围为 64-256 个字符。
- 不要默默地修改或截断输入。
- 应允许所有有效的 Unicode 字符,包括空格。
- 使用类似
zxcvbn的库来检查弱密码。 - 使用 haveibeenpwned 等 API 检测泄露的密码。
检查被泄露的密码
可以使用 haveibeenpwned 这样的免费服务检查密码是否在过去泄露中。使用 SHA-1 对密码进行哈希(十六进制编码),并发送前 5 个字符。
GET https://api.pwnedpasswords.com/range/12345
API 将提供以提供的 5 个字符开头的哈希密码后缀列表。
ec68dea7966a1ea2ba9408be4dcc409884f
248b2dddf14a111b9d08b906d06224a0a79
f10a49ecd2ada17a120dc359f162b84e12c
密码存储
密码必须在存储前进行加盐和哈希。我们推荐使用带加盐的 Argon2id。
基本上,哈希是一种生成输入唯一表示的单向过程。相同的输入应产生相同的哈希。与加密不同,它是不可逆的——无法从哈希中获得原始数据。常见的例子包括 MD5、SHA-1 和 SHA-256——不要将它们用于密码。
哈希确保即使数据泄露,黑客也无法获得原始密码。这在泄露范围有限的情况下尤为重要。即使他们只能读取用户表,一旦获取用户密码,他们就能访问所有内容。更重要的是,它保护您的用户免受进一步伤害。用户经常重复使用密码,泄露的密码可能会让黑客访问其他应用程序中的用户账户。
然而,密码的一个大问题是它们并不是真正随机的。技术上讲,8 个字符的字母数字密码有 (62^8) 种可能,但实际上,大多数密码使用常见的单词和名字,可能在末尾加上一些数字。这大大减少了暴力破解密码时需要测试的组合数量。
因此,使用专为密码设计的慢速哈希算法。常见的哈希算法如 SHA-256 旨在尽可能快地运行。
即使使用慢速算法,也可以使用一个预计算的常用密码哈希表,称为彩虹表。加盐是一种常用技术,通过在哈希之前向每个密码添加随机值来防止这些攻击。盐必须使用加密安全的随机生成器生成,并且应具有至少 120 位的熵。
salt = randomValues()
hash = hashPassword(password + salt) + salt
另一种选择是使用秘密密钥进行加密(peppering)。盐存储在哈希旁边,而秘密密钥存储在单独的位置。自己实现哈希机制可能是个坏主意,因此只有在算法支持的情况下才应这样做。
在比较密码哈希时,使用常量时间比较而不是 ==。这确保您的应用程序不易受到基于时间的攻击,攻击者可以通过比较密码与哈希所需的时间来提取信息。
import (
"crypto/subtle"
"golang.org/x/crypto/argon2"
)
var storedHash []byte
var password []byte
hash := argon2.IDKey(password, salt, 2, 19*1024, 1, 32)
if (subtle.ConstantTimeCompare(hash, storedHash)) {
// 有效密码。
}
Argon2id 应为首选,其次是 Scrypt,然后是用于遗留系统的 Bcrypt。
密码哈希计算资源密集,容易受到拒绝服务(DoS)攻击。
Argon2id
Argon2 是 2013 年密码哈希竞赛的获胜者,具有 3 个版本:Argon2i、Argon2d 和 Argon2id。Argon2id 应为默认选项,因为它在抵御侧信道和 GPU 攻击方面提供了良好的平衡。推荐的最低参数:
memorySize: 19456 (19 MB)iterations: 2parallelism: 1
可选地使用 secret 参数对哈希进行加密。查看 OWASP 详情。
Scrypt
推荐的最低参数:
N: 16384P: 16r: 1dkLen: 64
Bcrypt
工作因子至少应为 10。
Bcrypt 的最大输入长度为 72 字节,一些实现可能限制为 50 字节。使用 SHA-256/512 等算法进行预哈希并不推荐,因为一些 Bcrypt 实现无法处理空字节。也不要尝试通过使用 HMAC 实现加密。若需支持更长的密码,请使用类似 Argon2id 或 Scrypt 的算法。
防止暴力破解
密码容易受到暴力破解攻击。主要有两种暴力破解的方法:
- 攻击者尝试一堆常用密码。
- 攻击者使用泄露的密码针对特定账户(凭证填充)。
多因素认证 (MFA) 是防御暴力破解攻击的最佳方法。虽然它不能阻止暴力破解攻击本身,但它几乎使攻击无效。应推荐用户启用 MFA,并应要求安全关键应用程序使用。
应始终实施基于 IP 的限流。一个基本的例子是,在同一 IP 地址连续失败 10 次后,阻止所有尝试 10 分钟。其他方法包括在每次锁定时增加锁定时间,并在锁定后逐渐允许新的尝试。这也可以防止 DoS 攻击,因为密码哈希计算资源密集。还可以在 IP 限流的基础上实现基于标识符的限流,尽管这可能引入 DoS 漏洞(参见 设备 cookies)。
您可以实施的另一层安全性是使用 Captcha 等测试进行机器人检测。
最后,确保用户密码的强度。确保密码不弱且未曾泄露。参见 密码验证 部分。
错误处理
作为一个好的经验法则,错误消息应模糊且通用。例如,登录表单应显示“用户名或密码错误”,而不是“用户名错误”或“密码错误”。同样,登录表单不应透露电子邮件是否已被现有账户使用。
然而,从用户体验的角度来看,直接告诉用户他们的用户名或电子邮件不正确更为友好。这对于用户名已经公开(例如社交媒体)或知道电子邮件的有效性并不重要的网站(即大多数网站)来说是可以的。这使得暴力破解攻击稍微容易一些,因为攻击者只需猜测密码,但您应该已经实施了适当的措施。
如果需要保持用户名或电子邮件私密,请确保不要通过注册表单和密码重置表单泄露此类信息。例如,在创建账户时,可以提示用户“我们已向您的收件箱发送了一封包含进一步说明的电子邮件”,无论电子邮件是否已被占用。如果他们已经有账户,可以在电子邮件中包含该信息。即使返回通用消息,通过检查响应时间仍可能确定用户是否存在。例如,只有在用户名有效时才验证密码。防止时间攻击很难,所以只有在严格要求的情况下才走这条路。
其他注意事项
- 不要阻止用户复制粘贴密码,因为这会阻止用户使用密码管理器。
- 不要要求用户定期更改密码。
- 当用户尝试更改密码时,请询问当前密码。
- 开放重定向。