Search code examples
javascriptangularfilereadersha1cryptojs

How to make a SHA1 from a file in Angular using a filereader?


I have an input element on my html page, with which I can select 1/multiple file(s).
Once I have chosen my file(s), I would like to read the content of each file using a FileReader to make a SHA1 from it.
Once I have the SHA1 value, I would like to save it somewhere.

The problem is that I receive the SHA1 value only after the .onload of the FileReader is finished and that happens after I try to safe it's value.

I have tried to make the function async and using an await to wait until the file is read but that didn't work. I have tried to add a Promise but that didn't work either.
I really don't know what to do to have the desired outcome. Please help.

This is my angular function that I call when I have choosen my file(s):

hashFiles(files: Array<any>){
    console.log('start hashing');
    for (const file of files) {
      const myHash = hashFile(file);
      console.log('hash: ', myHash);
      /*I would like to save myHash here*/
    }
    console.log('done hashing');
}

This is my javascript function that's called from angular that will read the file with a FileReader and then make a sha1 hash from it's content

function hashFile(fileToHandle) {
  console.log('1');

  var reader = new FileReader();

  console.log('2');

  reader.onload = (function() {

    return function(e) {
      console.log('4');

      const hash = CryptoJS.SHA1(arrayBufferToWordArray(e.target.result)).toString();
      console.log('hash result in fileReader: ', hash);
      return hash;
    };
  }) (fileToHandle);
  reader.onerror = function(e) {
    console.error(e);
  };

  console.log('3');

  reader.readAsArrayBuffer(fileToHandle);

  console.log('5');
}

function arrayBufferToWordArray(ab) {
  var i8a = new Uint8Array(ab);
  var a = [];
  for (var i = 0; i < i8a.length; i += 4) {
    a.push(i8a[i] << 24 | i8a[i + 1] << 16 | i8a[i + 2] << 8 | i8a[i + 3]);
  }
  return CryptoJS.lib.WordArray.create(a, i8a.length);
}

When running this code I have the following in my console:

start hashing
1
2
3
5
hash: undefined
done hashing
4
hash result in fileReader: 327c468b64b4ca54377546f8a214d703ccbad64b

And I need it to be:

start hashing
1
2
3
hash result in fileReader: 327c468b64b4ca54377546f8a214d703ccbad64b
4
5
hash: 327c468b64b4ca54377546f8a214d703ccbad64b
done hashing

Here is a sample of my code:
https://stackblitz.com/edit/sha1-from-file


Solution

  • You need to either work with call backs or add a promise wrapper (observable would also work) to the native callbacks of the FileReader. Here is how you could do it just by adding a call back.

    Stackblitz

    app.component.ts

    hashFiles(event) {
      console.log('start hashing');
    
      const numberOfFiles = event.target.files.length;
      var fileCounter = 0;
      for (const file of event.target.files) {
        hashFile(file, (hashedFile, hash) => {
        console.log('hash: ', hash);
        fileCounter++;
        if(fileCounter === numberOfFiles)
          console.log('Done Hashing!');
        });
      }
    }
    

    script.js

    export function hashFile(fileToHandle, callback) {
      var CryptoJS = require("crypto-js");
    
      console.log('1');
    
      var reader = new FileReader();
    
      console.log('2');
    
      reader.onloadend = (function() {
        return function(e) {
          console.log('4');
          const hash = CryptoJS.SHA1(arrayBufferToWordArray(e.target.result)).toString();
          console.log('hash result in fileReader: ', hash);
          callback(fileToHandle, hash);
        };
      }) (fileToHandle);
      reader.onerror = function(e) {
        console.error(e);
      };
    
      console.log('3');
    
      reader.readAsArrayBuffer(fileToHandle);
    }