Cómo calcular el hash md5 de un archivo usando javascript

104

¿Hay alguna forma de calcular el hash MD5 de un archivo antes de subirlo al servidor usando Javascript?

LuRsT
fuente
1
Muy relacionado: [¿Cómo generar suma de comprobación y convertir a 64 bits en Javascript para archivos muy grandes sin RAM desbordada? ] ( stackoverflow.com/q/51987434/514235 )
iammilind

Respuestas:

92

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:

Paul Dixon
fuente
30
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
4
@Tomalak También es obligatorio hacerlo en el cliente si solo quieres subirlo si es diferente al que ya tienes.
Juan
2
@John Bueno, mi declaración no descarta esto. Las comprobaciones del lado del cliente son estrictamente para la conveniencia del usuario (y por lo tanto más o menos opcionales, dependiendo de lo conveniente que desee que sea). Las comprobaciones del lado del servidor, por otro lado, son obligatorias.
Tomalak
¿La función md5 en pajhome.org.uk/crypt/md5 no admite binario como entrada? Creo que es necesario calcular el flujo binario de una imagen cargada en el navegador. Gracias.
jiajianrong
Si puede, agregue un código de ejemplo a su respuesta. Sería de gran ayuda.
cbdeveloper
30

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.

https://github.com/satazor/SparkMD5

satazor
fuente
@Biswa aquí está mi implementación. gist.github.com/marlocorridor/3e6484ae5a646bd7c625
marlo
1
¡Oye, esto funciona muy bien! Probé CryptoJS y nunca pude obtener un MD5 preciso por alguna razón, ¡esto funciona como un encanto! ¿Algún plan para sha256? @satazor
cameck
@cameck, la biblioteca es buena. Sin embargo, lo probé hoy y parece que hay un problema con el .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.
iammilind
¡Gracias por la biblioteca! Reúna
Qortex
27

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.

Benny Neugebauer
fuente
2
El problema es que readAsBinaryString()no se ha estandarizado y no es compatible con Internet Explorer. No lo probé en Edge, pero incluso IE11 no lo admite.
StanE
@ user25163 Internet Explorer (y Opera Mini) parecen ser los únicos navegadores modernos que no son compatibles readAsBinaryString(): caniuse.com/#feat=filereader - Microsoft Edge lo admite.
Benny Neugebauer
¡Gracias por la información sobre MS Edge! Trabajo para una compañia. Y ya sabe, que los clientes suelen utilizar software antiguo y lo difícil que es convencerlos de que actualicen su software. Solo quería señalar que hay que tener cuidado al usarlo, 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.
StanE
2
CryptoJS ahora admite la conversión de un ArrayBuffer a Binary / WordArray a través de:CryptoJS.lib.WordArray.create(arrayBuffer);
Warren Parad
@WarrenParad ¿Y cómo se modificaría el código anterior para que funcione con ArrayBuffer? Ahh, lo encontré aquí: stackoverflow.com/questions/28437181/…
TheStoryCoder
9

HTML5 + spark-md5yQ

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>

Jossef Harush
fuente
8

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));
     }
 }
Aleksandar Totic
fuente
Webtoolkit MD5 señalado por bendewey funcionó mucho mejor, 16 segundos para un archivo de varios MB: webtoolkit.info/javascript-md5.html
Aleksandar Totic
1
Me las arreglé para que esto funcione y se está generando el mismo hash md5 (php: md5_file (...)) para archivos de texto, pero las imágenes me dan resultados diferentes. ¿Tiene algo que ver con los datos binarios o la forma en que se cargan?
Castillos
Estoy bastante seguro de que este código no funciona con varios archivos, porque onload es una devolución de llamada, la readervariable será el último archivo cuando se ejecuten las funciones de onload.
Dave
CryptoJS ahora admite la conversión de un ArrayBuffer a Binary / WordArray a través de:CryptoJS.lib.WordArray.create(arrayBuffer);
Warren Parad
4

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.

Marco
fuente
3

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

Marco Antonio
fuente
1

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();
  });
Zico Deng
fuente
1

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>

Biró Dani
fuente
0

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

user820955
fuente
-1

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.

kbosak
fuente