Come calcolare l'hash md5 di un file usando javascript


104

C'è un modo per calcolare l'hash MD5 di un file prima del caricamento sul server utilizzando Javascript?


1
Fortemente correlato: [Come generare checksum e convertirli a 64 bit in Javascript per file di grandi dimensioni senza sovraccaricare la RAM? ] ( Stackoverflow.com/q/51987434/514235 )
iammilind

Risposte:


92

Sebbene esistano implementazioni JS dell'algoritmo MD5, i browser meno recenti generalmente non sono in grado di leggere i file dal filesystem locale .

L'ho scritto nel 2009. E i nuovi browser?

Con un browser che supporta FileAPI , * puoi * leggere il contenuto di un file : l'utente deve averlo selezionato, con un <input>elemento o con il drag-and-drop. A partire da gennaio 2013, ecco come si accumulano i principali browser:


30
A parte l'impossibilità di ottenere l'accesso al file system in JS, non metterei alcuna fiducia in un checksum generato dal client. Quindi la generazione del checksum sul server è obbligatoria in ogni caso.
Tomalak

4
@Tomalak È anche obbligatorio farlo sul client se vuoi caricarlo solo se è diverso da quello che hai già.
Giovanni,

2
@ John Bene, la mia dichiarazione non lo esclude. I controlli lato client sono strettamente per comodità dell'utente (e quindi più o meno facoltativi, a seconda di quanto si desidera renderlo conveniente). I controlli lato server sono invece obbligatori.
Tomalak,

La funzione md5 in pajhome.org.uk/crypt/md5 non supporta il binario come input? Penso che sia necessario calcolare il flusso binario per un'immagine caricata nel browser. Grazie.
jiajianrong

Se puoi, aggiungi un codice di esempio alla tua risposta. Sarebbe molto utile.
cbdeveloper

30

Ho creato una libreria che implementa md5 incrementale per eseguire l'hashing di file di grandi dimensioni in modo efficiente. Fondamentalmente si legge un file in blocchi (per mantenere bassa la memoria) e lo si hash in modo incrementale. Hai un utilizzo di base ed esempi nel file readme.

Tieni presente che hai bisogno di HTML5 FileAPI, quindi assicurati di controllarlo. C'è un esempio completo nella cartella del test.

https://github.com/satazor/SparkMD5


@Biswa qui è la mia implementazione. gist.github.com/marlocorridor/3e6484ae5a646bd7c625
marlo

1
Ehi, funziona alla grande! Ho provato CryptoJS e non sono mai riuscito a ottenere un MD5 accurato per qualche motivo, funziona a meraviglia! Qualche progetto per sha256? @satazor
cameck

@cameck, la biblioteca è buona. Comunque l'ho provato oggi e sembra che ci sia un problema con il .end()metodo. Se chiami di nuovo questo metodo, le volte successive darà un risultato sbagliato. Perché .end()chiama .reset()internamente. Questo è un disastro della codifica e non va bene per la scrittura in biblioteca.
iammilind

Grazie per la biblioteca! Metti insieme un codice minimo: dev.to/micmo/compute-md5-checksum-for-a-file-in-typescript-59a4
Qortex

27

è abbastanza facile calcolare l'hash MD5 utilizzando la funzione MD5 di CryptoJS e l' API FileReader HTML5 . Il seguente frammento di codice mostra come leggere i dati binari e calcolare l'hash MD5 da un'immagine che è stata trascinata nel browser:

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);
};

Consiglio di aggiungere un po 'di CSS per vedere l'area Drag & Drop:

#holder {
  border: 10px dashed #ccc;
  width: 300px;
  height: 300px;
}

#holder.hover {
  border: 10px dashed #333;
}

Ulteriori informazioni sulla funzionalità Drag & Drop sono disponibili qui: File API e FileReader

Ho testato il campione in Google Chrome versione 32.


2
Il problema è che readAsBinaryString()non è stato standardizzato e non è supportato da Internet Explorer. Non l'ho testato in Edge, ma nemmeno IE11 lo supporta.
StanE

@ user25163 Internet Explorer (e Opera Mini) sembrano essere gli unici browser moderni che non supportano readAsBinaryString(): caniuse.com/#feat=filereader - Microsoft Edge lo supporta.
Benny Neugebauer

