3分钟讲解。
上周的面试故事
职位:初级安全工程师,刚毕业。
开始面试。
我:“这里你提到对数据安全有很好的理解。你能举例说明哪些方面的数据安全吗?”
A:“当然。例如,当我们构建一个系统时,会有一个用户模块,当我们在数据库的用户表中存储密码时,我们需要在存储之前对其进行加密。”
我:“你确定是加密而不是哈希处理吗?”
A:“是的。”
我:“那你把密钥存在哪里?”
A:“什么密钥?”
我:“你用来加密密码的加密密钥。是对称加密还是非对称加密?每个用户都有一个密钥还是共享一个密钥?”
A:“嗯……我们没有使用这些。所以应该是哈希处理。”
我:“没关系。你能解释一下为什么我们需要在存储之前进行哈希处理,而不是存储明文吗?”
A:“是的。因为我们想要确保安全。当我们验证密码时,密码不能以明文形式从UI发送到服务器进行验证。”
我:“当你注册用户时,是否需要以明文形式发送密码和确认密码?”
A:“是的,但那是唯一一次我们发送明文。”
我:“好的,我明白你的意思。你认为在数据库中存储哈希值的原因是为了减少在API调用期间发送敏感数据为明文的机会?”
A:“没错。”
我:“那么你认为当数据库被人入侵时,存储哈希值会有意义吗?”
A:“哦,是的,这样原始密码就不会被黑客看到。哈希值可以防止这一点。”
我:“是的,但这还不够。有人仍然可能使用彩虹表攻击,通过哈希碰撞得到原始值。你知道什么是盐(salt)吗?”
A:“听说过。值应该是一个随机字符串,这会使密码存储更安全。”
我:“很好。你知道它是如何工作的吗?”
A:“不太清楚。”
我:“假设有人通过SQL注入或其他方式获取了数据库,然后使用彩虹表进行攻击,发生哈希碰撞并得到了原始文本,但他得到的是混合了随机字符串的原始密码,这样我们可以防止原始密码被窃取。这意味着我们阻止了一些人获取你的‘一键登录’密码来登录你的邮件、社交媒体甚至银行账户。”
A:“我明白了。”
我:“那么你们目前系统中使用的是哪种哈希算法?”
A:“我们知道md5不安全,所以我们使用了其他选项,比如sha3。”
我:“你能简要说明一下为什么md5不安全吗?为什么选择sha3来哈希密码?因为据我所知,sha3的KDF(如hmac-sha3)并不是为哈希密码设计的,而是用于其他用途,如JWT令牌签名。”
A:“md5被证明是不安全的,已经有一些报道。但是KDF是什么?”
我:“KDF是密钥派生函数。这意味着你使用的哈希算法会将密码作为输入生成哈希值。是的,有报道说md5不安全。但你知道为什么吗?”
A:“嗯,不太清楚。”
我:“简而言之,这是因为它的哈希碰撞抵抗力弱,可能被彩虹表攻击。”
A:“什么是彩虹表?”
我:“彩虹表是一种预计算的表格,包含从明文密码派生的哈希值链。这些链通过反复应用哈希函数生成,然后通过一系列还原函数将结果哈希值还原为明文密码。彩虹表通常用于密码破解攻击中,攻击者将存储在被入侵数据库中的哈希密码与彩虹表中的条目进行比较,以找到匹配的明文密码。”
A:“我明白了。”
我:“那你为什么使用sha3来生成密码?”
A:“我觉得它比MD5更安全。”
我:“是的,但可能有更好的密码哈希选项。比如2015年获奖的argon2,比sha3需要更多的迭代和更高的RAM,这意味着更高的时间复杂度和资源消耗,从而使攻击者更难以进行暴力破解。但sha3通常用于其他用途,例如数字签名,平衡安全性和性能。”
……
让我们逐个分析这个面试中的细节。
密码哈希
我们都知道,在21世纪,密码绝不能以明文形式保存。
然而,这不仅仅是为了减少在某些API调用期间发送密码值为明文的机会。 主要的关注点是当数据库落入坏人之手时,使其无法恢复原始文本。 接下来发生的事情是数据库将被“凭证填充攻击”。相同的凭证会在不同的账户上反复尝试:电子邮件、银行卡、社交媒体、学校,甚至是政府账户。
为什么不使用加密而是哈希?
关键区别在于可逆性。加密是一个可逆的过程,这意味着使用正确的密钥可以恢复原始的明文密码。这构成了一个安全风险,即使加密密钥被安全管理。 此外,管理加密密钥,如构建密钥链,可能会带来显著的成本和复杂性。
为什么需要盐(salt)
盐用于为每个哈希密码添加随机性,使攻击者使用预计算表如彩虹表来破解密码变得计算上昂贵。 没有盐,攻击者可以通过将哈希值与大型预计算哈希数据库进行比较来有效猜测密码。 此外,盐必须对每个密码唯一,而不仅仅是对每个用户唯一。这确保即使两个用户有相同的密码,由于唯一的盐,他们的哈希值也会不同,防止攻击者轻易识别重复的密码。
不同用途的哈希
Sha3比md5更安全,为什么不使用sha3?哈希不仅用于密码值,还有不同的使用场景:
- 数据完整性(如SHA3):哈希算法如SHA3通常用于指纹文件,确保其完整性保持不变。这些算法需要在速度和哈希碰撞抵抗之间找到平衡。虽然SHA3提供了良好的碰撞抵抗,但它也保持了文件指纹任务的合理性能。
- 校验和(如CRC32):当速度至关重要时,如在网络传输期间验证数据完整性,CRC32等算法表现出色。CRC32通常用于快速识别文件在传输过程中是否被修改,优先考虑效率而不是碰撞抵抗。
- 密钥生成(如Argon2、bcrypt、PBKDF2):为了安全地存储密码或生成加密密钥,首选专门的密钥派生函数(KDF)如Argon2、bcrypt或PBKDF2。这些算法故意引入“慢”哈希过程,通过增加计算复杂性来抵御哈希碰撞的暴力破解。
虽然SHA3适用于HTTPS连接或JWT令牌签名生成等任务,但它缺乏提供密码哈希所需的必要“慢”特性。
为什么KDF在密码哈希中很重要
KDF在密码哈希中通过故意减慢哈希过程起着关键作用。这种故意的减慢是通过多次迭代哈希值计算实现的。虽然这些迭代的技术细节可能很复杂,但它们本质上涉及重复操作如移位、反转和XOR-ing哈希值,执行数千轮。
使用KDF哈希密码的主要目标是:
- 减慢过程:通过引入计算开销,KDF使攻击者更难以暴力破解密码。计算哈希值所需的时间和资源增加,作为快速词典或彩虹表攻击的威慑。
- 资源消耗:KDF在哈希过程中还会消耗大量的CPU和内存资源。这种资源密集型特性进一步阻碍了攻击者,因为他们必须投入大量的计算能力和时间来破解哈希密码。
什么是彩虹表攻击?
简要解释:
- 词典表:在彩虹表之前,攻击者预计算所有哈希值,并在一个称为“词典表”的表中枚举所有哈希值。然后,在获取目标数据库表后,进行哈希碰撞并得到关联的原始密码值(这也是为什么盐如此重要)。
- 然而,词典表可能会迅速增长,最终包含大量行,变得不可用。
- 彩虹表可以被视为“词典表的高效版本”。
- 在彩虹表中,有两个函数。H:从明文获取哈希值,R:从哈希值获取明文。
- 存储多行,每行是一个“哈希链”,每行从一个明文开始,然后多次进行H和R。
- 对目标数据库表进行哈希碰撞,一旦哈希值匹配,进行R以获取明文。
最后
为什么md5(或sha1)不能用于密码哈希?如果可以,md5还有其他用途吗?
简单回答:弱哈希碰撞抵抗。
- md5在2004年被证明具有弱哈希碰撞抵抗。
- sha1也在2005年被证明具有相同的问题,并由谷歌在2017年宣布。
- 我们可以将md5用于其他用途吗?比如指纹或校验和?不行。已被证明,两个不同的文件可以生成相同的md5;如果用于校验和,crc32具有更快的性能。
- md5没有使用场景,你永远不应该使用它。