对于密码,您无法击败bcrypt。这是关于 SO 的链接:How do you use bcrypt for hashing passwords in PHP?。它的主要优点是:它本来就很慢(而且安全)。虽然普通用户会使用它一次,并且不会意识到十分之一秒和百万分之一秒之间的差异,但破解者会发现,他现在需要 十二个世纪(除了强制 Blowfish 每秒尝试一百万次尝试也不需要四天:除非实施错误和尚未预见的加密突破,the heat death of the Universe will still come first)。
对于数据,我会依赖数据库引擎本身; MySQL 支持 AES,这非常好。是的,只要有足够多的 GPU,就可以击败它,而且由于要在本世纪内破解密码,需要大约 40 亿个 GPU,他可能也会获得不错的数量折扣。但我不会担心。
至于密码的“随机化”,没有任何意义。如果有人使用暴力破解您的密码,他将有相同的概率找到随机序列或非随机序列。如果使用 字典攻击 可能会有所帮助(即,不要尝试从 AAAAA 到 ZZZZZ 的所有内容,而是尝试 ABRAHAM、ACCORDION ……直到 ZYGOTE)。但即便如此,对服务器的攻击也不会改变任何事情(服务器会收到非随机密码!),对存储的哈希的攻击最好通过 salting 来消除,bcrypt 自动确实:即,存储$76XJ:MICKEYMOUSE 的哈希而不是MICKEYMOUSE 的哈希,以及处理输入时额外的'$76XJ:' 所需的开销。虽然必须修复随机化,但“$76X.J:”序列在每次写入时都完全不同——祝你好运搞清楚!
腌制
如果您自己实现加盐(如上所示,使用bcrypt 您不需要),您可以通过生成唯一的随机序列并将其存储在密码字段或第二个字段中来实现。例如
user password
alice d9c06f88:6c14d6d313d7cbcb132b5c63650682c4
然后,在 Alice(“mickeymouse”)收到密码后,您将在数据库中查看是否存在名为 alice 的用户。如果是,请恢复盐(此处为 d9c06f88)和哈希。如果没有,设置一个“BAD”标志并获取一个固定的盐和哈希(例如12345678:0000000...)。
在 MySQL 中,这可以使用 UNION 来完成:
SELECT password_fields FROM users WHERE user=? AND hash=?
UNION SELECT
'12345789' as salt,
'ffffffffffffffffffffffff' as hash,
'fake' as user
LIMIT 1;
将在大致相同的时间检索正确的数据或不正确的数据集(这可以防止有人通过计时回答“错误的用户或密码”所需的时间来猜测哪些名称存在,哪些不存在)。
然后连接盐和密码,生成d9c06f88:mickeymouse的哈希。如果不匹配,或设置了“BAD”标志,拒绝密码(而不是使用“bad”标志,您可以重复 MySQL 已经进行的用户匹配测试:您可以通过将最后一个字符替换为无效字符来初始化用户名,以确保它永远不会匹配真实用户。
通过非信息实现安全
选择固定字符串的附加扭曲很有用,因为您希望“用户不存在”、“用户存在但密码不正确”和“用户存在且密码正确”这三种情况相同(相同复杂性,相同的计算费用)尽可能。
这样,攻击者就不太可能知道发生了什么:用户不正确吗?密码错了吗?等等。如果时间相差足够大(比如两次查询有效用户,一次查询无效用户),一个小心翼翼、时间和好的秒表的攻击者可以统计确定给定用户名是否存在。
在用户的情况下,连名字比较都会
johnsmith against johnsmith if johnsmith exists
johnsmith against johnsmit? if johnsmith does not exist
因此,从 HTTP(s) 连接的另一端说出发生了什么并不容易。
出于同样的原因,您将不会为“错误用户”和“错误密码”返回两个不同的错误,但始终返回“错误用户或密码”;对于用户来说,也许可以选择在他/她的注册电子邮件中接收电子邮件,以提醒他/她使用用户名。当然,您希望系统在向同一用户发送类似电子邮件后的 24 小时内不发送此类电子邮件,以防止系统被利用以虚假“恢复”来骚扰某人电子邮件”。
如果您确实发送电子邮件,您将等到预设时间到期(例如 3 秒),然后通知用户如果用户名存在,那么他们应该检查他们的收件箱(以及垃圾邮件文件夹)。
这样的密码是有时间的
提高服务器安全性以防止暴力破解的一种便捷方法是在密码身份验证上实施延迟,并且可能(如果您真的很偏执)在几次错误尝试后进行验证码锁定。
您不希望第一次尝试验证码,因为用户对验证码的看法很模糊。
延迟锁定通常使用sleep(或等效项)实现,或者使用“锁定直到管理员手动重置”策略。 这两种方法都不好。锁定功能可用于创建拒绝服务攻击,方法是锁定用户,或创建大量在“密码验证延迟”状态下停止的服务器线程(它们不会使用 CPU,但它们仍会 使用内存、套接字和其他资源)。
在某些情况下,这可能会在不知不觉中发生。似乎有些白痴在使用我的银行,每隔几个月我就会收到一条短信,上面写着“在您的家庭银行系统中输入了错误的 PIN”。然后我必须从我的手机登录,以重置不成功尝试计数器;因为如果白痴没有意识到他无法进入他的帐户,因为他输入了我的帐号,三遍,它是我的 em> 帐户被锁定,我必须亲自去银行请求他们重置我的访问权限。让我告诉你,这是下层地区的主要痛苦,即使知道这不是他们的错,我仍然怨恨我的银行。你不想让你的用户产生这样的感觉。
最好把负担转移到客户身上:
(very pseudo code)
login = FAIL
if in SECURITY LOCKOUT MODE for this account
if a session is open and contains a last-attempt time
if at least DELAY seconds have elapsed since last-attempt
check the password
if it is correct
login = OK
zero password counter, exit lockout mode.
# An "innocent" user enters with no lockout! Yay!
end
else
# "Early knocker". Either a bruteforcing robot
# or a too clever user in a hurry. But we can't
# tell them apart.
end
else
# No session. Either a sessionless bruteforcing robot
# or a unaware, innocent user. Again we can't tell them
# apart. So we bounce both.
# But a genuine user will assume he has mistyped the password,
# or anyway will read the warning page, and will login after ONE
# round of delay.
# Users with password saved in browser will just click
# "login" again and be logged in.
# A robot will find itself delayed and ALL ITS PASSWORDS IGNORED
# every single time. Even if it finds the right password... it will
# not work.
end
else
check the password
if it is correct
# Good user, first attempt, fast login.
login = OK
else
# Beginning to doubt this is a good user...
increase password counter
if it is > MAX_ATTEMPTS
enter SECURITY LOCKOUT MODE for this account
end
end
end
if login is not OK
generate a page with HTTP_REFRESH time of DELAY+1 seconds
and a session ID, saying "User or password unknown,
or you tried to login before HH:MM:SS (DELAY seconds).
The page might also contain a Javascript timer, just in
case. The delay is 1s more than necessary as a safety
margin.
end