TOTP 2FA in Golang Applications

TOTP 2FA in Golang Applications

Introduction

Time-Based One-Time Password (TOTP) is a widely used two-factor authentication (2FA) mechanism that enhances the security of online accounts and systems. TOTP generates temporary, one-time passwords that expire after a short period of time, typically 30 seconds. These passwords are generated based on a shared secret key and the current time, ensuring that they are dynamic and highly secure.

Here’s how TOTP works:

  1. Initial Setup: During the initial setup of 2FA for an account, the user associates their account with a TOTP-compatible authentication app or device. This step typically involves scanning a QR code or manually entering a shared secret key provided by the service.

  2. Shared Secret Key: A secret key is securely stored on both the server and the user’s device. This key is used as the basis for generating TOTP codes.

  3. Code Generation: When the user attempts to log in or perform a sensitive action, they open the TOTP app, which calculates a time-based code using the shared secret key and the current time. This code is typically a 6- or 8-digit numerical value.

  4. Validation: The user enters the generated TOTP code on the login page. The server also calculates the expected TOTP code based on the shared secret key and the current time window. If the user’s entered code matches the server’s calculated code, access is granted.

  5. Time-Based Security: TOTP codes are time-sensitive, meaning they change every 30 seconds (or a configurable time interval). This time-based aspect adds an extra layer of security, as codes quickly become invalid.

TOTP is a secure and convenient method for implementing 2FA, as it doesn’t require a network connection during code generation, making it resistant to many common attack vectors. Popular authentication apps like Google Authenticator and Authy support TOTP, making it accessible to a wide range of users and services seeking to enhance their online security.

Flow

The flow to authenticate with TOTP (this with Twilio service API but the flow is quite similar between the services) is demonstrated in the sequence diagram below:

register-user-public-docs-sequence-diagram-Verify_TOTP_Sequence_Diagram 4.png

This diagram showcases the data flow to register new TOTP credential (TOTP seeding).

verify-user-public-docs-sequence-diagram-Verify_TOTP_Sequence_Diagram 3.png

This diagram shows how TOTP works when we login into the system (validate user).

The Time-based One-Time Password (TOTP) algorithm works under the hood by combining a shared secret, a timestamp, and a cryptographic hash function to generate a one-time password that changes over time. Here’s a summarized overview of how TOTP flows:

  1. Shared Secret: TOTP relies on a shared secret, typically generated and securely stored by the server and shared with the user during the initial setup. This secret is a random value and must remain secret to ensure the security of the system.

  2. Timestamp: TOTP uses a timestamp, often in the form of the current Unix time (seconds since the Unix epoch, which is January 1, 1970). This timestamp serves as a time reference point.

  3. Hash Function: TOTP employs a cryptographic hash function, commonly HMAC-SHA1, HMAC-SHA256, or HMAC-SHA512. This function takes two inputs: the shared secret and the timestamp, and it produces a fixed-length hash value as output.

  4. Time Step: TOTP defines a time step, which is typically set to 30 seconds. The timestamp is divided by the time step to determine the current time step.

  5. Counter: The current time step, along with the shared secret, is used as inputs to the hash function. The result is a hash value.

  6. Dynamic Truncation: The hash value is dynamically truncated to create a shorter value. The truncation involves selecting a portion of the hash, usually the lower bits, to create a smaller number.

  7. OTP Generation: The truncated value is processed to create a one-time password (OTP). This is often done by converting the truncated value into a numerical representation, applying modulo operations to limit the length, and optionally adding leading zeros.

  8. OTP Display: The OTP is displayed to the user through a trusted authentication application (such as Google Authenticator or Authy) or hardware token.

  9. Validation: When the user attempts to log in, they enter the OTP generated by their authentication app. The server also calculates the expected OTP based on the shared secret and the current timestamp.

  10. Time Drift Tolerance: To account for time synchronization issues and device clock drift, the server may check for OTP validity in the current and adjacent time steps.

  11. Validation Success: If the OTP entered by the user matches the expected OTP calculated by the server, access is granted. Otherwise, access is denied.

