为什么 node.js scrypt 函数会这样使用 HMAC?

信息安全 hmac 加密 节点.js
2021-08-14 16:23:49

根据文档,scrypt 哈希函数的工作方式如下:

哈希函数执行以下操作:

  • 添加随机盐。
  • 创建 HMAC 以防止主动攻击。
  • 使用 scrypt 密钥派生函数来派生密钥的散列。

哈希格式

所有哈希都以“scrypt”这个词开头。接下来是密钥派生函数中使用的 scrypt 参数,然后是随机盐。最后,附加先前内容的 256 位 HMAC,HMAC 的密钥由 scrypt 密钥派生函数生成。结果是一个 768 位(96 字节)的输出:

  1. 字节 0-5:单词“scrypt”
  2. 字节 6-15:Scrypt 参数 N、r 和 p
  3. 字节 16-47:32 位随机盐
  4. 字节 48-63:16 位校验和
  5. 字节 64-95:字节 0 到 63 的 32 位 HMAC,使用由 scrypt 密钥派生函数生成的密钥。

字节 0 到 63 以明文形式保留。这是必要的,因为这些字节包含验证哈希所需的元数据。这些信息没有被加密并不意味着安全性被削弱。就安全性而言,最重要的是散列完整性(意味着散列输出的任何部分都不能更改)并且无法从散列输出中确定原始密码(这就是您使用 scrypt 的原因 - 因为它在好办法)。字节 64 到 95 是所有这些发生的地方。

我的问题是为什么它使用 scrypt 哈希作为 HMAC 算法的密钥,而不是直接返回 scrypt 哈希?这提供了什么额外的保护?它提到了“主动攻击”,但没有提供细节。

2个回答

我创建了 Node Scrypt 模块。

HMAC 增加了额外的安全性。使用它还可以将该方案用作加密文件格式的标头(就像在 tarsnap 中所做的那样),而不仅仅是在身份验证服务器的数据库中。此外,Colin Percival(创建 scrypt)使用此方案进行验证(实际上我只是从他那里复制的)。

为了解释为什么使用 HMAC,让我们快速回顾一下。当使用 scrypt 密钥派生函数加密某些东西时,会产生一个 96 字节的结果,其分解如下:

 bytes 0-5: The word "scrypt"
 byte 6: 0
 byte 7: logN
 bytes 8-11: r
 bytes 12-15: p
 bytes 16-47: salt (which is 32 bytes)
 bytes 48-63: A 16 byte SHA256 checksum (hash) of the contents of bytes 0 to 47
 bytes 64-95: A 32 byte HMAC hash of bytes 0 to 63 with the key being the scrypt cryptographic hash

字节 0 到 47 必须是明文(不得以任何方式更改或加密),这一点至关重要。为确保这一点,有一个 16 字节的 SHA256 校验和。现在,虽然 SHA 可以非常有效地用作校验和(特别是在这种情况下),但它无法防范主动攻击,这意味着有人已经掌握了有效载荷,替换了他们自己的值。例如,我可以获取有效负载,计算我自己的 logN、r 和 p 以及我自己的校验和,然后将其作为原始数据传递出去。

为了防止这种情况发生,最后的 32 个字节是 HMAC。HMAC 用于确保消息完整性(即防止任何人主动更改有效负载),并且是加密武器库的主力军(阅读:使用起来安全可靠)。HMAC 需要一个密钥,我们使用 scrypt 哈希作为密钥。

如果最后 32 个字节只是一个 scrypt 散列,那么没有什么能阻止主动攻击者破坏所有内容并替换他们自己的 scrypt 散列。HMAC 可以防止。它不仅可以作为验证 scrypt 哈希的一种手段,还可以检查整个方案的完整性。

顺便说一句:人们可能想知道为什么需要校验和(字节 48 到 63)。好吧,如果您考虑一下,我们需要计算 scrypt 哈希,以便它可以用作 HMAC 的密钥。所以校验和增加了一个额外的检查级别:如果它没有成功,那么验证立即返回 false 而不进行任何进一步的检查。

稍后在文档中解释:

如果您对这个模块感兴趣是生成哈希来存储密码,那么我强烈建议您使用哈希函数。密钥派生函数不产生任何消息验证码以确保完整性。您还必须单独存储 scrypt 参数。最后,此模块中不包含本机验证功能。

如果您只想使用 scrypt 算法导出密钥,则可以使用此包中也包含的KDF 函数来实现。

这个包中所谓的散列函数是在原始 scrypt 散列函数之上的一个包装器。