nodejs 加密模块,hash.update() 是否将所有输入存储在内存中

nodejs crypto module, does hash.update() store all input in memory

我有一个 API 代理文件从 browser/client 上传到 AWS S3 的路由。

此 API 路由尝试在上传文件时对其进行流式处理,以避免在服务器内存中缓冲文件的全部内容。

但是,该路由还会尝试计算文件正文的 MD5 校验和。当文件的每个部分都被分块时,hash.update() 方法会被分块调用。

http://nodejs.org/api/crypto.html#crypto_hash_update_data_input_encoding

var crypto = require('crypto');
var hash = crypto.createHash('md5');
function write (chunk) {
  // invoked many times as file is uploaded
  hash.update(chunk);
}
function done() {
  // will hash buffer all chunks in memory at this point?
  hash.digest('hex');
}

Hash 实例是否会缓冲文件的所有内容以执行哈希计算(从而违背了避免将整个文件内容缓冲在内存中的目标)?或者是否可以增量计算 MD5 哈希,而无需整个输入可用于执行计算?

MD5 和其他一些哈希函数基于Merkle–Damgård construction。它支持 incremental/progressive/streaming 数据散列。在将数据转换为内部状态(具有固​​定大小)后,执行最后的终结步骤以通过填充和处理最后一个块生成最终哈希,然后简单地返回最终状态。

这可能也是为什么许多哈希库函数的设计方式都带有更新和完成步骤的原因。

回答你的问题:不,文件内容不保存在缓冲区中,而是转换为固定大小的内部状态。

所有现代加密哈希函数都是以可以增量更新的方式创建的。

为了允许增量更新,消息的输入数据首先按块排列。这些块按顺序处理。为此,实现通常会在内部缓冲输入,直到它有一个完整的块,然后使用所谓的 [=24] 将这个块与 当前状态 一起处理以产生一个新状态=]压缩函数。初始状态通常仅由预先确定的常数值组成。在调用 digest 期间,填充最后一个块 - 通常使用位填充和已处理字节数的编码 - 并计算最终状态;这可能需要一个没有任何消息数据的附加块。可以执行最后的操作,最后返回结果哈希值。

MD5 the Merkle–Damgård construction使用。这种通用结构也用于 SHA-1 和 SHA-2。 SHA-2 是基于 SHA-256 (SHA-224) 和 SHA-512(SHA-384,SHA-512/224 和 SHA-512/256)算法的哈希系列。 MD5 特别使用 512 位的块大小和 128 位的内部状态。最后一个块的内部状态(包括填充)直接输出,没有任何 post-MD5、SHA-1、SHA-256 和 SHA-512 的处理。

Keccak 已被选为 SHA-3。它是基于海绵的结构,具有特定的压缩功能。它不是 Merkle–Damgård 哈希 - 这是 为什么 它被选为 SHA-3 的一个重要原因。它仍然具有 Merkle–Damgård 哈希的所有更新属性,并且 设计 与 SHA-2 兼容。它像前面提到的哈希一样拆分和缓冲块,但它具有更大的内部状态并对输出执行最终操作,可以说它更安全。

因此,当您使用现代哈希结构(如 MD5)时,您会在不知不觉中执行额外的缓冲。幸运的是,缓冲一个 512 位的块 + 128 位的状态大小不太可能让你 运行 内存不足。哈希实现当然不需要在计算最终哈希值之前缓冲整个消息。


备注:

  • MD5 和 SHA-1 被认为是不安全的 w.r.t。抗碰撞性,最好不要再使用它们,尤其是在验证内容时;
  • A "compression function"是一个具体的密码学概念;它不是 LSZIP 或类似的东西;
  • 可能存在以不同方式执行值计算的专门的理论上的哈希值 - 从理论上讲,不需要将输入消息拆分为块并按顺序对块进行操作。不用担心,这些不太可能在您使用的库中;
  • 同样,实现可能决定一次缓冲更多的块,但幸运的是,这种情况也很少见。通常只有一个块用作缓冲区 - 在某些情况下,缓冲几个块可能会更高效;
  • 出于效率原因,一些低级实现可能需要您自己提供块。