Implementing RFC 6238 in Node.js

This post assumes you are familiar with Node.js, if you are not please see "What is Node.js".


For those unaware of what RFC 6238 is, RFC 6238 describes how to implement the Time-Based One-Time Password algorithm (TOTP). Though we will not go too deep into it, the TOTP algorithm is built upon the HMAC-Based One-Time Password Algorithm (HOTP), described in RFC 4226. To quote RFC 6238:

"Basically, we define TOTP as TOTP = HOTP(K, T), where T is an integer and represents the number of time steps between the initial counter time T0 and the current Unix time [and K is the secret, which is used for the hash]."

The HOTP algorithm is built upon the HMAC- SHA-1 algorithm which is defined in RFC 2104; however, we will not be implementing the HMAC- SHA-1 algorithm since it is available through NPM, we will have to briefly discuss the HOTP algorithm, later.

RFC 6238 is relevant to you because the TOTP algorithm is one of the most common (if not the most common) algorithm used to generate the one time passwords most services have begun using. If you have signed up for/logged in to any bank, any social media, or any other service in the past year, you are probably familiar with the 6 to 8 digit codes that are required to log in, often referred to as Multi Factor Authentication (MFA). Though MFA can come in multiple forms (i.e. voice recognition, facial recognition, etc), so far, the most widely adopted measure relies upon the TOTP algorithm (or a similar algorithm).

To start our implementation of RFC 6238 in Node.js we will want to initiate NPM in a new directory and then install jssha (link to module documentation, here), afterwards we will create a JavaScript file, you can call the file whatever you want, though the commands below will create a file called index.js.

mkdir ./rfc6238 && cd ./rfc6238
npm init
npm i jssha
touch ./index.js
create a new directory, initialize NPM in the new directory, and install jssha, then make a JavaScript file

After we have jssha installed, we will begin to implement the TOTP algorithm one step at a time.

Step 1 Setting up our file

First we need to create a base32 number (since most TOTP codes are in base32), we can use the command(s) provided in the article here for this, (or you can use a different method if you like). We also need to set a counter to track the time it takes for our TOTP to change, most time based passwords change every 30 seconds so ours will do the same. If this were a real application, the secret would come from the user (via an app, webpage, or something similiar).

const secret = 'PNZCZG3EHFYRIYKOD57AJEXDXEVKMPNW';
const counter = 30;
Defining the variables secret and counter

Since we are using jssha, we also need to require jssha, so we can use it later, typically require statements go at the top of the file.

const jsSHA = require('jssha');

const secret = 'PNZCZG3EHFYRIYKOD57AJEXDXEVKMPNW';
const counter = 30;
Requiring jssha

All of the values we will generate will be base16 (hexadecimal) and therefore, before we implement the algorithm, we need to configure a way to get the current time and convert that time to base16.

const milliSinceEpoch = Math.round(new Date().getTime() / 1000);
const timeBase16 = (`0000000000000000${Math.floor(milliSinceEpoch / counter).toString(16)}`).slice(-16);
Retrieve number of milliseconds since Epoch (January 1st, 1970 at 12:00 am UTC) and convert that time to base16

On the second line of the code snippet above we convert the value of milliSinceEpoch / counter (after being rounded down) to base16 by calling the function toString and passing in 16. Once the value is in base16 we prepend the string with zeros so the time is at least 16 characters long, then, the function slice removes the zeros at the start of the string until the string is 16 characters long.

Step 2 Generating an HMAC-SHA-1 key

Since we have jssha required in our file and the current time as a base16 number, we can generate the HMAC-SHA-1 key based on our secret and counter. However, before we can calculate the HMAC-SHA-1 key we need to create a function to convert base32 numbers to base16 since the converters built into JavaScript will not suffice.

function base32toBase16(base32) {
  let base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
  let bits = "";
  let hex = "";
  let i = 0;
  while (i < base32.length) {
    val = base32chars.indexOf(base32.charAt(i).toUpperCase());
    bits += `00000${val.toString(2)}`.slice(-5);
    i++;
  }
  i = 0;
  while (i + 4 <= bits.length) {
    chunk = bits.substr(i, 4);
    hex = hex + parseInt(chunk, 2).toString(16);
    i += 4;
  }
  return hex;
}
Convert base32 to base16 (hexadecimal)

In the function above we convert our base32 secret to base16 by converting the value of each digit to binary, then converting that binary into hexadecimal. Once all of the digits have been converted to hexadecimal we return the value of the number. Our base32 to base16 converter is based upon the definition of base32 from RFC 4648 (which was preceded by RFC 3548); however, the base32 built into JavaScript seems to use the definition of base32 from Douglas Crockford, the inventor of the JavaScript programing language.

Now that we can convert base32 to base16, we can pass our secret to jssha to create our HMAC-SHA-1 key from our base16 secret.

const shaObj = new jsSHA("SHA-1", "HEX");
shaObj.setHMACKey(base32toBase16(secret), "HEX");
Initialize `jsSHA` and provide a key from the secret

After we have created our HMAC key, we then need to update our key with the value of our current time in base16.

shaObj.update(timeBase16);
Update HMAC key with the current base16 time

Step 3 Generating a One Time Password

Now that we have an updated key, we want to retrieve the hexadecimal value of our updated key and save the last digit of the value to a variable.

const hmac = shaObj.getHMAC("HEX");
const offset = parseInt(hmac.substring(hmac.length - 1), 10);
let otp = (parseInt(hmac.substr(offset * 2, 8), 16) & parseInt("7fffffff", 16)) + "";
otp = `000000${otp}`.slice(-6);
Retrieve the HMAC value, then save to last digit to offest, then generate the OTP

We are saving the last digit on line two of the snippet above in order to concatenate our HMAC key which will be used to genertae our one time password (OTP). On line three we are concatenating part of our HMAC key and the hexadecimal value 7fffffff with the bit wise operator & which will generate our OTP.  The reason behind setting the offset to the last digit then trimming the length of the key to that offset is because the offset acts as a counter, while the value 7fffffff mimics the logic used in the HOTP algorithm, then when the bit wise operator is used the result is the OTP. After generating the OTP we then may need to append zeros to our OTP before we output it to the client (via the command line, or other method).


The full code for this post (and a console log at the end) can be found below, and hopefully this article helped you learn about the TOTP algorithm!

const jsSHA = require('jssha');

const secret = 'PNZCZG3EHFYRIYKOD57AJEXDXEVKMPNW'; //change this to your own secret
const counter = 30;

function base32toBase16(base32) {
  let base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
  let bits = "";
  let hex = "";
  let i = 0;
  while (i < base32.length) {
    val = base32chars.indexOf(base32.charAt(i).toUpperCase());
    bits += `00000${val.toString(2)}`.slice(-5);
    i++;
  }
  i = 0;
  while (i + 4 <= bits.length) {
    chunk = bits.substr(i, 4);
    hex = hex + parseInt(chunk, 2).toString(16);
    i += 4;
  }
  return hex;
}

const milliSinceEpoch = Math.round(new Date().getTime() / 1000.0);
const timeBase16 = (`0000000000000000${Math.floor(milliSinceEpoch / counter).toString(16)}`).slice(-16);
const shaObj = new jsSHA("SHA-1", "HEX");
shaObj.setHMACKey(base32toBase16(secret), "HEX");
shaObj.update(timeBase16);
const hmac = shaObj.getHMAC("HEX");
const offset = parseInt(hmac.substring(hmac.length - 1), 10);
let otp = (parseInt(hmac.substr(offset * 2, 8), 16) & parseInt("7fffffff", 16)) + "";
otp = `000000${otp}`.slice(-6);

console.log(otp);