对于密码,您无法击败bcrypt
. 这是关于 SO 的链接:How do you use bcrypt for hashing passwords in PHP? . 它的主要优点是:它本质上很慢(而且安全)。虽然普通用户会使用一次,并且不会理解十分之一秒和百万分之一秒之间的差异,但破解者会发现,他现在需要12 个世纪而不是在四天内找到密码(除了强制 Blowfish每秒一百万次尝试不需要四天:除非实施错误和尚未预见到的加密突破,否则宇宙的热寂仍将首先出现)。
对于数据,我会依赖数据库引擎本身;MySQL 支持 AES,这非常好。是的,只要有足够多的 GPU 就可以击败它,而且由于要在本世纪内破解代码需要大约 40 亿个 GPU,他可能也会得到一个不错的数量折扣。但我不会担心。
至于密码的“随机化”,没有任何意义。如果有人使用暴力破解您的密码,他将有相同的概率找到随机序列或非随机序列。如果要使用字典攻击可能会有所帮助(即,不要尝试从 AAAAA 到 ZZZZZ 的所有内容,而是尝试 ABRAHAM、ACCORDION ……直到 ZYGOTE)。但即便如此,对服务器的攻击也不会改变任何事情(服务器接收到非随机密码!),对存储哈希的攻击最好通过salting无效,这bcrypt
自动执行:即,存储 $76XJ:MICKEYMOUSE 的散列而不是 MICKEYMOUSE 的散列,以及处理输入时额外的 '$76XJ:' 所需的开销。虽然必须修复随机化,但“$76XJ:”序列在每次写入时都完全不同——祝你好运!
盐
如果您自己实现加盐(如上所示,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) 连接的另一端说出发生了什么并不容易。
出于同样的原因,您不会为“Bad user”和“Bad password”返回两个不同的错误,而始终是“Bad user or password”;对于用户来说,也许可以选择在他/她的注册电子邮件中接收电子邮件,以提醒他/她的用户名。当然,您希望系统在向同一用户发送类似电子邮件后的 24 小时内不发送此类电子邮件,以防止系统被利用以虚假的“恢复电子邮件”骚扰某人。
如果您确实发送了电子邮件,您将等到预设时间到期(例如 3 秒),然后通知用户如果存在用户名,那么他们应该检查他们的收件箱(以及垃圾邮件文件夹以防万一)。
这样的密码本来是有时间的
提高服务器安全性以防止暴力破解的一种便捷方法是延迟密码身份验证,并且可能(如果您真的很偏执)在某些 X 次错误尝试后进行验证码锁定。
您不希望第一次尝试验证码,因为用户对验证码的看法很模糊。
延迟锁定通常使用sleep
(或等效)或使用“锁定直到管理员手动重置”策略来实现。两种方法都不好。锁定功能可用于创建拒绝服务攻击,方法是锁定用户,或创建大量以“密码验证延迟”状态停止的服务器线程(它们不会使用 CPU,但仍会使用内存,套接字和其他资源)。
在某些情况下,这可能会在不知不觉中发生。似乎有些白痴在使用我的银行,每隔几个月我就会收到一条短信,上面写着“在您的家庭银行系统中输入了错误的 PIN”。然后我必须从我的手机登录,以重置不成功尝试计数器;因为如果这个白痴没有意识到他因为输入了我的帐号而无法进入他的帐户,那么三次,我的帐户就会被锁定,我必须亲自去银行请求他们重置我的帐户使用权。我告诉你,这是冥域的一大痛点,即使知道这不是他们的错,我仍然对我的银行感到不满。你不想在你的用户中产生这样的感觉。
最好将负担转移到客户身上:
(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