T-OTP

Article originally written for the Coddity blog
For a while now I’ve been building, in my spare time, a complete authentication service, with the aim of implementing a lot of authentication mechanisms: magic link, login/password, Oauth2, account validation, password recovery, and the famous 2FA.
I’ll admit I used Google Authenticator every day without ever thinking to look at the mechanism “under the hood”. This article is the plain-language version of what I learned.
2 Factor Access
First, a quick reminder of what 2FA is: two-factor authentication. It is a strong authentication method that asks the user to provide 2 proofs of identity. You log in to a site with your login/password and you have to enter a code that only you know.

One Time Password (OTP)
The OTP is a password which, as the name says, can be used only once: one login action. Unlike your static login password, which doesn’t change until YOU change it, the OTP is dynamic, meaning it changes on every action.
An OTP can be generated in different ways:
- by using time and a secret key shared between the client and the server (this is the T-OTP we’re going to dig into next)
- by a hash chain: a password is generated from the previous one
- by a counter or a mathematical challenge
The setup for the user can be imagined in different ways:
- an application like Google Authenticator or Authy
- small electronic devices with a display that hand out authentication codes
- an SMS received on the user’s mobile
- and even paper

HMAC-OTP
H-OTP was described in RFC4226.
A unique-code generation algorithm is used symmetrically by the client and the server to generate a code at each login request. The server compares the code provided by the client with the one it generated itself, and allows the login if the 2 codes match.
The code is generated by a predefined hash algorithm (SHA-1, SHA-256, or SHA-512) over the secret key together with a dynamic factor common to the server and the client, for example a login counter. The result is truncated so the HOTP code can be extracted, and that code can be 6 digits long (the most common) or more.

Time based-OTP
The Time based One Time Password is derived from H-OTP, and described in RFC6238.
Here the dynamic factor T is the number of time intervals X (generally 30s) elapsed since UNIX time, that is 1 January 1970 UTC, with T = (time.Now() - T0)/X.
Every time interval X, the HMAC code changes, and whatever the device, the value of T is the same.

When T-OTP 2FA is turned on, the server generates a secret key for each user. The client needs a few pieces of information to generate codes identical to the server’s:
- the interval T
- the algorithm used
- the user’s account
- the issuer
- the secret key
That information is handed to the login client via a QR code or a URI. The QR code below carries the same information as the following URI: otpauth://totp/demoT-OTP:debbie.harry@coddity.com?algorithm=SHA1&digits=6&issuer=demoT-OTP&period=30&secret=MEQGI2LSOR4SA3DJOR2GKIDTMVRXEZLU

Demo code
I wrote a small Go program so you can play with T-OTP, available here. This code uses a package that generates HOTP and TOTP and checks the codes sent by a client.
The parameters used are the following (hardcoded, if you please):
- a secret key
a dirty little secret - a time interval X fixed at 30 seconds
- SHA-1 as the hash algorithm
- a length of 6 digits for the HMAC code
- a fixed account
debbie.harry@coddity.com - an issuer
demoT-OTP
Using it couldn’t be simpler. With Go installed on your machine, you just build and run the code with the argument generate to generate the user, run to start generating codes, and verify <TOTP code> to check a code provided by the client.
Anyone following along will have understood that whoever runs the generate command generates the same user (debbie.harry@coddity.com) with the same secret key a dirty litte secret. The consequence is that the codes generated on every Authenticator-style app will be identical and will change every 30 seconds, unless you change the secret key in the code.
package main
import (
"encoding/base32"
"fmt"
"log"
"os"
"time"
"github.com/gosuri/uilive"
"github.com/jltorresm/otpgo"
"github.com/jltorresm/otpgo/config"
)
var account *otpgo.TOTP
func newOTP() *otpgo.TOTP {
secretString := []byte("a dirty litte secret")
secret := base32.StdEncoding.EncodeToString(secretString)
return &otpgo.TOTP{
Key: secret,
Period: 30,
Delay: 2,
Algorithm: config.HmacSHA1,
Length: 6,
}
}
func generateQRCode() {
keyUri := account.KeyUri("debbie.harry@coddity.com", "demoT-OTP")
uri := keyUri.String()
qrCode, err := keyUri.QRCode()
if err != nil {
log.Fatal(err)
}
fmt.Printf("************URI**************\n%s\n*****************************\n\n\n************QRCode**************\n%s\n*****************************", uri, qrCode)
}
func run() {
writer := uilive.New()
writer.Start()
for {
token, err := account.Generate()
if err != nil {
log.Fatalf(err.Error())
}
fmt.Fprintf(writer, "code : %s\n", token)
time.Sleep(500 * time.Millisecond)
}
writer.Stop()
}
func verify(code string) {
access, err := account.Validate(code)
if err != nil {
log.Fatalf(err.Error())
}
if access {
fmt.Println("login in!")
} else {
fmt.Println("access denied")
}
}
func main() {
account = newOTP()
if len(os.Args) == 1 || len(os.Args) > 3 {
fmt.Println("Usage : generate / run / verify <CODE_2_VERIFY>")
os.Exit(1)
}
switch os.Args[1] {
case "generate":
generateQRCode()
case "run":
run()
case "verify":
if len(os.Args) != 3 {
fmt.Println("need code to verify")
os.Exit(1)
}
verify(os.Args[2])
default:
fmt.Println("Usage : generate / run / verify <CODE_2_VERIFY>")
os.Exit(1)
}
}
Specifics
👉 The usual 30 seconds of validity is a compromise between security and ease of use: long enough to type by hand, and short enough to shrink the attack window if a third party got hold of the OTP. Besides, a longer window for typing an OTP would lock out the user who mistyped it. Would you be up for code-entry windows of 3 min, and potentially waiting just as long when you get it wrong? I very much doubt it 🤓
👉 The RFC looks at the problems caused by the client and the validating server drifting out of sync, and recommends accepting codes from the previous (or next) time intervals according to a predefined step P: for a value of T the server also checks the codes for the range [T-P; T+P]. That way, if the clocks are off, the user can still be validated.
👉 The server has to store the secret key tied to a user, unlike passwords, of which only the hash is saved. Possibly a security hole.
👉 There are no calls to the network. The code can be generated offline by the authenticator apps, but I’ll admit I haven’t checked whether, for example, Google Authenticator logs the applications you use and sends them off somewhere.
👉 Losing the device that holds the app and the secret key tied to the account is a problem, unless you kept the URI/QRcode somewhere or used a second device when you enrolled. Generally, a recovery key is handed out when 2FA is turned on, based on a sequence of words. Authy offers a backup system to cover this, but if Authy gets hacked… anyway, you get it.