Comment calculer le hachage MD5 d'un fichier en utilisant javascript


104

Existe-t-il un moyen de calculer le hachage MD5 d'un fichier avant le téléchargement sur le serveur en utilisant Javascript?


1
Fortement lié: [Comment générer une somme de contrôle et convertir en 64 bits en Javascript pour de très gros fichiers sans débordement de RAM? ] ( stackoverflow.com/q/51987434/514235 )
iammilind

Réponses:


92

Bien qu'il existe des implémentations JS de l'algorithme MD5, les anciens navigateurs sont généralement incapables de lire les fichiers à partir du système de fichiers local .

J'ai écrit ça en 2009. Alors qu'en est-il des nouveaux navigateurs?

Avec un navigateur qui prend en charge FileAPI , vous * pouvez * lire le contenu d'un fichier - l'utilisateur doit l'avoir sélectionné, soit avec un <input>élément , soit par glisser-déposer. Depuis janvier 2013, voici comment les principaux navigateurs s'empilent:


30
Hormis l'impossibilité d'obtenir un accès au système de fichiers dans JS, je ne mettrais aucune confiance dans une somme de contrôle générée par le client. La génération de la somme de contrôle sur le serveur est donc obligatoire dans tous les cas.
Tomalak

4
@Tomalak Il est également obligatoire de le faire sur le client si vous ne souhaitez le télécharger que s'il est différent de ce que vous avez déjà.
John

2
@John Eh bien, ma déclaration n'exclut pas cela. Les vérifications côté client sont strictement destinées à la commodité de l'utilisateur (et donc plus ou moins facultatives, selon la commodité que vous souhaitez effectuer). Les contrôles côté serveur sont en revanche obligatoires.
Tomalak

La fonction md5 dans pajhome.org.uk/crypt/md5 ne prend pas en charge le binaire comme entrée? Je pense qu'il est nécessaire de calculer le flux binaire pour une image téléchargée dans le navigateur. Je vous remercie.
jiajianrong

Si vous le pouvez, ajoutez un exemple de code à votre réponse. Cela aiderait beaucoup.
cbdeveloper

30

J'ai créé une bibliothèque qui implémente md5 incrémentiel afin de hacher efficacement de gros fichiers. En gros, vous lisez un fichier par morceaux (pour garder la mémoire faible) et le hachez de manière incrémentielle. Vous avez une utilisation de base et des exemples dans le readme.

Sachez que vous avez besoin de HTML5 FileAPI, alors assurez-vous de le vérifier. Il y a un exemple complet dans le dossier de test.

https://github.com/satazor/SparkMD5



1
Hé cela fonctionne très bien! J'ai essayé CryptoJS et je n'ai jamais pu en tirer un MD5 précis pour une raison quelconque, cela fonctionne comme un charme! Des plans pour sha256? @satazor
cameck

@cameck, la bibliothèque est bonne. Cependant, je l'ai essayé aujourd'hui et il semble qu'il y ait un problème avec la .end()méthode. Si vous appelez à nouveau cette méthode, cela donne un résultat erroné les fois suivantes. Parce que les .end()appels en .reset()interne. C'est un désastre de codage et pas bon pour l'écriture de bibliothèque.
iammilind

Merci pour la bibliothèque! Composez
Qortex

27

il est assez facile de calculer le hachage MD5 en utilisant la fonction MD5 de CryptoJS et l' API HTML5 FileReader . L'extrait de code suivant montre comment vous pouvez lire les données binaires et calculer le hachage MD5 à partir d'une image qui a été glissée dans votre navigateur:

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

Je recommande d'ajouter du CSS pour voir la zone Drag & Drop:

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

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

Pour en savoir plus sur la fonctionnalité Drag & Drop, cliquez ici: File API & FileReader

J'ai testé l'exemple dans Google Chrome version 32.


2
Le problème est que cela readAsBinaryString()n'a pas été normalisé et n'est pas pris en charge par Internet Explorer. Je ne l'ai pas testé dans Edge, mais même IE11 ne le prend pas en charge.
StanE

@ user25163 Internet Explorer (et Opera Mini) semblent être les seuls navigateurs modernes ne prenant pas en charge readAsBinaryString(): caniuse.com/#feat=filereader - Microsoft Edge le prend en charge.
Benny Neugebauer

