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?

LuRsT
źródło
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:

Paul Dixon
źródło
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

satazor
źródło
@Biswa to moja realizacja. gist.github.com/marlocorridor/3e6484ae5a646bd7c625
marlo
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.

Benny Neugebauer
źródło
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>

Jossef Harush
źródło
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));
     }
 }
Aleksandar Totic
źródło
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.

Marco
źródło
2

W Internecie jest kilka skryptów do tworzenia skrótu MD5.

Ten z webtoolkit jest dobry, http://www.webtoolkit.info/javascript-md5.html

Chociaż nie wierzę, że będzie miał dostęp do lokalnego systemu plików, ponieważ dostęp ten jest ograniczony.

Bendewey
źródło
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();
  });
Zico Deng
źródło
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>

Biró Dani
źródło
0

Przy obecnym HTML5 powinno być możliwe obliczenie skrótu md5 pliku binarnego, ale myślę, że krokiem poprzedzającym to byłoby przekonwertowanie danych banarnych BlobBuilder na ciąg znaków, próbuję wykonać ten krok: ale nie udało mi się.

Oto kod, który próbowałem: Konwertowanie BlobBuildera na ciąg znaków w JavaScript HTML5

user820955
źródło
-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.

kbosak
źródło