¿Hay alguna forma de calcular el hash MD5 de un archivo antes de subirlo al servidor usando Javascript?
¿Hay alguna forma de calcular el hash MD5 de un archivo antes de subirlo al servidor usando Javascript?
Respuestas:
Si bien hay implementaciones JS del algoritmo MD5, los navegadores más antiguos generalmente no pueden leer archivos del sistema de archivos local .
Lo escribí en 2009. ¿Qué pasa con los nuevos navegadores?
Con un navegador que admita FileAPI , * puede * leer el contenido de un archivo ; el usuario debe haberlo seleccionado, ya sea con un <input>
elemento o arrastrando y soltando. A partir de enero de 2013, así es como se comparan los principales navegadores:
Hice una biblioteca que implementa md5 incremental para procesar archivos grandes de manera eficiente. Básicamente, lee un archivo en trozos (para mantener baja la memoria) y lo hash de forma incremental. Tienes usos básicos y ejemplos en el archivo Léame.
Tenga en cuenta que necesita HTML5 FileAPI, así que asegúrese de verificarlo. Hay un ejemplo completo en la carpeta de prueba.
.end()
método. Si vuelve a llamar a este método, las próximas veces dará un resultado incorrecto. Porque .end()
llama .reset()
internamente. Este es un desastre de codificación y no es bueno para la escritura de bibliotecas.
Es bastante fácil calcular el hash MD5 utilizando la función MD5 de CryptoJS y la API FileReader de HTML5 . El siguiente fragmento de código muestra cómo puede leer los datos binarios y calcular el hash MD5 a partir de una imagen que se ha arrastrado a su 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);
};
Recomiendo agregar algo de CSS para ver el área de arrastrar y soltar:
#holder {
border: 10px dashed #ccc;
width: 300px;
height: 300px;
}
#holder.hover {
border: 10px dashed #333;
}
Puede encontrar más información sobre la funcionalidad de arrastrar y soltar aquí: API de archivos y lector de archivos
Probé la muestra en Google Chrome versión 32.
readAsBinaryString()
no se ha estandarizado y no es compatible con Internet Explorer. No lo probé en Edge, pero incluso IE11 no lo admite.
readAsBinaryString()
: caniuse.com/#feat=filereader - Microsoft Edge lo admite.
readAsBinaryString()
ya que no es compatible con los navegadores más antiguos. Una alternativa que encontré es SparkMD5. También utiliza la API FileReader, pero el método readAsArrayBuffer
, que es compatible con IE. Y puede manejar archivos enormes leyéndolos en trozos.
CryptoJS.lib.WordArray.create(arrayBuffer);
spark-md5
yQ
Suponiendo que está utilizando un navegador moderno (que admite la API de archivos HTML5), así es como calcula el Hash MD5 de un archivo grande (calculará el hash en fragmentos variables)
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>
Necesita usar FileAPI. Está disponible en la última versión de FF y Chrome, pero no en IE9. Coge cualquier implementación JS de md5 sugerida anteriormente. Probé esto y lo abandoné porque JS era demasiado lento (minutos en archivos de imagen grandes). Podría volver a visitarlo si alguien reescribe MD5 usando matrices escritas.
El código se vería así:
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
variable será el último archivo cuando se ejecuten las funciones de onload.
CryptoJS.lib.WordArray.create(arrayBuffer);
Aparte de la imposibilidad de obtener acceso al sistema de archivos en JS, no confiaría en absoluto en una suma de comprobación generada por el cliente. Por lo tanto, generar la suma de comprobación en el servidor es obligatorio en cualquier caso. - Tomalak 20 de abril de 2009 a las 14:05
Lo cual es inútil en la mayoría de los casos. Desea que el MD5 se calcule en el lado del cliente, de modo que pueda compararlo con el código recalculado en el lado del servidor y concluir que la carga salió mal si difieren. Necesitaba hacer eso en aplicaciones que trabajaban con grandes archivos de datos científicos, donde recibir archivos no corrompidos era clave. Mis casos eran simples, porque los usuarios ya tenían el MD5 calculado a partir de sus herramientas de análisis de datos, por lo que solo necesitaba preguntárselo con un campo de texto.
Para obtener el hash de los archivos, hay muchas opciones. Normalmente, el problema es que es muy lento obtener el hash de archivos grandes.
Creé una pequeña biblioteca que obtiene el hash de los archivos, con los 64kb del inicio del archivo y los 64kb del final.
Ejemplo en vivo: http://marcu87.github.com/hashme/ y biblioteca: https://github.com/marcu87/hashme
Hay un par de scripts en Internet para crear un MD5 Hash.
El de webtoolkit es bueno, http://www.webtoolkit.info/javascript-md5.html
Aunque, no creo que tenga acceso al sistema de archivos local ya que ese acceso es limitado.
Espero que ya haya encontrado una buena solución. De lo contrario, la siguiente solución es una implementación de promesa ES6 basada en 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();
});
El siguiente fragmento muestra un ejemplo, que puede archivar un rendimiento de 400 MB / s mientras lee y procesa el archivo.
Utiliza una biblioteca llamada hash-wasm , que se basa en WebAssembly y calcula el hash más rápido que las bibliotecas solo js. A partir de 2020, todos los navegadores modernos admiten 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>
Con HTML5 actual debería ser posible calcular el hash md5 de un archivo binario, pero creo que el paso anterior sería convertir los datos banarios BlobBuilder en una cadena, estoy tratando de hacer este paso: pero no he tenido éxito.
Aquí está el código que probé: Convertir un BlobBuilder en cadena, en HTML5 Javascript
No creo que haya una forma en JavaScript para acceder al contenido de la carga de un archivo. Por lo tanto, no puede mirar el contenido del archivo para generar una suma MD5.
Sin embargo, puede enviar el archivo al servidor, que luego puede enviar una suma MD5 o devolver el contenido del archivo ... pero eso es mucho trabajo y probablemente no valga la pena para sus propósitos.