-
Finally had some time to create the modules:
I will add proper READMEs soon.
Usage:
const HMAC = require('https://raw.githubusercontent.com/coajaxial/espruino-hmac/master/hmac.js'); var hmac = HMAC.SHA1(E.toArrayBuffer('my secret key')); console.log(hmac.digest(E.toArrayBuffer('my message'))); // FixedSHA1 is faster than SHA1, but digested message must always be the same fixed length. var hmacf = HMAC.FixedSHA1(E.toArrayBuffer('my secret key'), 2); // 2 bytes console.log(hmacf.digest(E.toArrayBuffer('bb'))); console.log(hmacf.digest(E.toArrayBuffer('xx')));
const TOTP = require('https://raw.githubusercontent.com/coajaxial/espruino-totp/master/totp.js'); const totp = TOTP.create('JBSWY3DPEHPK3PXP'); console.log(totp.generate(getTime(), 6, 30));
-
Example:
class A { constructor() { } } class B extends A { constructor() { super(); } } class C extends B { constructor() { super(); } } new C();
Result:
Uncaught Error: Too much recursion - the stack is about to overflow at line 7 col 1 super(); ^ in function called from line 7 col 7 super(); ^ [.......] in function called from line 7 col 7 super(); ^ Execution Interrupted
-
-
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.
-
Finally got it working using built in SHA1 of the crypto module and a custom implementation of HMAC. Performance: ~16ms per token
var crypto = require('crypto'); var TOTP = function(secret) { var base32decode = function(encoded) { encoded = encoded.toUpperCase(); var result = [], buffer = 0, next = 0, bitsLeft = 0, charValue = 0, i = 0; for ( ; i < encoded.length; i++ ) { charValue = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'.indexOf(encoded.charAt(i)); buffer <<= 5; buffer |= charValue & 31; bitsLeft += 5; if (bitsLeft >= 8) { result[next++] = (buffer >> (bitsLeft - 8)) & 0xFF; bitsLeft -= 8; } } return result; }; var intToByteArray = (x) => [0, 0, 0, 0, x >> 24 & 0xFF, x >> 16 & 0xFF, x >> 8 & 0xFF, x & 0xFF]; var keyBytes = base32decode(secret); if ( keyBytes.length > 64 ) { keyBytes = [].slice.call(crypto.SHA1(keyBytes)); } while ( keyBytes.length < 64 ) { keyBytes.push(0x00); } var o_key_pad = keyBytes.map((byte) => byte ^ 0x5c); var i_key_pad = keyBytes.map((byte) => byte ^ 0x36); var digest = function(messageBytes) { return crypto.SHA1(o_key_pad.concat(crypto.SHA1(i_key_pad.concat(messageBytes)))); }; this.generate = function(tokenPeriod, digits) { tokenPeriod = (tokenPeriod == undefined ? 30 : tokenPeriod) * 1000; digits = digits == undefined ? 6 : digits; var epoch = Math.floor(new Date().getTime() / tokenPeriod); var hmacBytes = digest(intToByteArray(epoch)); var offset = hmacBytes[hmacBytes.length - 1] & 0x0F; var dt = ((hmacBytes[offset] & 0x7f) << 24) | (hmacBytes[offset + 1] << 16) | (hmacBytes[offset + 2] << 8) | hmacBytes[offset + 3]; return dt % Math.pow(10, digits); }; }; var totp = new TOTP("NBQW44ZAO52XE43UEBUXG5BANZSSAZTVMNVWS3THEBTWM3RAMRZWY23HNIQGY23TMRVGO3DLNIQHGZDMNNTWUZDTNJTWY2ZAONSGWZDL"); setWatch(function() { var start = new Date().getTime(); var otp = totp.generate(); var end = new Date().getTime(); console.log(otp); console.log('Took: ' + (end - start) + 'ms'); }, BTN, {debounce:100,repeat:true, edge:"rising"});
yes, of course!