Como calcular o hash md5 de um arquivo usando javascript

104

Existe uma maneira de calcular o hash MD5 de um arquivo antes do upload para o servidor usando Javascript?

LuRsT
fonte
1
Fortemente relacionado: [Como gerar checksum e converter para 64 bits em Javascript para arquivos muito grandes sem estourar a RAM? ] ( stackoverflow.com/q/51987434/514235 )
iammilind

Respostas:

92

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:

Paul Dixon
fonte
30
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
4
@Tomalak Também é obrigatório fazer no cliente se você só quiser fazer o upload se for diferente do que você já tem.
John
2
@John Bem, minha declaração não exclui isso. As verificações do lado do cliente são estritamente para conveniência do usuário (e, portanto, mais ou menos opcionais, dependendo de quão conveniente você deseja torná-las). As verificações do lado do servidor, por outro lado, são obrigatórias.
Tomalak
A função md5 em pajhome.org.uk/crypt/md5 não suporta binário como entrada? Acho que é necessário calcular o fluxo binário para uma imagem carregada no navegador. Obrigado.
jiajianrong
Se você puder, adicione algum código de exemplo à sua resposta. Isso ajudaria muito.
cbdeveloper de
30

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.

https://github.com/satazor/SparkMD5

satazor
fonte
@Biswa aqui está minha implementação. gist.github.com/marlocorridor/3e6484ae5a646bd7c625
marlo
1
Ei, isso funciona muito bem! Eu tentei o CryptoJS e nunca consegui obter um MD5 preciso dele por algum motivo, isso funciona como um encanto! Algum plano para sha256? @satazor
cameck
@cameck, a biblioteca é boa. No entanto, tentei hoje e parece que há um problema com o .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.
iammilind
Obrigado pela biblioteca! Monte
Qortex
27

é 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.

Benny Neugebauer
fonte
2
O problema é que 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.
StanE
@ user25163 Internet Explorer (e Opera Mini) parecem ser os únicos navegadores modernos sem suporte readAsBinaryString(): caniuse.com/#feat=filereader - Microsoft Edge oferece suporte.
Benny Neugebauer
Obrigado pelas informações sobre o MS Edge! Eu trabalho para uma empresa. E você sabe, que os clientes costumam usar software antigo e como é difícil convencê-los a atualizar seu software. Eu só queria salientar que é preciso ter cuidado ao usá- 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.
StanE
2
CryptoJS agora suporta a conversão de um ArrayBuffer para Binary / WordArray via:CryptoJS.lib.WordArray.create(arrayBuffer);
Warren Parad
@WarrenParad E como o código acima seria modificado para funcionar com ArrayBuffer? Ahh, encontrei aqui: stackoverflow.com/questions/28437181/…
TheStoryCoder
9

HTML5 + spark-md5eQ

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>

Jossef Harush
fonte
8

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));
     }
 }
Aleksandar Totic
fonte
O Webtoolkit MD5 apontado por bendewey teve um desempenho muito melhor, 16s para um arquivo multi-MB: webtoolkit.info/javascript-md5.html
Aleksandar Totic
1
Consegui fazer isso funcionar e o mesmo hash md5 está gerando (php: md5_file (...)) para arquivos de texto, mas as imagens estão me dando resultados diferentes? Isso tem a ver com os dados binários ou com a forma como são enviados?
Castelos
Tenho certeza de que este código não funciona com vários arquivos, porque onload é um retorno de chamada, a readervariável será o último arquivo no momento em que as funções de onload forem executadas.
Dave
CryptoJS agora suporta a conversão de um ArrayBuffer para Binary / WordArray via:CryptoJS.lib.WordArray.create(arrayBuffer);
Warren Parad
4

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.

Marco
fonte
3

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

Marco Antonio
fonte
2

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.

bendewey
fonte
1

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

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>

Biró Dani
fonte
0

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

user820955
fonte
-1

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.

kbosak
fonte