Pat*_*ski 2 .net c# security hash login
我遇到了一个安全问题。请看下面的伪代码
public static bool TryLogin(string email, string password)
{
if (UserExists(email)) // here is the problem
return false;
var hash = GetRealPasswordHash(email);
var hash2 = GetHash(email, password);
return SlowEquals(hash, hash2);
}
Run Code Online (Sandbox Code Playgroud)
你们中的一些人可能已经看到了这个洞。攻击者可能会通过网络执行时间攻击 - 检测返回答案的速度,并基于该检测数据库中是否有用户!知道这一点后,攻击者现在可以检查他已经知道存在的用户的各种密码!
如果您没有看到问题或不相信这是一个问题,请解释一下(这是针对哈希,但类比问题在这里):
在“长度恒定”时间内比较散列值可确保攻击者无法使用定时攻击在在线系统中提取密码的散列值,然后离线破解它。
检查两个字节序列(字符串)是否相同的标准方法是比较第一个字节,然后是第二个,然后是第三个,依此类推。一旦发现两个字符串的字节不同,您就会知道它们是不同的,并且可以立即返回否定响应。如果您通过两个字符串而没有发现任何不同的字节,则您知道这些字符串是相同的并且可以返回正结果。这意味着比较两个字符串可能需要不同的时间,具体取决于字符串匹配的程度。
例如,字符串“xyzabc”和“abcxyz”的标准比较会立即看到第一个字符不同,而不会费心检查字符串的其余部分。另一方面,当比较字符串“aaaaaaaaaaB”和“aaaaaaaaaaZ”时,比较算法在确定字符串不相等之前扫描“a”块。
在网络上运行定时攻击似乎是不可能的。然而,它已经完成,并且已被证明是实用的。
我应该怎么做呢,当我检测到用户不存在?我绝对应该执行更多计算,但这里有什么好的技术?你们在这种情况下使用什么?
如果这有任何意义,我正在使用 C#。
解决方案很简单,无论用户是否存在,您都遵循相同的代码路径。
public static bool TryLogin(string email, string password)
{
bool userExists = UserExists(email);
var hash = GetRealPasswordHash(email);
var hash2 = GetHash(email, password);
return SlowEquals(hash, hash2) && userExists;
}
Run Code Online (Sandbox Code Playgroud)
现在,您需要确保GetRealPasswordHash为有效电子邮件和无效电子邮件花费相同的时间,并为无效电子邮件返回“假”哈希(Guvante在评论中提到的“虚拟”用户密码哈希) .
这可以确保GetHash和SlowEquals两者仍然使用有效数据调用,但由于userExists错误而忽略它们的结果。
但是我也同意 Guvante 的第二点,任何正常的系统都会在多次尝试无效密码后锁定用户帐户。这可以防止对用户登录的暴力/字典攻击。您只有在一段时间过后或用户呼叫并验证其身份后才解锁登录锁定。