Existe uma maneira de calcular o hash MD5 de um arquivo antes do upload para o servidor usando Javascript?
Existe uma maneira de calcular o hash MD5 de um arquivo antes do upload para o servidor usando Javascript?
Respostas:
Embora existam implementações JS do algoritmo MD5, os navegadores mais antigos geralmente são incapazes de ler arquivos do sistema de arquivos local .
Eu escrevi isso em 2009. E quanto aos novos navegadores?
Com um navegador que suporta FileAPI , você * pode * ler o conteúdo de um arquivo - o usuário deve selecioná-lo, seja com um <input>
elemento ou arrastar e soltar. Desde janeiro de 2013, veja como os principais navegadores se comparam:
Eu fiz uma biblioteca que implementa md5 incremental para fazer o hash de arquivos grandes com eficiência. Basicamente, você lê um arquivo em partes (para manter a memória baixa) e faz o hash incrementalmente. Você tem uso básico e exemplos no leiame.
Esteja ciente de que você precisa de HTML5 FileAPI, portanto, certifique-se de verificá-lo. Há um exemplo completo na pasta de teste.
.end()
método. Se você chamar esse método novamente, ele dará um resultado errado nas próximas vezes. Porque .end()
liga .reset()
internamente. Este é um desastre de codificação e não é bom para escrever em bibliotecas.
é muito fácil calcular o hash MD5 usando a função MD5 do CryptoJS e a API FileReader HTML5 . O seguinte snippet de código mostra como você pode ler os dados binários e calcular o hash MD5 de uma imagem que foi arrastada para o seu navegador:
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);
};
Eu recomendo adicionar algum CSS para ver a área Arrastar e Soltar:
#holder {
border: 10px dashed #ccc;
width: 300px;
height: 300px;
}
#holder.hover {
border: 10px dashed #333;
}
Mais sobre a funcionalidade Arrastar e Soltar pode ser encontrado aqui: API de Arquivo e Leitor de Arquivos
Testei a amostra no Google Chrome versão 32.
readAsBinaryString()
isso não foi padronizado e não é compatível com o Internet Explorer. Não testei no Edge, mas mesmo o IE11 não oferece suporte.
readAsBinaryString()
: caniuse.com/#feat=filereader - Microsoft Edge oferece suporte.
readAsBinaryString()
lo, pois não é compatível com navegadores mais antigos. Uma alternativa que encontrei é SparkMD5. Ele também usa a API FileReader, mas o método readAsArrayBuffer
, que é compatível com o IE. E pode lidar com arquivos enormes, lendo-os em partes.
CryptoJS.lib.WordArray.create(arrayBuffer);
spark-md5
eQ
Supondo que você esteja usando um navegador moderno (compatível com API de arquivo HTML5), veja como calcular o Hash MD5 de um arquivo grande (ele calculará o hash em pedaços variáveis)
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>
Você precisa usar o FileAPI. Ele está disponível no FF e Chrome mais recente, mas não no IE9. Pegue qualquer implementação JS md5 sugerida acima. Eu tentei isso e abandonei porque JS era muito lento (minutos em arquivos de imagem grandes). Pode ser revisitado se alguém reescrever MD5 usando matrizes digitadas.
O código seria mais ou menos assim:
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));
}
}
reader
variável será o último arquivo no momento em que as funções de onload forem executadas.
CryptoJS.lib.WordArray.create(arrayBuffer);
Além da impossibilidade de obter acesso ao sistema de arquivos em JS, eu não confiaria em uma soma de verificação gerada pelo cliente. Portanto, a geração da soma de verificação no servidor é obrigatória em qualquer caso. - Tomalak de 20 de abril de 2009 às 14:05
O que é inútil na maioria dos casos. Você deseja que o MD5 seja calculado no lado do cliente, para que possa compará-lo com o código recomputado no lado do servidor e concluir que o upload deu errado se eles forem diferentes. Precisei fazer isso em aplicativos que trabalham com grandes arquivos de dados científicos, em que o recebimento de arquivos não corrompidos era fundamental. Meus casos eram simples, porque os usuários já tinham o MD5 computado a partir de suas ferramentas de análise de dados, então eu só precisava perguntar a eles com um campo de texto.
Para obter o hash de arquivos, existem várias opções. Normalmente, o problema é que é muito lento obter o hash de arquivos grandes.
Criei uma pequena biblioteca que pega o hash dos arquivos, com os 64kb do início do arquivo e os 64kb do final dele.
Exemplo ao vivo: http://marcu87.github.com/hashme/ e biblioteca: https://github.com/marcu87/hashme
Existem alguns scripts na Internet para criar um Hash MD5.
O do webtoolkit é bom, http://www.webtoolkit.info/javascript-md5.html
No entanto, não acredito que ele terá acesso ao sistema de arquivos local, pois esse acesso é limitado.
espero que você tenha encontrado uma boa solução até agora. Caso contrário, a solução abaixo é uma implementação de promessa ES6 baseada em js-spark-md5
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();
});
O fragmento a seguir mostra um exemplo, que pode arquivar uma taxa de transferência de 400 MB / s durante a leitura e hash do arquivo.
Ele está usando uma biblioteca chamada hash-wasm , que é baseada no WebAssembly e calcula o hash mais rápido do que as bibliotecas somente js. A partir de 2020, todos os navegadores modernos suportam 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>
Com o HTML5 atual, deve ser possível calcular o hash md5 de um arquivo binário, mas acho que a etapa anterior seria converter os dados banários do BlobBuilder em uma String, estou tentando fazer esta etapa: mas não tive sucesso.
Aqui está o código que experimentei: Converter um BlobBuilder em string, em JavaScript HTML5
Não acredito que haja uma maneira em javascript de acessar o conteúdo de um upload de arquivo. Portanto, você não pode olhar o conteúdo do arquivo para gerar uma soma MD5.
No entanto, você pode enviar o arquivo para o servidor, que pode então enviar uma quantia MD5 de volta ou enviar o conteúdo do arquivo de volta ... mas isso é muito trabalhoso e provavelmente não vale a pena para seus propósitos.