Jak obliczyć hash md5 pliku za pomocą javascript


104

Czy istnieje sposób na obliczenie skrótu MD5 pliku przed przesłaniem na serwer przy użyciu JavaScript?


1
Silnie powiązane: [Jak wygenerować sumę kontrolną i przekonwertować ją do formatu 64-bitowego w JavaScript dla bardzo dużych plików bez przepełnienia pamięci RAM? ] ( stackoverflow.com/q/51987434/514235 )
iammilind

Odpowiedzi:


92

Chociaż istnieją implementacje JS algorytmu MD5, starsze przeglądarki zazwyczaj nie są w stanie odczytać plików z lokalnego systemu plików .

Napisałem to w 2009 roku. A co z nowymi przeglądarkami?

Dzięki przeglądarce obsługującej FileAPI * możesz * odczytać zawartość pliku - użytkownik musi go zaznaczyć za pomocą <input>elementu lub przeciągnij i upuść. Od stycznia 2013 r. Przedstawiamy zestawienie głównych przeglądarek:


30
Poza niemożliwością uzyskania dostępu do systemu plików w JS, nie ufałbym w ogóle sumie kontrolnej wygenerowanej przez klienta. Zatem generowanie sumy kontrolnej na serwerze jest w każdym przypadku obowiązkowe.
Tomalak

4
@Tomalak Konieczne jest również zrobienie tego na kliencie, jeśli chcesz go przesłać tylko wtedy, gdy różni się od tego, co już masz.
John

2
@John Cóż, moje oświadczenie nie wyklucza tego. Kontrole po stronie klienta służą wyłącznie wygodzie użytkownika (a zatem są mniej lub bardziej opcjonalne, w zależności od tego, jak wygodnie chcesz to zrobić). Z drugiej strony testy po stronie serwera są obowiązkowe.
Tomalak

Funkcja md5 w pajhome.org.uk/crypt/md5 nie obsługuje binarnych danych wejściowych? Myślę, że konieczne jest obliczenie strumienia binarnego dla przesłanego obrazu w przeglądarce. Dziękuję Ci.
jiajianrong,

Jeśli możesz, dodaj przykładowy kod do swojej odpowiedzi. To by bardzo pomogło.
cbdeveloper

30

Stworzyłem bibliotekę, która implementuje przyrostowe md5 w celu wydajnego haszowania dużych plików. Zasadniczo czytasz plik w kawałkach (aby zachować mało pamięci) i haszujesz go przyrostowo. Masz podstawowe zastosowania i przykłady w pliku readme.

Pamiętaj, że potrzebujesz HTML5 FileAPI, więc koniecznie sprawdź to. Pełny przykład znajduje się w folderze testowym.

https://github.com/satazor/SparkMD5



1
Hej, to działa świetnie! Wypróbowałem CryptoJS iz jakiegoś powodu nigdy nie mogłem uzyskać z niego dokładnego MD5, to działa jak urok! Jakieś plany dotyczące sha256? @satazor
cameck

@cameck, biblioteka jest dobra. Jednak dzisiaj spróbowałem i wygląda na to, że jest problem z .end()metodą. Jeśli wywołasz tę metodę ponownie, następnym razem da ona zły wynik. Ponieważ .end()dzwoni .reset()wewnętrznie. To jest katastrofa kodowania i nie nadaje się do pisania w bibliotece.
iammilind

Dzięki za bibliotekę! Utwórz
Qortex

27

obliczenie skrótu MD5 przy użyciu funkcji MD5 w CryptoJS i HTML5 FileReader API jest całkiem łatwe . Poniższy fragment kodu pokazuje, w jaki sposób można odczytać dane binarne i obliczyć skrót MD5 z obrazu, który został przeciągnięty do przeglądarki:

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

Polecam dodać trochę CSS, aby zobaczyć obszar Drag & Drop:

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

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

Więcej na temat funkcji Drag & Drop można znaleźć tutaj: File API i FileReader

Próbkę przetestowałem w Google Chrome w wersji 32.


2
Problem polega na tym, że readAsBinaryString()nie zostało to ustandaryzowane i nie jest obsługiwane przez Internet Explorer. Nie testowałem tego w Edge, ale nawet IE11 go nie obsługuje.
StanE

@ user25163 Internet Explorer (i Opera Mini) wydaje się być jedynymi nowoczesnymi przeglądarkami, które nie obsługują readAsBinaryString(): caniuse.com/#feat=filereader - obsługuje go Microsoft Edge.
Benny Neugebauer

