如何使用JavaScript计算文件的MD5哈希值


Answers:


92

虽然有MD5算法的JS实现,但是较旧的浏览器通常无法从本地文件系统读取文件

我在2009年写道。那么新的浏览器呢?

使用支持FileAPI的浏览器,您可以*读取文件的内容 -用户必须通过<input>元素或拖放方式选择文件。截至2013年1月,以下是主要浏览器的堆叠方式:


30
除了无法在JS中访问文件系统外,我完全不会在客户端生成的校验和中放置任何信任。因此,无论如何都必须在服务器上生成校验和。
Tomalak

4
@Tomalak如果您只想上传与现有内容不同的文件,则必须在客户端上执行该操作。
约翰,

2
@John嗯,我的陈述并不排除这一点。严格来说,客户端检查是为了用户方便(因此或多或少是可选的,具体取决于您希望这样做的方便程度)。另一方面,服务器端检查是强制性的。
Tomalak

pajhome.org.uk/crypt/md5中的md5函数不支持二进制作为输入?我认为有必要计算浏览器中上传图像的二进制流。谢谢。
jiajianrong

如果可以,请在答案中添加一些示例代码。这会很有帮助。
cbdeveloper19年

30

我制作了一个实现增量md5的库,以便有效地对大型文件进行哈希处理。基本上,您以块为单位读取文件(以保持较低的内存)并逐步对其进行哈希处理。自述文件中提供了基本用法和示例。

请注意,您需要HTML5 FileAPI,因此请务必进行检查。测试文件夹中有完整的示例。

https://github.com/satazor/SparkMD5



1
嘿,这很棒!我尝试了CryptoJS,由于某种原因从来没有得到过准确的MD5,这就像一个魅力!有任何关于sha256的计划吗?@satazor
cameck

@cameck,图书馆很好。但是我今天尝试了一下,看来方法上有问题.end()。如果再次调用此方法,则下次将给出错误的结果。因为内部.end()通话.reset()。这是一场编码灾难,不利于图书馆写作。
iammilind

感谢图书馆!放一个最小的代码:dev.to/micmo/compute-md5-checksum-for-file-in-typescript-59a4
Qortex

27

使用CryptoJSMD5函数HTML5 FileReader API计算MD5哈希值非常容易。以下代码段显示了如何读取二进制数据并从已拖动到浏览器的图像中计算MD5哈希值:

var holder = document.getElementById('holder');

holder.ondragover = function() {
  return false;
};

holder.ondragend = function() {
  return false;
};

holder.ondrop = function(event) {
  event.preventDefault();

  var file = event.dataTransfer.files[0];
  var reader = new FileReader();

  reader.onload = function(event) {
    var binary = event.target.result;
    var md5 = CryptoJS.MD5(binary).toString();
    console.log(md5);
  };

  reader.readAsBinaryString(file);
};

我建议添加一些CSS以查看“拖放”区域:

#holder {
  border: 10px dashed #ccc;
  width: 300px;
  height: 300px;
}

#holder.hover {
  border: 10px dashed #333;
}

可在此处找到有关拖放功能的更多信息:File API和FileReader

我在Google Chrome版本32中测试了该示例。


2
问题是,readAsBinaryString()尚未标准化,Internet Explorer不支持。我没有在Edge中测试过它,但即使IE11也不支持它。
StanE

@ user25163 Internet Explorer(和Opera Mini)似乎是唯一不支持的现代浏览器readAsBinaryString()caniuse.com/#feat=filereader-Microsoft Edge支持它。
Benny Neugebauer

感谢有关MS Edge的信息!我在一家公司工作。而且您知道,客户经常使用旧软件,并且很难说服他们更新软件。我只想指出,readAsBinaryString()由于较旧的浏览器不支持使用它,因此必须谨慎使用。我发现的另一个选择是SparkMD5。它也使用FileReader API,但是readAsArrayBufferIE支持该方法。它可以通过分块读取来处理大型文件。
StanE

2
CryptoJS现在支持通过以下方式从ArrayBuffer转换为Binary / WordArray:CryptoJS.lib.WordArray.create(arrayBuffer);
Warren Parad,

@WarrenParad然后如何修改上面的代码以与ArrayBuffer一起使用?啊,在这里找到它:stackoverflow.com/questions/28437181/…–
TheStoryCoder

9

HTML5 + spark-md5Q

假设您使用的是现代浏览器(支持HTML5 File API),这是计算大型文件的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>


8

您需要使用FileAPI。它在最新的FF和Chrome中可用,但在IE9中不可用。抓住上面建议的任何md5 JS实现。我已经尝试过并放弃了它,因为JS太慢了(在大图像文件上几分钟)。如果有人使用类型化数组重写MD5,则可能会重新访问它。

代码看起来像这样:

HTML:     
<input type="file" id="file-dialog" multiple="true" accept="image/*">

JS (w JQuery)

$("#file-dialog").change(function() {
  handleFiles(this.files);
});