In summary, TOTP generates one-time passwords by combining a secret key, a timestamp, and a cryptographic hash function. This process ensures that the generated passwords change over time, enhancing security by requiring a new password for each authentication attempt. Users and servers synchronize their clocks to ensure accurate OTP generation and validation, and time-based validity windows are used to account for potential time discrepancies. This combination of elements makes TOTP an effective and widely adopted method for two-factor authentication.

Implementation in Go

We can use our way to implement TOTP with a nearly identical flow for verification with https://github.com/pquerna/otp library.

Here is a simple implementation of the library:

package main

import (
    "github.com/pquerna/otp"
    "github.com/pquerna/otp/totp"

    "bufio"
    "bytes"
    "encoding/base32"
    "fmt"
    "image/png"
    "io/ioutil"
    "os"
    "time"
)

func display(key *otp.Key, data []byte) {
    fmt.Printf("Issuer:       %s\\n", key.Issuer())
    fmt.Printf("Account Name: %s\\n", key.AccountName())
    fmt.Printf("Secret:       %s\\n", key.Secret())
    fmt.Println("Writing PNG to qr-code.png....")
    ioutil.WriteFile("qr-code.png", data, 0644)
    fmt.Println("")
    fmt.Println("Please add your TOTP to your OTP Application now!")
    fmt.Println("")
}

func promptForPasscode() string {
    reader := bufio.NewReader(os.Stdin)
    fmt.Print("Enter Passcode: ")
    text, _ := reader.ReadString('\\n')
    return text
}

func main() {
    key, err := totp.Generate(totp.GenerateOpts{
        Issuer:      "Example.com",
        AccountName: "alice@example.com",
    })
    if err != nil {
        panic(err)
    }
    // Convert TOTP key into a PNG
    var buf bytes.Buffer
    img, err := key.Image(200, 200)
    if err != nil {
        panic(err)
    }
    png.Encode(&buf, img)

    // display the QR code to the user.
    display(key, buf.Bytes())

    // Now Validate that the user's successfully added the passcode.
    fmt.Println("Validating TOTP...")
    passcode := promptForPasscode()
    valid := totp.Validate(passcode, key.Secret())
    if valid {
        println("Valid passcode!")
        os.Exit(0)
    } else {
        println("Invalid passcode!")
        os.Exit(1)
    }
}

Look simple enough. Let’s break it down.

  • The function display is for displaying and generating the QR code (in this case replacing the FE).

  • The promptForPasscode function is for the user to input the passcode.

  • in main function:

    • First, we generate a key ( this key is for registering the credential ) and this key holds the secret to the credential that must be saved in the DB ( replace the factor SID ).

    • Then we generate an image from the QR code of the key

    • Then display the QR code to the user.

    • The user then scans the QR code with their favorite authenticate app (MS Auth, Authy, Google Auth,…).

    • Then call promptForPasscode function to prompt a field for user to input the passcode in the app and verify it with Validate method.

As you can see the flow is quite similar for any other 3rd party services but with this, we have to manage everything ourselves.

If you run the code above it will be something like this:

And your Directory should have qr-code.png image

Scan the QR code with your favorite Authenticator ( Google Authenticator, MS Authenticator, Twilio Authy,…) and input the passcode into the prompt you will get something like this:

Alternative

We can also use other off-the-shelf services to save the effort of maintaining and upgrading

https://www.twilio.com/docs/verify/quickstarts/totp

https://firebase.google.com/docs/auth/web/totp-mfa

Conclusion

The implementation of Two-Factor Authentication (2FA) using the Time-based One-Time Password (TOTP) algorithm in Go represents a significant advancement in cybersecurity. TOTP, with its reliance on time-based codes generated by a shared secret, adds an invaluable layer of security by requiring an additional one-time code, even if an attacker gains access to the user's password. The Go programming language's efficiency, simplicity, and a strong standard library make it a pragmatic choice for implementing TOTP-based 2FA, allowing developers to easily create TOTP generators and verifiers. Overall, the TOTP implementation in Go enhances security and demonstrates the adaptability of the language in addressing cybersecurity challenges.

Contributing

At Dwarves, we encourage our people to read, write, share what we learn with others, and contributing to the Brainery is an important part of our learning culture. For visitors, you are welcome to read them, contribute to them, and suggest additions. We maintain a monthly pool of $1500 to reward contributors who support our journey of lifelong growth in knowledge and network.

Love what we are doing?

Reference: