UPDATE

See UTF-8, TypedArrays, Base64, Unicode, and You.

Original

WARNING: This article is useless. See the one above.

var te = new TextEncoder();
te.encode("𝄢hello world𝄢");

Update: Most Bugs Fixed. Handles binary and most UTF-8 strings.

These tests pass:

checksha1sum("hello world\n", "22596363b3de40b06f981fb85d82312e8c0ed511"); // PASS
checksha1sum("hello world", "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"); // PASS
checksha1sum("☃hello world☃", "417d0c8d58819531d284081f3b759c5c8e80e67d"); // PASS
checksha1sum("hello ☢ world", "9833aab8f84706aec2092927d9a5a5dd9e2f19a5"); // PASS

However, these tests unicode characters past 0xFFFF do not:

checksha1sum("hello 𝄢 world", "cfa43fa50f1ac433cda3ec702ba3c4434bf06181"); // FAIL
checksha1sum("𝄢hello world𝄢", "ef763063543b0284ce9b93c784d8f130aea46659"); // FAIL

I need to take a closer look at https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_.22Unicode_Problem.22 and I may find a solution.

The Web Crypto API handles binary decently well, but it only works on ArrayBuffers. It can't handle strings and there are no native tools in Browsers to convert from strings to buffers.

Github's editor can't properly handle those strings either, so I don't feel too bad. But Chrome's inspector can, so there's hope.

Hashing with the Web Crypto API

Web Crypto does support sha1 as SHA-1 and sha256 as SHA-256.

The Good News is that it works in Chrome, Firefox, and even MSIE11+

The Bad News is that it doesn't support md5 (or any of the md* digests), at all.

How to do an md5sum with the Web Crypto (API?

Sorry, you can't. Web Crypto doesn't support md5sum because it's "broken". Ugh!

How to do a sh1sum / sha256sum with window.crypto?

Here you go!

;(function () {
  'use strict';

  window.sha1sum("hello world").then(function (hex) {
    console.log('digest', hex);
    // 2aae6c35c94fcfb415dbe95f408b9ce91ee846ed
  })
}());

PSYCH! You have to build it yourself!

I've got it done for you hear below. It's not as simple as in node.js, that's for sure!

Implementing sha1sum(str);

;(function (exports) {
  'use strict';

  // Chrome, Firefox, and even MSIE11+ all support crypto
  var crypto = window.crypto || window.msCrypto
    , algos
    ;

  // convenience mappings for common digest algorithms
  algos = {
    'sha1': 'SHA-1'
  , 'sha256': 'SHA-256'
  , 'sha512': 'SHA-512'
  };

  // The function to generate a sha1sum is the same as generating any digest
  // but here's a shortcut function anyway
  function sha1sum(str) {
    return hashsum('sha1', str);
  }

  // a more general convenience function
  function hashsum(hash, str) {
      // you have to convert from string to array buffer
    var ab
      // you have to represent the algorithm as an object
      , algo = { name: algos[hash] }
      ;

    if ('string' === typeof str) {
      ab = str2ab(str);
    } else {
      ab = str;
    }

    // All crypto digest methods return a promise
    return crypto.subtle.digest(algo, ab).then(function (digest) {
      // you have to convert the ArrayBuffer to a DataView and then to a hex String
      return ab2hex(digest);
    }).catch(function (e) {
      // if you specify an unsupported digest algorithm or non-ArrayBuffer, you'll get an error
      console.error('sha1sum ERROR');
      console.error(e);
      throw e;
    });
  }

  // convert from arraybuffer to hex
  function ab2hex(ab) {
    var dv = new DataView(ab)
      , i
      , len
      , hex = ''
      , c
      ;

    for (i = 0, len = dv.byteLength; i < len; i += 1) {
      c = dv.getUint8(i).toString(16);
      if (c.length < 2) {
        c = '0' + c;
      }
      hex += c;
    }

    return hex;
  }

  // convert from string to arraybuffer
  function str2ab(stringToEncode, insertBOM) {
      stringToEncode = stringToEncode.replace(/\r\n/g,"\n");
      var utftext = [];
      if( insertBOM == true )  {
          utftext[0]=  0xef;
          utftext[1]=  0xbb;
          utftext[2]=  0xbf;
      }

      for (var n = 0; n < stringToEncode.length; n++) {

          var c = stringToEncode.charCodeAt(n);

          if (c < 128) {
              utftext[utftext.length]= c;
          }
          else if((c > 127) && (c < 2048)) {
              utftext[utftext.length]= (c >> 6) | 192;
              utftext[utftext.length]= (c & 63) | 128;
          }
          else {
              utftext[utftext.length]= (c >> 12) | 224;
              utftext[utftext.length]= ((c >> 6) & 63) | 128;
              utftext[utftext.length]= (c & 63) | 128;
          }

      }
      return new Uint8Array(utftext).buffer;
  }

  // convert from string to arraybuffer
  function str2ab_ascii(str) {
    var buf = new ArrayBuffer(str.length)
      , bufView = new Uint8Array(buf)
      , i
      , strLen
      ;

    // can't use .forEach on DOM objects
    for (i = 0, strLen = str.length; i < strLen; i += 1) {
      bufView[i] = str.charCodeAt(i);
    }

    return buf;
  }

  exports.hashsum = hashsum;
  exports.sha1sum = sha1sum;
}(window));

It's like they invited the Java team to come in and make sure it was nearly impossible for the average guy to figure out before making the spec, y'know?

Reference hashsums

echo "hello world" | shasum
22596363b3de40b06f981fb85d82312e8c0ed511  -

echo -n "hello world" | shasum
2aae6c35c94fcfb415dbe95f408b9ce91ee846ed  -

echo -n "☃hello world☃" | shasum
417d0c8d58819531d284081f3b759c5c8e80e67d  -

echo -n "hello ☢ world" | shasum
9833aab8f84706aec2092927d9a5a5dd9e2f19a5  -

echo -n "hello 𝄢 world" | shasum
cfa43fa50f1ac433cda3ec702ba3c4434bf06181  -

echo -n "𝄢hello world𝄢" | shasum
ef763063543b0284ce9b93c784d8f130aea46659  -
function checksha1sum(input, expected) {
  return sha1sum(input).then(function (actual) {
    if (actual !== expected) {
      console.error('input:', input);
      console.error('expected:', expected);
      console.error('actual:', actual);
      throw new Error("sha1sums did not match :-(");
    } else {
      console.log('PASS');
    }
  });
}

checksha1sum("hello world\n", "22596363b3de40b06f981fb85d82312e8c0ed511");
checksha1sum("hello world", "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed");
checksha1sum("☃hello world☃", "417d0c8d58819531d284081f3b759c5c8e80e67d");
checksha1sum("hello ☢ world", "9833aab8f84706aec2092927d9a5a5dd9e2f19a5");
checksha1sum("hello 𝄢 world", "cfa43fa50f1ac433cda3ec702ba3c4434bf06181");
checksha1sum("𝄢hello world𝄢", "ef763063543b0284ce9b93c784d8f130aea46659");

Resources


By AJ ONeal

If you loved this and want more like it, sign up!


Did I make your day?
Buy me a coffeeBuy me a coffee  

(you can learn about the bigger picture I'm working towards on my patreon page )