如何对大文件进行增量式的MD5哈希运算?

6
在浏览器中,我使用JS FileReader().readAsBinaryString()来读取文件。使用CryptoJS库,我可以对数据进行MD5哈希。
这样做很好,但是我不知道如何处理大文件。例如,只需读取2GiB的文件即可使浏览器窗口崩溃。我可以从文件数据中切片块并在执行操作时进行哈希,但这会阻止其他人在不遵循相同步骤的情况下验证相同的哈希值。
在这种情况下,有没有一种方法可以获取大文件的md5哈希值?例如,如何计算1TB文件的md5哈希值?我需要将文件读入流中吗?
这是我第一次尝试此操作,并不确定该如何处理。
这个问题出现在Angular指令中,因此涉及作用域。
var reader = new FileReader();
                reader.onload = function (loadEvent) {
                    scope.$apply(function () {
                        scope.files = changeEvent.target.files;
                        scope.fileread = loadEvent.target.result;
                        scope.md5Data = CryptoJS.MD5(scope.fileread).toString();
                    });
                }
                // First ten megs of the file
                reader.readAsBinaryString((changeEvent.target.files[0]).slice(0, 10 * 1024 * 1024));

这与编程有关,应该放在SO上。 - Karan
一个好的哈希库应该有某种“init”/“update”/“finish” API,您可以在其中为文件的每个块调用“update”。 - CodesInChaos
5个回答

4

使用spark-md5Q

由于其他答案没有提供完整的代码片段,下面是计算大文件MD5哈希值的方法:

function calculateMD5Hash(file, bufferSize) {
  var def = Q.defer();

  var fileReader = new FileReader();
  var fileSlicer = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
  var hashAlgorithm = new SparkMD5();
  var totalParts = Math.ceil(file.size / bufferSize);
  var currentPart = 0;
  var startTime = new Date().getTime();

  fileReader.onload = function(e) {
    currentPart += 1;

    def.notify({
      currentPart: currentPart,
      totalParts: totalParts
    });

    var buffer = e.target.result;
    hashAlgorithm.appendBinary(buffer);

    if (currentPart < totalParts) {
      processNextPart();
      return;
    }

    def.resolve({
      hashResult: hashAlgorithm.end(),
      duration: new Date().getTime() - startTime
    });
  };

  fileReader.onerror = function(e) {
    def.reject(e);
  };

  function processNextPart() {
    var start = currentPart * bufferSize;
    var end = Math.min(start + bufferSize, file.size);
    fileReader.readAsBinaryString(fileSlicer.call(file, start, end));
  }

  processNextPart();
  return def.promise;
}

function calculate() {

  var input = document.getElementById('file');
  if (!input.files.length) {
    return;
  }

  var file = input.files[0];
  var bufferSize = Math.pow(1024, 2) * 10; // 10MB

  calculateMD5Hash(file, bufferSize).then(
    function(result) {
      // Success
      console.log(result);
    },
    function(err) {
      // There was an error,
    },
    function(progress) {
      // We get notified of the progress as it is executed
      console.log(progress.currentPart, 'of', progress.totalParts, 'Total bytes:', progress.currentPart * bufferSize, 'of', progress.totalParts * bufferSize);
    });
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/q.js/1.4.1/q.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/spark-md5/2.0.2/spark-md5.min.js"></script>


<div>
  <input type="file" id="file"/>
  <input type="button" onclick="calculate();" value="Calculate" class="btn primary" />
</div>


2

use SparkMD5 https://github.com/satazor/SparkMD5

var spark = new SparkMD5(); 
spark.append('Hi');
spark.append('there');
var hexHash = spark.end();

它还有一个文件分片的示例


1
我可以从文件数据中切割出一个块并在处理过程中对其进行哈希,但这会防止其他人在不跟随我的步骤的情况下验证相同的哈希值吗?
是的,因此这正是MD5算法在其合同中提供的内容:
1. 您有一个文件 2. 文件通过添加单个“1”和多个“0”进行填充,以便文件可以被512整除。 3. 每次操作都计算文件的512字节切片的md5哈希,并将其与先前的结果组合。
因此,您将不需要重复这些步骤,并确保另一个用户执行相同的操作。
由于MD5是按块计算的,因此可以进行流式传输,如此处所述(虽然使用的是nodejs的crypt模块完成的,该模块是googlecode项目crypto-js的模块化端口)。

http://www.hacksparrow.com/how-to-generate-md5-sha1-sha512-sha256-checksum-hashes-in-node-js.html


最好使用“块”这个词,而不是“轮”。后者在密码学中有特定的含义,与您想传达的内容无关。 - CodesInChaos
同意。感谢澄清。 - Jo Oko

1
你可能想查看 CryptoJS 网站上的 渐进式哈希 段落。
示例:
var sha256 = CryptoJS.algo.SHA256.create();
sha256.update("Message Part 1");
sha256.update("Message Part 2");
sha256.update("Message Part 3");
var hash = sha256.finalize();

SHA256替换为MD5,然后重命名变量(你可以选择一个好的名称)。

尝试使用MD5时,出现错误:“*未捕获(在承诺中)TypeError:无法读取未定义的属性'create'*”。顺便问一下,SHA256(或MD5)是否可以通过以增量方式在所有浏览器中生成相同的校验和来工作。在我的测试中,它们给出了不同的结果。请参见如何在JavaScript中为非常大的文件生成校验和并转换为64位而不会溢出RAM? - iammilind
所以你找不到MD5类。那是一个程序配置问题。而且不同的结果可能是由于输入的二进制差异,例如在Result#text()之后。请确保它完全相同。 - Maarten Bodewes

0

使用方法:

const md5 = await incrementalMD5(file)

incrementalMD5 源代码:

import SparkMD5 from 'spark-md5'

export const incrementalMD5 = file =>
  new Promise((resolve, reject) => {
    const fileReader = new FileReader()
    const spark = new SparkMD5.ArrayBuffer()
    const chunkSize = 2097152 // Read in chunks of 2MB
    const chunks = Math.ceil(file.size / chunkSize)
    let currentChunk = 0

    fileReader.onload = event => {
      spark.append(event.target.result) // Append array buffer
      ++currentChunk
      currentChunk < chunks ? loadNext() : resolve(spark.end()) // Compute hash
    }

    fileReader.onerror = () => reject(fileReader.error)

    const loadNext = () => {
      const start = currentChunk * chunkSize
      const end = start + chunkSize >= file.size ? file.size : start + chunkSize
      fileReader.readAsArrayBuffer(File.prototype.slice.call(file, start, end))
    }

    loadNext()
  })

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接