Merci pour les informations concernant MS Edge! Je travaille pour une entreprise. Et vous savez que les clients utilisent souvent d'anciens logiciels et combien il est difficile de les convaincre de mettre à jour leur logiciel. Je voulais juste souligner qu'il faut être prudent en utilisant readAsBinaryString()car il n'est pas pris en charge par les navigateurs plus anciens. Une alternative que j'ai trouvée est SparkMD5. Il utilise également l'API FileReader mais la méthode readAsArrayBuffer, qui est prise en charge par IE. Et il peut gérer d'énormes fichiers en les lisant en morceaux.
StanE

2
CryptoJS prend désormais en charge la conversion d'un ArrayBuffer en Binary / WordArray via:CryptoJS.lib.WordArray.create(arrayBuffer);
Warren Parad

@WarrenParad Et comment le code ci-dessus serait-il ensuite modifié pour fonctionner avec ArrayBuffer? Ahh, je l'
ai

9

HTML5 + spark-md5etQ

En supposant que vous utilisez un navigateur moderne (qui prend en charge l'API de fichier HTML5), voici comment calculer le hachage MD5 d'un gros fichier (il calculera le hachage sur des blocs variables)

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

Vous devez utiliser FileAPI. Il est disponible dans les derniers FF et Chrome, mais pas dans IE9. Prenez n'importe quelle implémentation md5 JS suggérée ci-dessus. J'ai essayé et abandonné parce que JS était trop lent (minutes sur de gros fichiers image). Pourrait le revoir si quelqu'un réécrit MD5 en utilisant des tableaux typés.

Le code ressemblerait à ceci:

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 pointé par bendewey a donné de bien meilleurs résultats, 16s pour un fichier multi-Mo: webtoolkit.info/javascript-md5.html
Aleksandar Totic

1
J'ai réussi à faire fonctionner cela et le même hachage md5 génère (php: md5_file (...)) pour les fichiers texte mais les images me donnent des résultats différents? Est-ce quelque chose à voir avec les données binaires ou la façon dont elles sont téléchargées?
Châteaux

Je suis à peu près sûr que ce code ne fonctionne pas avec plusieurs fichiers, car onload est un rappel, la readervariable sera le dernier fichier au moment où les fonctions de chargement sont exécutées.
Dave

CryptoJS prend désormais en charge la conversion d'un ArrayBuffer en Binary / WordArray via:CryptoJS.lib.WordArray.create(arrayBuffer);
Warren Parad

4

Hormis l'impossibilité d'obtenir un accès au système de fichiers dans JS, je ne mettrais aucune confiance dans une somme de contrôle générée par le client. La génération de la somme de contrôle sur le serveur est donc obligatoire dans tous les cas. - Tomalak 20 avril 09 à 14:05

Ce qui est inutile dans la plupart des cas. Vous voulez que le MD5 soit calculé côté client, afin de pouvoir le comparer avec le code recalculé côté serveur et conclure que le téléchargement s'est mal passé s'ils diffèrent. J'ai eu besoin de le faire dans des applications fonctionnant avec de gros fichiers de données scientifiques, où la réception de fichiers non corrompus était la clé. Mes cas étaient simples, car les utilisateurs avaient déjà le MD5 calculé à partir de leurs outils d'analyse de données, alors j'avais juste besoin de le leur demander avec un champ de texte.




1

j'espère que vous avez trouvé une bonne solution maintenant. Sinon, la solution ci-dessous est une implémentation de promesse ES6 basée sur 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

L'extrait de code suivant montre un exemple, qui peut archiver un débit de 400 Mo / s lors de la lecture et du hachage du fichier.

Il utilise une bibliothèque appelée hash-wasm , qui est basée sur WebAssembly et calcule le hachage plus rapidement que les bibliothèques js uniquement. À partir de 2020, tous les navigateurs modernes prennent en charge 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

Je ne crois pas qu'il existe un moyen en javascript d'accéder au contenu d'un téléchargement de fichier. Vous ne pouvez donc pas regarder le contenu du fichier pour générer une somme MD5.

Vous pouvez cependant envoyer le fichier au serveur, qui peut alors renvoyer une somme MD5 ou renvoyer le contenu du fichier .. mais c'est beaucoup de travail et probablement pas utile pour vos besoins.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.