Czy istnieje sposób na obliczenie skrótu MD5 pliku przed przesłaniem na serwer przy użyciu JavaScript?
Czy istnieje sposób na obliczenie skrótu MD5 pliku przed przesłaniem na serwer przy użyciu JavaScript?
Odpowiedzi:
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:
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.
.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.
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.
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.
readAsBinaryString()
: caniuse.com/#feat=filereader - obsługuje go Microsoft Edge.
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 readAsArrayBuffer
obsługiwana przez IE. I może obsługiwać duże pliki, czytając je w kawałkach.
CryptoJS.lib.WordArray.create(arrayBuffer);
spark-md5
iQ
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>
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));
}
}
reader
zmienna będzie ostatnim plikiem do czasu uruchomienia funkcji onload.
CryptoJS.lib.WordArray.create(arrayBuffer);
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.
Aby uzyskać hash plików, istnieje wiele opcji. Zwykle problem polega na tym, że bardzo wolno jest uzyskać skrót dużych plików.
Stworzyłem małą bibliotekę, która pobiera hash plików, z 64kb na początku pliku i 64kb na końcu.
Przykład na żywo: http://marcu87.github.com/hashme/ i biblioteka: https://github.com/marcu87/hashme
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.
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();
});
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>
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
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.