Skip to content
fumbledlolPublic

About

RFC 6238 TOTP and RFC 4226 HOTP two-factor authentication with backup recovery codes

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

3 Commits

Folders and files

Repository files navigation

totp-go

Go Reference License: MIT

Go implementation of RFC 6238 (TOTP) and RFC 4226 (HOTP) with backup recovery codes.

Features

  • Strictly follows RFC 6238 and RFC 4226, verified against official RFC test vectors.
  • Standard library only (crypto/hmac, crypto/rand, crypto/subtle, encoding/base32).
  • Constant-time code comparison with crypto/subtle.
  • ValidateWithStep returns the counter step to prevent replay attacks within the 30-second window.
  • Generates xxxx-xxxx backup codes using 32 unambiguous characters (23456789abcdefghjkmnpqrstuvwxyz, skipping 0, 1, i, l, o).
  • Zero modulo bias because 256 divides evenly by 32.
  • Generates secrets in ~145 ns and recovery codes in ~560 ns.

Installation

go get github.com/fumbledlol/totp-go

Usage

1. Enrollment (secret and QR code URL)

package main

import (
	"fmt"
	"log"

	"github.com/fumbledlol/totp-go"
)

func main() {
	secret, err := totp.GenerateSecret()
	if err != nil {
		log.Fatal(err)
	}

	authURL := totp.BuildURL("Acme", "alice@example.com", secret)
	fmt.Println("Secret:", secret)
	fmt.Println("URL:", authURL)
}

2. Verify passcodes

valid := totp.Validate("123456", secret)
if valid {
    fmt.Println("Code accepted")
}

3. Replay prevention

To stop users or attackers from reusing the same passcode during its 30-second window, record the consumed counter step in Redis or SQL:

valid, step := totp.ValidateWithStep(passcode, userSecret)
if !valid {
    return errors.New("invalid or expired passcode")
}

if step <= user.LastUsedTOTPStep {
    return errors.New("passcode already used")
}

user.LastUsedTOTPStep = step

4. Backup recovery codes

Generate, normalize, and hash backup codes:

codes, err := totp.GenerateRecoveryCodes(10)
if err != nil {
    log.Fatal(err)
}

// Show these to the user once:
for _, code := range codes {
    fmt.Println(code) // e.g. 7m4k-9y2p
}

// Store hashes with your server pepper
serverPepper := []byte("your-secret-server-pepper")
for _, code := range codes {
    hash := totp.HashRecoveryCode(code, serverPepper)
    // save hash to database...
}

// When validating user input:
inputCode := "7M4K 9Y2P" // handles spaces, dashes, and casing
inputHash := totp.HashRecoveryCode(inputCode, serverPepper)

Benchmarks

BenchmarkGenerateSecret-12             8068794       144.5 ns/op        32 B/op       1 allocs/op
BenchmarkValidate-12                    716224      1457.0 ns/op      1024 B/op      16 allocs/op
BenchmarkGenerateRecoveryCodes-12       2140929       562.6 ns/op       400 B/op      12 allocs/op
BenchmarkHashRecoveryCode-12           1605768       758.0 ns/op       664 B/op      10 allocs/op

License

MIT

About

RFC 6238 TOTP and RFC 4226 HOTP two-factor authentication with backup recovery codes

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages