Sending single sign in tokens using Keybaord HID for TOTP sign in

Posted on
Page
of 2
/ 2
Next
  • Hi I am trying to generate time based single sign on tokens on my Pixl and puckjs. these can then be sent to a PC or phone over BT via the keyboard HID.

    My research so far says that I need the crypto.js lib from modules and OTPAuth from npm as it has no dependencies

    So far I am only getting erros. I think I do not have my requires working I will post more after I get better results

  • I think I have found the issue the OTPAuth is too large when loaded with the crypto.js lib.

    var crypto = require("crypto");
    var otp = require("https://raw.githubusercontent.com/hectorm/otpauth/master/dist/otpauth.min.js");
    
    

    Gives the following error

     ____                 _
    |  __|___ ___ ___ _ _|_|___ ___
    |  __|_ -| . |  _| | | |   | . |
    |____|___|  _|_| |___|_|_|_|___|
             |_| espruino.com
     2v03 (c) 2018 G.Williams
    >Uncaught SyntaxError: Got UNFINISHED STRING expected EOF
     at line 1 col 98
    ...aster/dist/otpauth.min.js","(function(){/*\n otpauth v4.0.0 ...
                                  ^
    Execution Interrupted
    New interpreter error: LOW_MEMORY,MEMORY
    >Uncaught Error: Module https://raw.githubusercontent.com/hectorm/otpauth/master/dist/otpauth.min.js not found
     at line 3 col 81
    ...master/dist/otpauth.min.js");
                                  ^
    > 
    

    I will look for a different OTP lib

  • I am now trying Tiny-OTP which is only 4K when minified. I still get a memory error

    1. https://github.com/triestpa/Tiny-OTP
  • Now trying JS-OTP but still rungging low on memory...

    https://github.com/jiangts/JS-OTP

  • Sat 2019.08.10

    Hello @user101436, while I'm not going to be able to find a solution to the issue you have, as the crypto and OAuth topics are beyond my knowledge base, I can confirm that attempting to load just the otp file on to an MDBT42Q, produces the same error.

    >process.memory()
    ={ "free": 2476, "usage": 24, "total": 2500, "history": 7,
    

    I don't have a Puck nor a Pixl handy, but I believe the Pixl has the same memory capacity. Would you mind posting process.memory() for others to assist please.

    I double checked with Google's Closure Compiler and there don't appear to be obvious code/string related errors.

    I saved the .min.js file to disk on Windows10 and have a file size of 25K. I was able to load a 29K file onto the MDBT board, but my file has comment blocks that are stripped prior to upload.

    Are you able to pare down that file?

    ' at line 1 col 98 ...aster/dist/otpauth.min.js","'

    Do you know if this otp module can even be loaded on to an Espruino device?
    It is quite possible that the length of that particular minified string is just too long for the receive buffer on Espruino.

    '){return"undefined"!=typeof window&&window===f?'

    Around the fifth line down scares me a bit. If this is the browser window object, this file may eventually load, but possibly not execute on an Espruino device.

  • New interpreter error: LOW_MEMORY,MEMORY

    The LOW_MEMORY means the Espruino is low on memory, but it's just a warning, if you see it alone.
    MEMORY means Espruino definitely ran out of memory.

    You can try one thing: In the Web IDE settings, go to Communication, and

    • check "Modules uploaded as functions"
    • Save on Send -> set to "Direct to Flash"

    Btw https://github.com/triestpa/Tiny-OTP fits this way, but there is an error running it:

    Uncaught SyntaxError: Got '=' expected ','
     at line 1 col 69
    ...ports=class{constructor(a,b='utf8'){'base32'===b&&(a=e.decod...
                                  ^
    

    I think Espruino doesn't support default parameters.

    As Robin said, there could be couple of tiny issues preventing you from uploading code that runs fine on node or in browser...

  • Sat 2019.08.10

    'I think Espruino doesn't support default parameters'

    Bingo! @AkosLukacs you are correct. Not implemented yet. Below ES6 heading:

    http://www.espruino.com/Features

  • Thanks for the suggestions I have JS-OTP (One Time Password) loading now without the error or memory warning by editing out the ES6 default values. I cut and pasted the GIT hub code into the right hand IDE panel and uploaded it from there. I also removed any browser objects as the code was designed to work in browser.

    But I hit a new issue with HMAC. (HMAC stands for Keyed-Hashing for Message Authentication) The hash features used by JS-OTP and TinyOTP expect a .hmac method on the hash object. The built in crypto libs do not have this feature.

    I don't think I can implement HMAC so I am looking for an OTP implementation that is not using HMAC.

    I will update here when I make progress.

  • @user101436 Did you do any progress? I'm struggeling with the same issue. I would really love to secure some stuff via TOTP. You know there's the crypto module and the hmac module but there is no tutorial to use them correctly or to create TOTPs.. There is just an outdated tutorial on https://www.espruino.com/hmac because they removed the hashlib with version 2v00 (see changelog https://www.espruino.com/ChangeLog).

  • Hello,

    I've just gone down a rabbit hole trying to discover how best to do this, and have written some Python code that seems to work. I think it should be OK to translate this into Javascript.

    I found a Javascript SHA1 implementation that works in the Espruino simulator at http://webtoolkit.info/javascript_sha1.html

    I had some Python code to do TOTP tokens so had a go at rewriting it to use my own HMAC algorithm based on the Wikipedia page https://en.wikipedia.org/wiki/Hash-based_message_authentication_code

    I think the Javascript code above returns the digest as hex - it will need to be changed to return an array of bytes but I haven't looked how to do that. But once that is done, I think my python call to sha1() can just use the Javascript code.

    The "get_hotp_token" function was the original one - I forget where it came from. From what I can see my function and this function return the same results.

    I hope it should be OK to transliterate my Python code into Javascript. The 'chr' and 'ord' function should be removed - this is as the code uses Python strings, but Javascript arrays won't have this problem. Likewise, the 'b'\x00' *' part is a Python thing that would need to be rewritten for Javascript.

    I hope my code when combined with the Javascript SHA1 code gives some help anyway.

    # SHA1 block size = 512 bits, or 64 bytes
    # https://en.wikipedia.org/wiki/Hash-based_message_authentication_code
    
    def colin_hmac(key,msg):
        # https://en.wikipedia.org/wiki/Hash-based_message_authentication_code
        # https://raw.githubusercontent.com/python/cpython/master/Lib/hmac.py
        # key is 10 bytes, msg is 8 bytes
        # Pad key to be 64 bytes long
        blocksize = 64
        key = key + b'\x00' * (blocksize - len(key)) # Pad key with 0 byte values
        # Build up new strings with the key XORed with 0x36 and 0x5C
        k36=""
        k5c=""
        for c in key:
            k36 = k36+chr(ord(c) ^ 0x36)
            k5c = k5c+chr(ord(c) ^ 0x5c)
            
        hmac=sha1(k5c + sha1(k36 + msg).digest())
        return hmac.digest()
    
    def colin_hotp_token(secret, intervals_no):
        # Key is a 10 byte array
        key = base64.b32decode(secret, True)
        # Python debug : print the key as bytes
        [#print](https://forum.espruino.com/search/?q=%23print) "KEY",[ord(x) for x in key]
        [#keybytes](https://forum.espruino.com/search/?q=%23keybytes)=[0, 68, 50, 20, 199, 66, 64, 17, 12, 133]
        # msg is 8 bytes long - number with last byte = least significant
        msg = struct.pack(">Q", intervals_no)
        [#msgbytes](https://forum.espruino.com/search/?q=%23msgbytes)=[0, 0, 0, 0, 0, 0, 0, 1]
    
        colin_hm=colin_hmac(key,msg)
        binary=offset=ord(colin_hm[19]) & 0x0f
        binary=((ord(colin_hm[offset]) & 0x7f) << 24) | ((ord(colin_hm[offset+1]) & 0xff) << 16) | ((ord(colin_hm[offset+2]) & 0xff) << 8 ) | (ord(colin_hm[offset+3]) & 0xff)
        # We want the last 6 digits
        return binary % 1000000
        
    def get_hotp_token(secret, intervals_no):
        key = base64.b32decode(secret, True)
        msg = struct.pack(">Q", intervals_no)
        hm = hmac.new(key, msg, hashlib.sha1)
        h = hm.digest() # Return byte array
        o = ord(h[19]) & 0x0f
        h = (struct.unpack(">I", h[o:o+4])[0] & 0x7fffffff) % 1000000   
        return h
    
    
  • Hmm. The posting above went a bit wrong - I guess I'm posting Python code rather than Javascript.

    Here is the garbled bit, not marked as code to hopefully keep the forum happy

    def colin_hotp_token(secret, intervals_no):
    # Key is a 10 byte array
    key = base64.b32decode(secret, True)
    # Python debug : print the key as bytes
    #print "KEY",[ord(x) for x in key]

    #keybytes=[0, 68, 50, 20, 199, 66, 64, 17, 12, 133]
    # msg is 8 bytes long - number with last byte = least significant
    msg = struct.pack(">Q", intervals_no)
    #msgbytes=[0, 0, 0, 0, 0, 0, 0, 1]

  • 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"});
    
  • 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

  • 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.

  • That's awesome - thanks! Even just those few lines of HMAC would be amazingly useful.

  • 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).

  • One optimization could be using E.toArrayBuffer()

    console.log(ByteArray.toHexString(hmac.digest(E.toArrayBuffer(message))));
    
  • @coajaxial Do you have a snippet how to

    decode ba38a78074db157c10feb8ed1845975ecdf2b5d9­

    back to "This is my message" ?

  • What about using map to convert to hex string?

    function toHexString(bytes) {
           return bytes.map(function(byte) {
               return ('0' + (byte & 0xFF).toString(16)).slice(-2);
           }).join('');
    }
    
    console.log( toHexString(hmac.digest(E.toArrayBuffer(message))));
    
    
  • Is there any performance gain when using Uint8Arrays over normal arrays?

    Yes, there's quite a bit - also memory usage is miles lower :)

    However it depends how many array items you have - if it's ~32 then it probably doesn't matter - if it's more than that then Uint8Array will noticeably better.

    The thing is, normal arrays have operations like concat that are much faster than implementing the same for Uint8Array

    In that case you'd have to do something like:

    var result = new Uint8Array(a.length+b.length);
    result.set(a,0);
    result.set(b,a.length);
    

    It's slightly more long-winded but is far faster than a FOR-loop copy :)

  • Forget about my decode question

    HMAC is a MAC/keyed hash, not a cipher. It's not designed to be decrypted

  • Finally had some time to create the modules:

    HMAC
    TOTP

    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));
    
  • This looks great - thanks!

  • Just wanted to check - please can I pull these into the core set of Espruino modules in the EspruinoDocs repo?

  • e can I pull these into the core set of Espruino modules in the EspruinoDocs repo?

    yes, of course!

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Sending single sign in tokens using Keybaord HID for TOTP sign in

Posted by Avatar for user101436 @user101436

Actions