如何使用 javascript 计算文件的 md5哈希

有没有一种方法可以在上传到服务器之前使用 Javascript 计算文件的 MD5散列?

167876 次浏览

While there are JS 实现 of the MD5 algorithm, 较旧的浏览器通常无法从本地文件系统读取文件.

这是我在2009年写的,那么新的浏览器呢?

使用支持 FileAPI的浏览器,can读取文件 的内容——用户必须选择它,要么使用 <input>元素,要么使用拖放。截至2013年1月,主流浏览器的情况如下:

怎么做到的?

参见使用 MD5 function of CryptoJS下面是由本尼 · 纽格鲍尔(Benny Neugebauer)提供的答案

互联网上有一些创建 MD5哈希的脚本。

来自 webtoolkit 的那个很好,http://www.webtoolkit.info/javascript-md5.html

但是,我不相信它能够访问本地文件系统,因为这种访问是有限的。

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

然而,您可以将文件发送到服务器,然后服务器可以发送一个 MD5总和或发送文件内容回来。.但那是很多工作,可能对你的目的来说不值得。

除了不可能得到 在 JS 文件系统访问,我不会 把任何信任都放在一个 客户生成的校验和,所以 在服务器上生成校验和 在任何情况下都是强制性的。-托马拉克 09年4月20日14:05

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

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

Code would look something like this:

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));
}
}

I've made a library that implements incremental md5 in order to hash large files efficiently. Basically you read a file in chunks (to keep memory low) and hash it incrementally. 在自述文件中有基本的用法和示例。

请注意,您需要 HTML5FileAPI,因此一定要检查它。 测试文件夹中有一个完整的示例。

Https://github.com/satazor/sparkmd5

要获取文件的散列,有很多选项。通常情况下,问题是获取大文件的散列非常慢。

我创建了一个获取文件散列的小库,文件的开始位置为64kb,结束位置为64kb。

实例: http://marcu87.github.com/hashme/和库: https://github.com/marcu87/hashme

使用 CryptoJS 的 MD5功能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;
}

关于拖放功能的更多信息可以在这里找到: 文件 API 和文件阅读器

我在 GoogleChromeVersion32中测试了这个示例。

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>

希望你现在已经找到了一个好的解决办法。如果没有,下面的解决方案是基于 Js-park-md5的 ES6承诺实现

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();
});

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

它正在使用一个名为 一团糟的库,该库基于 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>

If sha256 is also fine:

  async sha256(file: File) {
// get byte array of file
let buffer = await file.arrayBuffer();


// hash the message
const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);


// convert ArrayBuffer to Array
const hashArray = Array.from(new Uint8Array(hashBuffer));


// convert bytes to hex string
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
return hashHex;
}

This is another 一团糟 example, but using the streams API, instead of having to set FileReader:

async function calculateSHA1(file: File) {
const hasher = await createSHA1()


const hasherStream = new WritableStream<Uint8Array>({
start: () => {
hasher.init()
// you can set UI state here also
},
write: chunk => {
hasher.update(chunk)
// you can set UI state here also
},
close: () => {
// you can set UI state here also
},
})


await file.stream().pipeTo(hasherStream)


return hasher.digest('hex')
}