Go Code: Generating a One-Time Password (TOTP)

Have you ever logged into a service that required you to enter a six-digit code from an app on your phone? If so, you've interacted with a Time-Based One-Time Password (TOTP), a key component of Two-Factor Authentication (2FA). This article breaks down a piece of Go code that implements this very mechanism, showing how to create a TOTP securely and efficiently.

The code we will examine is the generateTOTP function, designed to generate a temporary one-time password.

 

func generateTOTP(email string, config models.ConfParams) (string, time.Time, error) {
	// Derive a unique secret key from the user's email address
	// This ensures each user has a different TOTP secret
	derivedSecret := deriveSecretFromEmailWithArgon2(email, config.AppSecret, config)
// Create a new TOTP generator with the following parameters:
// - SHA1 hash algorithm (standard for TOTP)
// - Derived secret as the key
// - Code length (usually 6 digits)
// - Time window in seconds (usually 30 seconds)
// - No counter offset or skew tolerance
otp, err := otp.NewTOTP(otp.HashAlgorithmSHA1, derivedSecret, config.CodeLenght, config.Seconds, 0, 0)
if err != nil {
	return "", time.Time{}, err
}

// Get the current Unix timestamp
now := time.Now()

// Generate the TOTP code for the current time window
currentCode := otp.Generate(now.Unix())

// Calculate when the current code expires
// This truncates to the current time window and adds one full window duration
expiresAt := now.Truncate(time.Duration(config.Seconds) * time.Second).Add(time.Duration(config.Seconds) * time.Second)

return currentCode, expiresAt, nil

}

 

This function takes a user's email and a configuration object (config) as input, and returns the generated TOTP code (a string), its expiration time (time.Time), and a potential error.

Here the 'config' struct: 


type ConfParams struct {
	Mailsenderaddress string `yaml:"mailsenderaddress"`
	Mailsendername    string `yaml:"mailsendername"`
	Mailhost          string `yaml:"mailhost"`
	Mailport          int    `yaml:"mailport"`
	Mailsubjectok     string `yaml:"mailsubjectok"`
	MailUser          string `yaml:"mailUser"`
	MailPw            string `yaml:"mailPw"`
	Host              string `yaml:"host"`
	Port              string `yaml:"port"`
	CodeLenght        int    `yaml:"codeLenght"`
	Seconds           int    `yaml:"seconds"`
	AppSecret         string `yaml:"appSecret"`
	HashIterations    uint32 `yaml:"hashIterations"`
	HashMemory        uint32 `yaml:"hashMemory"`
	HashThreads       uint8  `yaml:"hashThreads"`
	HashKeyLen        uint32 `yaml:"hashKeyLen"`
	HtmlTemplate      string `yaml:"htmlTemplate"`
	ApplicationName   string `yaml:"applicationName"`
	AppUser           string `yaml:"appUser"`
}

1. Generating a Unique Secret Key

The first line of the code is crucial for security:

derivedSecret := deriveSecretFromEmailWithArgon2(email, config.AppSecret, config)

 

To generate a TOTP code, the system needs a secret key that is known to both the server and the user's application (like Google Authenticator). It's not secure to use the same key for all users. This code solves the problem by deriving a unique secret key for each user, starting from their email address. The use of Argon2, a secure hashing algorithm, ensures that even if your site's main AppSecret key were compromised, it would be difficult to trace back to individual user secrets.

 

2. Creating the TOTP Generator

 

Once the unique secret key is obtained, the code creates a TOTP generator:

otp, err := otp.NewTOTP(otp.HashAlgorithmSHA1, derivedSecret, config.CodeLenght, config.Seconds, 0, 0)

 

This line creates a generator object that is the "engine" for creating the password. The most important parameters are:

  • HashAlgorithmSHA1: The hashing algorithm (SHA1 is the standard for TOTP).

  • derivedSecret: The unique secret key we just generated.

  • CodeLenght: The length of the code (usually 6 digits).

  • Seconds: The time window in which the code is valid (usually 30 seconds).

 

3. Calculating the Current Code and Expiration

 

At this point, the function generates the current TOTP code and calculates its expiration.

 

now := time.Now()
currentCode := otp.Generate(now.Unix())
expiresAt := now.Truncate(time.Duration(config.Seconds) * time.Second).Add(time.Duration(config.Seconds) * time.Second)

 

The otp generator uses the current time (now) to calculate a unique value. The code is "time-bound": for each time window (e.g., every 30 seconds), a different code is generated.

The most clever part is the expiration calculation. It doesn't just add 30 seconds to the current time, but it truncates the current time to the start of the current time window (e.g., 14:02:00 instead of 14:02:15), and then adds 30 seconds. This ensures that the expiration time is always the start of the next 30-second interval, guaranteeing that the code expires exactly when a new one is generated.

 

In Summary

 

This small Go function encapsulates the essence of time-based two-factor authentication. By:

  1. Securely deriving a secret key for each user.

  2. Creating a configurable TOTP generator.

  3. Generating the code and precisely calculating its expiration.

The code ensures that each user has a one-time password that changes regularly, significantly strengthening login security.


TOPT Golang