有没有办法在使用 Javascript 上传到服务器之前计算文件的 MD5 哈希?
12 回答
虽然有MD5 算法的JS 实现,但较旧的浏览器通常无法从本地文件系统读取文件。
我是在 2009 年写的。那么新的浏览器呢?
使用支持FileAPI的浏览器,您可以读取文件的内容<input>
- 用户必须通过元素或拖放来选择它。截至 2013 年 1 月,以下是主要浏览器的堆叠方式:
- FF 3.6 支持FileReader,FF4 支持更多基于文件的功能
- Chrome 从 7.0.517.41版本开始支持 FileAPI
- Internet Explorer 10 具有部分FileAPI 支持
- Opera 11.10部分支持 FileAPI
- Safari - 我找不到一个好的官方来源,但这个网站建议部分支持 5.1,完全支持 6.0。另一篇文章报告了与旧版 Safari 的一些不一致之处
如何?
我制作了一个实现增量 md5 的库,以便有效地散列大文件。基本上,您以块的形式读取文件(以保持低内存)并逐步散列它。您在自述文件中获得了基本用法和示例。
请注意,您需要 HTML5 FileAPI,因此请务必检查它。测试文件夹中有一个完整的示例。
使用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;
}
可以在此处找到有关拖放功能的更多信息:File API 和 FileReader
我在 Google Chrome 版本 32 中测试了该示例。
HTML5 +spark-md5
和Q
假设您使用现代浏览器(支持 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>
以下片段显示了一个示例,它可以在读取和散列文件时归档 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>
您需要使用 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));
}
}
除了无法在 JS 中访问文件系统之外,我根本不会信任客户端生成的校验和。因此在任何情况下都必须在服务器上生成校验和。– Tomalak 2009 年 4 月 20 日 14:05
在大多数情况下这是无用的。您希望在客户端计算 MD5,以便您可以将其与在服务器端重新计算的代码进行比较,如果它们不同,则得出上传错误的结论。我需要在处理大型科学数据文件的应用程序中执行此操作,其中接收未损坏的文件是关键。我的案例很简单,因为用户已经通过他们的数据分析工具计算了 MD5,所以我只需要通过文本字段向他们询问。
要获取文件的哈希,有很多选项。通常的问题是获取大文件的哈希真的很慢。
我创建了一个小库来获取文件的哈希,文件开头为 64kb,结尾为 64kb。
实时示例: http: //marcu87.github.com/hashme/和库:https ://github.com/marcu87/hashme
互联网上有几个脚本可以创建 MD5 哈希。
来自 webtoolkit 的那个很好,http://www.webtoolkit.info/javascript-md5.html
虽然,我不相信它会访问本地文件系统,因为访问是有限的。
希望您现在已经找到了一个好的解决方案。如果没有,下面的解决方案是基于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();
});
使用当前的 HTML5 应该可以计算二进制文件的 md5 哈希,但我认为之前的步骤是将二进制数据 BlobBuilder 转换为字符串,我正在尝试执行此步骤:但没有成功。
这是我尝试过的代码:Converting a BlobBuilder to string, in HTML5 Javascript
我不相信 javascript 中有一种方法可以访问文件上传的内容。因此,您无法查看文件内容来生成 MD5 和。
但是,您可以将文件发送到服务器,然后服务器可以发回 MD5 和或发回文件内容.. 但这是很多工作,可能不值得为您的目的。