C'è un modo per calcolare l'hash MD5 di un file prima del caricamento sul server utilizzando Javascript?
C'è un modo per calcolare l'hash MD5 di un file prima del caricamento sul server utilizzando Javascript?
Risposte:
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:
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.
.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.
è 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.
readAsBinaryString()
non è stato standardizzato e non è supportato da Internet Explorer. Non l'ho testato in Edge, ma nemmeno IE11 lo supporta.
readAsBinaryString()
: caniuse.com/#feat=filereader - Microsoft Edge lo supporta.
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.
CryptoJS.lib.WordArray.create(arrayBuffer);
spark-md5
eQ
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>
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));
}
}
reader
variabile sarà l'ultimo file quando verranno eseguite le funzioni di onload.
CryptoJS.lib.WordArray.create(arrayBuffer);
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.
Per ottenere l'hash dei file, ci sono molte opzioni. Normalmente il problema è che è molto lento ottenere l'hash di file di grandi dimensioni.
Ho creato una piccola libreria che ottiene l'hash dei file, con i 64kb dell'inizio del file e i 64kb della fine.
Esempio dal vivo: http://marcu87.github.com/hashme/ e libreria: https://github.com/marcu87/hashme
Ci sono un paio di script disponibili su Internet per creare un hash MD5.
Quello di webtoolkit è buono, http://www.webtoolkit.info/javascript-md5.html
Tuttavia, non credo che avrà accesso al filesystem locale poiché tale accesso è limitato.
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();
});
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>
Con l'attuale HTML5 dovrebbe essere possibile calcolare l'hash md5 di un file binario, ma penso che il passaggio prima sarebbe quello di convertire i dati banali BlobBuilder in una stringa, sto cercando di fare questo passaggio: ma non ho avuto successo.
Ecco il codice che ho provato: Conversione di un BlobBuilder in stringa, in JavaScript HTML5
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.