Dziękuję za informacje dotyczące MS Edge! Pracuję dla firmy. I wiesz, że klienci często używają starego oprogramowania i jak trudno jest ich przekonać do aktualizacji oprogramowania. Chciałem tylko zaznaczyć, że należy uważać, readAsBinaryString()ponieważ nie jest obsługiwany przez starsze przeglądarki. Alternatywą, którą znalazłem, jest SparkMD5. Używa także API FileReader, ale metoda readAsArrayBufferobsługiwana przez IE. I może obsługiwać duże pliki, czytając je w kawałkach.
StanE

2
CryptoJS obsługuje teraz konwersję z ArrayBuffer do Binary / WordArray przez:CryptoJS.lib.WordArray.create(arrayBuffer);
Warren Parad

@WarrenParad A w jaki sposób powyższy kod zostałby zmodyfikowany, aby działał z ArrayBuffer? Ahh, znalazłem to tutaj: stackoverflow.com/questions/28437181/ ...
TheStoryCoder

9

HTML5 + spark-md5iQ

Zakładając, że korzystasz z nowoczesnej przeglądarki (obsługującej interfejs API plików HTML5), oto jak obliczyć skrót MD5 dużego pliku (obliczy hash na zmiennych fragmentach)

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

Musisz użyć FileAPI. Jest dostępny w najnowszych wersjach FF i Chrome, ale nie w IE9. Pobierz dowolną implementację md5 JS sugerowaną powyżej. Wypróbowałem to i porzuciłem, ponieważ JS był zbyt wolny (minuty w przypadku dużych plików graficznych). Może wrócę, jeśli ktoś przepisuje MD5 przy użyciu tablic typowanych.

Kod wyglądałby mniej więcej tak:

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 wskazany przez bendeweya działał znacznie lepiej, 16s dla pliku o wielu MB: webtoolkit.info/javascript-md5.html
Aleksandar Totic

1
Udało mi się to uruchomić i generuje ten sam hash md5 (php: md5_file (...)) dla plików tekstowych, ale obrazy dają mi inne wyniki? Czy ma to coś wspólnego z danymi binarnymi lub sposobem ich przesyłania?
Zamki

Jestem prawie pewien, że ten kod nie działa z wieloma plikami, ponieważ onload jest wywołaniem zwrotnym, readerzmienna będzie ostatnim plikiem do czasu uruchomienia funkcji onload.
Dave

CryptoJS obsługuje teraz konwersję z ArrayBuffer do Binary / WordArray przez:CryptoJS.lib.WordArray.create(arrayBuffer);
Warren Parad

4

Poza niemożliwością uzyskania dostępu do systemu plików w JS, nie ufałbym w ogóle sumie kontrolnej wygenerowanej przez klienta. Zatem generowanie sumy kontrolnej na serwerze jest w każdym przypadku obowiązkowe. - Tomalak 20 kwietnia 2009 o 14:05

Co jest w większości przypadków bezużyteczne. Chcesz, aby MD5 był obliczany po stronie klienta, abyś mógł porównać go z kodem ponownie obliczonym po stronie serwera i stwierdzić, że przesyłanie poszło nie tak, jeśli się różnią. Musiałem to zrobić w aplikacjach pracujących z dużymi plikami danych naukowych, gdzie kluczem było otrzymywanie nieuszkodzonych plików. Moje przypadki były proste, ponieważ użytkownicy mieli już obliczoną MD5 na podstawie ich narzędzi do analizy danych, więc musiałem tylko poprosić ich o to za pomocą pola tekstowego.




1

mam nadzieję, że znalazłeś już dobre rozwiązanie. Jeśli nie, poniższe rozwiązanie jest obiecującą implementacją ES6 opartą na 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

Poniższy fragment kodu przedstawia przykład, który może zarchiwizować przepustowość 400 MB / s podczas odczytu i haszowania pliku.

Korzysta z biblioteki o nazwie hash-wasm , która jest oparta na WebAssembly i oblicza skrót szybciej niż biblioteki obsługujące tylko js. Od 2020 roku wszystkie nowoczesne przeglądarki obsługują 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

Nie sądzę, aby w javascript był sposób na uzyskanie dostępu do zawartości przesłanego pliku. Dlatego nie możesz spojrzeć na zawartość pliku, aby wygenerować sumę MD5.

Możesz jednak wysłać plik na serwer, który następnie może odesłać sumę MD5 lub odesłać zawartość pliku ... ale to dużo pracy i prawdopodobnie nie opłaca się do twoich celów.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.