You may have entered a 6-digit code from a 2FA app like Google Authenticator 😬, but where did this code come from? The answer lies within the QR code and the inner workings of the Time-based One-time Password algorithm (TOTP) 🔍.
In this article, we will decode a QR code, explain the TOTP algorithm, and show how the HOTP value is calculated to generate a time-based 2FA code.
Setting up the sample
We will use the example of enrolling a work email account for a 2FA app, we will use Microsoft Authenticator app and scan the following QR code.
Decoding the QR
First, we must decode and make sense of the QR code.
To decode the QR code, we can use a decoder. We can see the following URI:
otpauth://totp/work-email-stevemc%40work-email.com?secret=BL5IVV7PKMI5KPIJ&issuer=Work%20Email
From this, we can see some of the fields which are displayed by the app. In the order of appearance, this is the issuer, account name, and issuer again. There are also some information which are intended to be read by the app. Here, it is the type of OTP algorithm (TOTP), and secret.
The time
Time undoubtedly plays an important role in the TOTP algorithm, which is defined as TOTP = HOTP(K, T), where K is the secret key and T = (Current Unix Time - T0) / X, with floor division being applied (the remainder of the division is ignored).
Current Unix Time is an integer counting the number of seconds (excluding leap seconds) since the Unix Epoch (January 1st, 1970).
To generate a code, the app takes the current time and divides it by the interval, in this case, 30 seconds. The app then hashes the result of this division with a secret key and truncates the result to create a 6-digit code.
HOTP
From before, we have K = BL5IVV7PKMI5KPIJ and T = {Current Unix Time / 30}. By default, HOTP uses HMAC-SHA1. Taking K (key) and T (counter value), HMAC-SHA1 produces an irreversible fixed-size value (a hash) from the input.
Substituting in the converted values, we can compute the HMAC. This hash is represented in hexadecimal (base 16), which uses the digits 0-9 and A-F to represent values from 0 to 15.
Next, we follow a dynamic truncation algorithm to extract a section of the hash.
Given the 20 byte hash String, extract the “low-order 4 bits of String[19]” as Offset. When splitting the hash into 20 segments, String[19] is the last segment.
Convert the 4 bits into a number.
Return the last 31 bits of String[Offset]...String[Offset+3]
Finally, the last 6 digits of the result are taken as the TOTP value.
Top comments (0)