Google Authenticator is something that many of us use all the time but how many of us really understand how it works under the hood?
In this tutorial, I will demystify the magic behind Google Authenticatorâs expiring OTP codes and show you how to add Google Authenticator support to your application from scratch, in JavaScript.
As part of Pestoâs career accelerator program, I got an opportunity to contribute to this amazing open-source project called Enquirer. It lets you create stylish command line prompts that are user-friendly and easy to create. At the time of writing this article, Enquirer has upwards of 4500 Github stars and is used by more than 7500 other repositories.Â
One of the features I added to Enquirer was the ability to verify time-based one-time passwords, directly from command-line, generated by any two-factor authentication application, such as Google Authenticator, without the need of an internet connection.
Why Time-based OTPs?
Time-based OTP (TOTP) is an algorithm that factors in the current time to generate a unique one-time password.
In todayâs age, it is a no-brainer that passwords alone canât keep the bad guys out. You need an additional layer of securityâââa second factor.
There are many types of two-factor authentication systems, but for them to work, they must be both secure and user-friendly. TOTP ticks both boxes perfectly.
Itâs secure because:
- The password changes every n number of seconds (usually, 30 seconds), preventing eavesdroppers from using that same password later in the future if somehow theyâre able to get hold of it.
- The password may be generated by an app on the userâs phone, making it more difficult for an attacker to acquire the password, as the userâs phone is usually by his/her side.
Itâs user-friendly because:
- In mobile app implementations, the user only needs to launch the TOTP application and then key-in the TOTP that appears on-screen into the application
- Unlike most mobile-based one-time passwords that need to be received via a text message or the Internet through some wireless connection, TOTPs arenât dependent on the presence of a cellular signal or data connection.
I used Google Authenticator as the mobile app to verify one-time passwords.
Google Authenticator generates time-based OTPs which are calculated using the algorithm specified in RFC6238. The app also supports HMAC-based OTPs calculated using the algorithm specified in RFC4226.Â
Time-based OTPs rely on the algorithm for HMAC-based OTPs (HOTPs). In this article, weâll be implementing both the algorithms, using NodeJS (JavaScript).
Generating Secret
Firstly, weâll have to create an application-specific secret key that will be used to verify OTPs. This will also be shared with the Google Authenticator app.
The secret key is how the authenticator apps know what OTPs to generate for a specific app/website. The secret can be generated in many ways and this is one of the implementations.
Weâll generate a completely random buffer of data using the in-built
crypto
NodeJS library and then weâll encode the data to base32 using hi-base32
npm module.const crypto = require('crypto');
const base32 = require('hi-base32');
function generateSecret(length = 20) {
const randomBuffer = crypto.randomBytes(length);
return base32.encode(randomBuffer).replace(/=/g, '');
}
This function will return a random string which is our secret key.Â
Input this in the Google Authenticator app. Weâll be verifying the OTPs for this secret, using our implementation.
Generating HMAC-based OTPs
To generate HOTP we need a secret key and a counter value. We already have a secret. We donât need to worry about counter as of now because weâll be providing its value when we generate TOTPs.
Basics
Letâs say our function that generates HOTP accepts two arguments,
Letâs say our function that generates HOTP accepts two arguments,
secret
and counter
. First, weâll decode the secret.const decodedSecret = base32.decode.asBytes(secret);
Now weâll create a buffer from the
counter
value.const buffer = Buffer.alloc(8);
for (let i = 0; i < 8; i++) {
buffer[7 - i] = counter & 0xff;
counter = counter >> 8;
}
According to RFC4226, we have three major steps to generate a HOTP.
Step 1âââGenerate an HMACÂ value
Google Authenticator uses SHA1 algorithm to create HMAC. Weâll use functions from the
Google Authenticator uses SHA1 algorithm to create HMAC. Weâll use functions from the
crypto
library to create an HMAC (using SHA1), update the above-created buffer
with this and then produce an HMAC value;const hmac = crypto.createHmac('sha1', Buffer.from(decodedSecret));
hmac.update(buffer);
const hmacResult = hmac.digest();
hmacResult
is a 20-byte string which is an HMAC-SHA-1 value.Step 2âââDynamic Truncation
The purpose of dynamic truncation is to extract 4-byte dynamic binary code from a 20-byte HMAC-SHA-1 result. These steps are directly taken from RFC4226.
The purpose of dynamic truncation is to extract 4-byte dynamic binary code from a 20-byte HMAC-SHA-1 result. These steps are directly taken from RFC4226.
const offset = hmacValue[hmacValue.length - 1] & 0xf;
const code = ((hmacValue[offset] & 0x7f) << 24) |
((hmacValue[offset + 1] & 0xff) << 16) |
((hmacValue[offset + 2] & 0xff) << 8) |
(hmacValue[offset + 3] & 0xff);
Step 3âââCompute the HOTPÂ value
Google Authenticator generates six-digit passwords, so weâll extract the first six-digits of the
Google Authenticator generates six-digit passwords, so weâll extract the first six-digits of the
code
to get our final HOTP value.const hotp = code % (10 ** 6);
Putting it all together, the generateHOTP function can be written as:
const crypto = require('crypto');
const base32 = require('hi-base32');
function generateHOTP(secret, counter) {
const decodedSecret = base32.decode.asBytes(secret);
const buffer = Buffer.alloc(8);
for (let i = 0; i < 8; i++) {
buffer[7 - i] = counter & 0xff;
counter = counter >> 8;
}
// Step 1: Generate an HMAC-SHA-1 value
const hmac = crypto.createHmac('sha1', Buffer.from(decodedSecret));
hmac.update(buffer);
const hmacResult = hmac.digest();
// Step 2: Generate a 4-byte string (Dynamic Truncation)
const code = dynamicTruncationFn(hmacResult);
// Step 3: Compute an HOTP value
return code % 10 ** 6;
}
function dynamicTruncationFn(hmacValue) {
const offset = hmacValue[hmacValue.length - 1] & 0xf;
return (
((hmacValue[offset] & 0x7f) << 24) |
((hmacValue[offset + 1] & 0xff) << 16) |
((hmacValue[offset + 2] & 0xff) << 8) |
(hmacValue[offset + 3] & 0xff)
);
}
We have a function using which we can generate HMAC based one-time passwords. Now we can use this function to generate Time based one-time passwords.
Generating Time-based OTPs
The algorithm for time-based OTPs simply uses current Unix time (with a time-step of 30 seconds) as the
counter
value in the generateHOTP
function.We can get the current time in JavaScript using
Date.now()
. But this returns the number of milliseconds elapsed since January 1, 1970, 00:00:00 UTC
. We have to convert the time-step of 1 millisecond to time-step of 30 seconds.const counter = Math.floor(Date.now() / 30000);
We also accept a
window
argument which helps us get the TOTP of any time window from current time. So if we want to know what TOTP was generated before 2 minutes from now, we set window = -4
. This calculates the TOTP at 4 time-steps (of 30 seconds each) before the current time.So, our
generateTOTP
function can be written as:function generateTOTP(secret, window = 0) {
const counter = Math.floor(Date.now() / 30000);
return generateHOTP(secret, counter + window);
}
Verifying Time-based OTPs
To verify TOTPs generated on the Google Authenticator app, we need the secret key. Once we have the secret key we can use the
generateTOTP
function above and calculate the TOTP to see if it matches or not.Sometimes, it is possible that while a user is typing the OTP the current window of 30 seconds passes and the OTP she entered gets failed. To improve the user experience, we not only check the TOTP generated at the current time-step window but also one step before and one step after the current window.Â
If the current time is Tâ°, we'll verify the TOTP for these steps:
Tâ° â 1
Tâ°
Tâ° + 1
We can also use a wider window but doing so may be a security risk. If we use a window of
TⰠ± 20
, for example, this would mean that an OTP generated 10 minutes ago is still valid!Keeping this in mind, our
verifyTOTP
function can be written as:function verifyTOTP(token, secret, window = 1) {
if (Math.abs(+window) > 10) {
console.error('Window size is too large');
return false;
}
for (let errorWindow = -window; errorWindow <= +window; errorWindow++) {
const totp = generateTOTP(secret, errorWindow);
if (token === totp) {
return true;
}
}
return false;
}
token
is the TOTP from Google Authenticator. We are invoking generateTOTP
function to calculate the TOTPs for all windows and checking if it matches with the token entered.This function returns
true
if the token is successfully verified. This completes the implementation of Two Factor Authentication (TOTP) with Google Authenticator.Iâve removed some error checking to keep this article simple. You can find the complete code at this pull request.Â
Make sure you check out the Enquirer library and drop a Github star. Thanks for reading!
Hi, my name is Rajat. I am a truly full-stack software engineer with experience working on everything from patching U-boot and Linux drivers in C to launching top-rated products on Product Hunt.