Avatar for coajaxial

coajaxial

Member since Jul 2020 • Last active Aug 2020
  • 1 conversations
  • 5 comments

Most recent activity

  • in Puck.js, Pixl.js and MDBT42
    Avatar for coajaxial

    Finally had some time to create the modules:

    HMAC
    TOTP

    I will add proper READMEs soon.

    Usage:

    const HMAC = require('https://raw.githubusercontent.c­om/coajaxial/espruino-hmac/master/hmac.j­s');
    
    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.c­om/coajaxial/espruino-totp/master/totp.j­s');
    
    const totp = TOTP.create('JBSWY3DPEHPK3PXP');
    console.log(totp.generate(getTime(), 6, 30));
    
  • in JavaScript
    Avatar for coajaxial

    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
    
  • in Puck.js, Pixl.js and MDBT42
    Avatar for coajaxial

    Is there any performance gain when using Uint8Arrays over normal arrays? The thing is, normal arrays have operations like concat that are much faster than implementing the same for Uint8Array (a LOT faster).

  • in Puck.js, Pixl.js and MDBT42
    Avatar for coajaxial

    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.d­igest(ByteArray.fromUTF8String(message))­));
    

    THis will print ba38a78074db157c10feb8ed1845975ecdf2b5d9­. You can verify this here: https://www.freeformatter.com/hmac-gener­ator.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.

  • in Puck.js, Pixl.js and MDBT42
    Avatar for coajaxial

    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'.index­Of(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("NBQW44ZAO52XE43UEBUXG5BANZSSAZTVMN­VWS3THEBTWM3RAMRZWY23HNIQGY23TMRVGO3DLNI­QHGZDMNNTWUZDTNJTWY2ZAONSGWZDL");
    
    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"});
    
Actions