function handleFiles(files) {
    for (var i=0; i<files.length; i++) {
        var reader = new FileReader();
        reader.onload = function() {
        var md5 = binl_md5(reader.result, reader.result.length);
            console.log("MD5 is " + md5);
        };
        reader.onerror = function() {
            console.error("Could not read the file");
        };
        reader.readAsBinaryString(files.item(i));
     }
 }

bentewey指出的Webtoolkit MD5的性能要好得多,对于一个MB大小的文件,性能为16s:webtoolkit.info/javascript-md5.html
Aleksandar Totic 2011年

1
我已经设法使它正常工作,并且为文本文件生成了相同的md5哈希值(php:md5_file(...)),但是图像却给了我不同的结果?这与二进制数据或其上载方式有关吗?
城堡

我很确定此代码不适用于多个文件,因为onload是一个回调,reader在onload函数运行时,变量将是最后一个文件。
戴夫

CryptoJS现在支持通过以下方式从ArrayBuffer转换为Binary / WordArray:CryptoJS.lib.WordArray.create(arrayBuffer);
Warren Parad,

4

除了无法在JS中访问文件系统外,我完全不会在客户端生成的校验和中放置任何信任。因此,无论如何都必须在服务器上生成校验和。– Tomalak 09年4月20日在14:05

在大多数情况下这是没有用的。您希望在客户端计算MD5,以便可以将其与在服务器端重新计算的代码进行比较,并得出结论:如果两者不同,则上传将出错。我需要在处理大型科学数据文件的应用程序中执行此操作,而接收未损坏的文件是关键。我的案例很简单,因为用户已经从他们的数据分析工具中计算出了MD5,所以我只需要使用文本字段向他们询问即可。




1

希望您现在找到了一个好的解决方案。如果没有,下面的解决方案是基于js-spark-md5的ES6 promise实现

import SparkMD5 from 'spark-md5';

// Read in chunks of 2MB
const CHUCK_SIZE = 2097152;

/**
 * Incrementally calculate checksum of a given file based on MD5 algorithm
 */
export const checksum = (file) =>
  new Promise((resolve, reject) => {
    let currentChunk = 0;
    const chunks = Math.ceil(file.size / CHUCK_SIZE);
    const blobSlice =
      File.prototype.slice ||
      File.prototype.mozSlice ||
      File.prototype.webkitSlice;
    const spark = new SparkMD5.ArrayBuffer();
    const fileReader = new FileReader();

    const loadNext = () => {
      const start = currentChunk * CHUCK_SIZE;
      const end =
        start + CHUCK_SIZE >= file.size ? file.size : start + CHUCK_SIZE;

      // Selectively read the file and only store part of it in memory.
      // This allows client-side applications to process huge files without the need for huge memory
      fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
    };

    fileReader.onload = e => {
      spark.append(e.target.result);
      currentChunk++;

      if (currentChunk < chunks) loadNext();
      else resolve(spark.end());
    };

    fileReader.onerror = () => {
      return reject('Calculating file checksum failed');
    };

    loadNext();
  });

1

下面的代码片段显示了一个示例,该示例可以在读取和哈希文件时将400 MB / s的吞吐量归档。

它使用的是名为hash-wasm的库,该库基于WebAssembly,比仅使用js的库更快地计算哈希。截至2020年,所有现代浏览器均支持WebAssembly。

const chunkSize = 64 * 1024 * 1024;
const fileReader = new FileReader();
let hasher = null;

function hashChunk(chunk) {
  return new Promise((resolve, reject) => {
    fileReader.onload = async(e) => {
      const view = new Uint8Array(e.target.result);
      hasher.update(view);
      resolve();
    };

    fileReader.readAsArrayBuffer(chunk);
  });
}

const readFile = async(file) => {
  if (hasher) {
    hasher.init();
  } else {
    hasher = await hashwasm.createMD5();
  }

  const chunkNumber = Math.floor(file.size / chunkSize);

  for (let i = 0; i <= chunkNumber; i++) {
    const chunk = file.slice(
      chunkSize * i,
      Math.min(chunkSize * (i + 1), file.size)
    );
    await hashChunk(chunk);
  }

  const hash = hasher.digest();
  return Promise.resolve(hash);
};

const fileSelector = document.getElementById("file-input");
const resultElement = document.getElementById("result");

fileSelector.addEventListener("change", async(event) => {
  const file = event.target.files[0];

  resultElement.innerHTML = "Loading...";
  const start = Date.now();
  const hash = await readFile(file);
  const end = Date.now();
  const duration = end - start;
  const fileSizeMB = file.size / 1024 / 1024;
  const throughput = fileSizeMB / (duration / 1000);
  resultElement.innerHTML = `
    Hash: ${hash}<br>
    Duration: ${duration} ms<br>
    Throughput: ${throughput.toFixed(2)} MB/s
  `;
});
<script src="https://cdn.jsdelivr.net/npm/hash-wasm"></script>
<!-- defines the global `hashwasm` variable -->

<input type="file" id="file-input">
<div id="result"></div>



-1

我不相信javascript中有一种方法可以访问文件上传的内容。因此,您无法查看文件内容以生成MD5和。

但是,您可以将文件发送到服务器,然后服务器可以将其发送回MD5或将文件内容发送回..但这是很多工作,可能对您而言不值得。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.