-
Here is a generic implementation of HMAC. I used the pseudo code from Wikipedia (https://en.wikipedia.org/wiki/HMAC) to implement this in Espruino-compatible JS.
var HMAC = function(keyBytes, hash, blockSize) { var toArray = (input) => [].slice.call(input); keyBytes = toArray(keyBytes); if ( keyBytes.length > blockSize ) { keyBytes = toArray(hash(keyBytes)); } while ( keyBytes.length < blockSize ) { keyBytes.push(0x00); } var o_key_pad = keyBytes.map((byte) => byte ^ 0x5c); var i_key_pad = keyBytes.map((byte) => byte ^ 0x36); this.digest = function(messageBytes) { return toArray(hash(o_key_pad.concat(hash(i_key_pad.concat(messageBytes))))); }; };
So, to implement a HMAC_SHA1, you can just "curry" the function:
var crypto = require('crypto'); var HMAC_SHA1 = function(keyBytes) { return new HMAC(keyBytes, crypto.SHA1, 64 /* Block size of SHA1 in bytes */); };
Usage:
var key = "some secret key"; var message = "This is my message"; var hmac = new HMAC_SHA1(ByteArray.fromUTF8String(key)); console.log(ByteArray.toHexString(hmac.digest(ByteArray.fromUTF8String(message))));
THis will print ba38a78074db157c10feb8ed1845975ecdf2b5d9. You can verify this here: https://www.freeformatter.com/hmac-generator.html
I used some helper functions to convert byte arrays to string and vice-versa:
var ByteArray = { fromUTF8String: function(str) { var out = [], p = 0; for (var i = 0; i < str.length; i++) { var c = str.charCodeAt(i); if (c < 128) { out[p++] = c; } else if (c < 2048) { out[p++] = (c >> 6) | 192; out[p++] = (c & 63) | 128; } else if ( ((c & 0xFC00) == 0xD800) && (i + 1) < str.length && ((str.charCodeAt(i + 1) & 0xFC00) == 0xDC00)) { // Surrogate Pair c = 0x10000 + ((c & 0x03FF) << 10) + (str.charCodeAt(++i) & 0x03FF); out[p++] = (c >> 18) | 240; out[p++] = ((c >> 12) & 63) | 128; out[p++] = ((c >> 6) & 63) | 128; out[p++] = (c & 63) | 128; } else { out[p++] = (c >> 12) | 224; out[p++] = ((c >> 6) & 63) | 128; out[p++] = (c & 63) | 128; } } return out; }, toUTF8String: function(bytes) { var out = [], pos = 0, c = 0; while (pos < bytes.length) { var c1 = bytes[pos++]; if (c1 < 128) { out[c++] = String.fromCharCode(c1); } else if (c1 > 191 && c1 < 224) { var c2 = bytes[pos++]; out[c++] = String.fromCharCode((c1 & 31) << 6 | c2 & 63); } else if (c1 > 239 && c1 < 365) { // Surrogate Pair var c2 = bytes[pos++]; var c3 = bytes[pos++]; var c4 = bytes[pos++]; var u = ((c1 & 7) << 18 | (c2 & 63) << 12 | (c3 & 63) << 6 | c4 & 63) - 0x10000; out[c++] = String.fromCharCode(0xD800 + (u >> 10)); out[c++] = String.fromCharCode(0xDC00 + (u & 1023)); } else { var c2 = bytes[pos++]; var c3 = bytes[pos++]; out[c++] = String.fromCharCode((c1 & 15) << 12 | (c2 & 63) << 6 | c3 & 63); } } return out.join(''); }, toHexString: function(bytes) { var output = ''; for ( var i = 0; i < bytes.length; i++ ) { var hexByte = bytes[i].toString(16); output += hexByte.length > 1 ? hexByte : '0' + hexByte; } return output; } };
I will try to create a module, I will post further progress here.
Nice - thanks for posting up! So is that HMAC pretty much standard such that it can interact with other devices? Would you be willing to have that put into Espruino as a module? It seems like it'd be pretty useful to a lot of people