Grazie per le informazioni su MS Edge! Lavoro per un'azienda. E sai, che i clienti usano spesso vecchi software e quanto sia difficile convincerli ad aggiornare il loro software. Volevo solo sottolineare che bisogna stare attenti nell'uso in readAsBinaryString()quanto non è supportato dai browser meno recenti. Un'alternativa che ho trovato è SparkMD5. Utilizza anche l'API FileReader ma il metodo readAsArrayBuffer, che è supportato da IE. E può gestire file enormi leggendoli a blocchi.
StanE

2
CryptoJS ora supporta la conversione da ArrayBuffer a Binary / WordArray tramite:CryptoJS.lib.WordArray.create(arrayBuffer);
Warren Parad

@WarrenParad E come verrebbe modificato il codice precedente per funzionare con ArrayBuffer? Ahh, che si trova qui: stackoverflow.com/questions/28437181/...
TheStoryCoder

9

HTML5 + spark-md5eQ

Supponendo che tu stia utilizzando un browser moderno (che supporta HTML5 File API), ecco come calcolare l' hash MD5 di un file di grandi dimensioni (calcolerà l'hash su blocchi variabili)

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>


8

Devi usare FileAPI. È disponibile negli ultimi FF e Chrome, ma non in IE9. Prendi qualsiasi implementazione JS md5 suggerita sopra. L'ho provato e l'ho abbandonato perché JS era troppo lento (minuti su file di immagini di grandi dimensioni). Potrebbe rivisitarlo se qualcuno riscrive MD5 usando array tipizzati.

Il codice sarebbe simile a questo:

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));
     }
 }

Webtoolkit MD5 indicato da bendewey ha funzionato molto meglio, 16 secondi
Aleksandar Totic

1
Sono riuscito a farlo funzionare e lo stesso hash md5 sta generando (php: md5_file (...)) per i file di testo ma le immagini mi danno risultati diversi? Ha qualcosa a che fare con i dati binari o con il modo in cui vengono caricati?
Castelli

Sono abbastanza sicuro che questo codice non funzioni con più file, perché onload è un callback, la readervariabile sarà l'ultimo file quando verranno eseguite le funzioni di onload.
Dave

CryptoJS ora supporta la conversione da ArrayBuffer a Binary / WordArray tramite:CryptoJS.lib.WordArray.create(arrayBuffer);
Warren Parad

4

A parte l'impossibilità di ottenere l'accesso al file system in JS, non metterei alcuna fiducia in un checksum generato dal client. Quindi la generazione del checksum sul server è obbligatoria in ogni caso. - Tomalak 20 aprile 2009 alle 14:05

Che è inutile nella maggior parte dei casi. Si desidera che l'MD5 venga calcolato sul lato client, in modo da poterlo confrontare con il codice ricalcolato sul lato server e concludere che il caricamento è andato storto se differiscono. Avevo bisogno di farlo in applicazioni che lavoravano con file di grandi dimensioni di dati scientifici, dove la ricezione di file non danneggiati era fondamentale. I miei casi erano semplici, perché gli utenti avevano già calcolato l'MD5 dai loro strumenti di analisi dei dati, quindi dovevo solo chiederglielo con un campo di testo.




1

spero che tu abbia trovato una buona soluzione ormai. In caso contrario, la soluzione seguente è un'implementazione della promessa ES6 basata su 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();
  });

1

Il frammento di codice seguente mostra un esempio, che può archiviare una velocità effettiva di 400 MB / s durante la lettura e l'hashing del file.

Utilizza una libreria chiamata hash-wasm , che si basa su WebAssembly e calcola l'hash più velocemente delle librerie js-only. A partire dal 2020, tutti i browser moderni supportano 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>



-1

Non credo che ci sia un modo in javascript per accedere ai contenuti di un caricamento di file. Quindi non puoi quindi guardare il contenuto del file per generare una somma MD5.

È tuttavia possibile inviare il file al server, che può quindi restituire una somma MD5 o restituire il contenuto del file .. ma è molto lavoro e probabilmente non vale la pena per i tuoi scopi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.