tinymfa

package module
v0.4.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 5, 2026 License: MIT Imports: 15 Imported by: 0

README

Tiny MFA

A Go package for generating and verifying Time-Based One-Time Passwords (TOTP) per RFC 6238.

What it does

  • Generate and validate TOTP tokens (SHA-1, SHA-256, SHA-512)
  • Generate secret keys of appropriate size for each algorithm
  • Create QR codes so users can add accounts to their authenticator app
  • AES-GCM encrypt/decrypt helpers
  • Base32 encode/decode for secret keys
  • Bcrypt password hashing

Installation

go get github.com/ghmer/go-tiny-mfa

Quick Start

package main

import (
    "fmt"
    "time"

    "github.com/ghmer/go-tiny-mfa"
    "github.com/ghmer/go-tiny-mfa/utils"
)

func main() {
    tmfa := tinymfa.NewTinyMfa()
    util := utils.NewTinyMfaUtil()

    // Generate a secret key
    key, err := tmfa.GenerateStandardSecretKey()
    if err != nil {
        panic(err)
    }

    // Encode the key to base32 for storage/display
    encodedKey := util.EncodeBase32Key(key)
    fmt.Println("Secret Key:", *encodedKey)

    // Generate a TOTP token
    token, err := tmfa.GenerateToken(
        time.Now().Unix(),
        key,
        tinymfa.Present,
        6,
        tinymfa.SHA1,
        tinymfa.DefaultTimeStep,
        tinymfa.DefaultT0,
    )
    if err != nil {
        panic(err)
    }
    fmt.Printf("Current Token: %06d\n", token)

    // Validate the token
    valid, err := tmfa.ValidateToken(
        token,
        key,
        time.Now().Unix(),
        6,
        tinymfa.SHA1,
        tinymfa.DefaultTimeStep,
        tinymfa.DefaultT0,
    )
    if err != nil {
        panic(err)
    }
    fmt.Println("Token Valid:", valid)
}

Core API

Secret Key Generation

Keys are generated using crypto/rand. There are convenience methods for the recommended key sizes per algorithm:

tmfa := tinymfa.NewTinyMfa()

// 20-byte key (for SHA-1)
key, err := tmfa.GenerateStandardSecretKey()

// 32-byte key (for SHA-256)
key, err := tmfa.GenerateExtendedSecretKey()

// 64-byte key (for SHA-512)
key, err := tmfa.GenerateSuperbSecretKey()

// Or pick the algorithm and let the library choose the size
key, err := tmfa.GenerateSecretKeyForAlgorithm(tinymfa.SHA256)

// Or specify the size directly (20, 32, or 64 bytes)
key, err := tmfa.GenerateSecretKey(tinymfa.KeySizeSHA256)
Token Generation
tmfa := tinymfa.NewTinyMfa()
timestamp := time.Now().Unix()

// 6-digit token, SHA-1, default 30-second window
token, err := tmfa.GenerateToken(
    timestamp,
    &secretKey,
    tinymfa.Present,         // Present, Future, or Past window
    6,                       // Token length (5–8 digits)
    tinymfa.SHA1,
    tinymfa.DefaultTimeStep, // 30 seconds
    tinymfa.DefaultT0,       // epoch offset 0
)

// Token for the next time window
nextToken, err := tmfa.GenerateToken(
    timestamp,
    &secretKey,
    tinymfa.Future,
    6,
    tinymfa.SHA1,
    tinymfa.DefaultTimeStep,
    tinymfa.DefaultT0,
)

// 8-digit token with SHA-256
token, err := tmfa.GenerateToken(
    timestamp,
    &secretKey,
    tinymfa.Present,
    8,
    tinymfa.SHA256,
    tinymfa.DefaultTimeStep,
    tinymfa.DefaultT0,
)
Token Validation

Validation checks the present, past, and future time windows to account for clock drift:

tmfa := tinymfa.NewTinyMfa()

// Basic validation
valid, err := tmfa.ValidateToken(
    123456,
    &secretKey,
    time.Now().Unix(),
    6,
    tinymfa.SHA1,
    tinymfa.DefaultTimeStep,
    tinymfa.DefaultT0,
)

// Convenience method — uses the current timestamp automatically
validation := tmfa.ValidateTokenCurrentTimestamp(
    123456,
    &secretKey,
    6,
    tinymfa.SHA1,
    tinymfa.DefaultTimeStep,
    tinymfa.DefaultT0,
)
fmt.Println("Valid:", validation.Success)
fmt.Println("Message:", validation.Message)
if validation.Error != nil {
    fmt.Println("Error:", validation.Error)
}

// Or validate against a specific timestamp
validation = tmfa.ValidateTokenWithTimestamp(
    123456,
    &secretKey,
    timestamp,
    6,
    tinymfa.SHA1,
    tinymfa.DefaultTimeStep,
    tinymfa.DefaultT0,
)
QR Code Generation

Generate QR codes that work with Google Authenticator, Authy, and similar apps:

tmfa := tinymfa.NewTinyMfa()
util := utils.NewTinyMfaUtil()

encodedKey := util.EncodeBase32Key(&secretKey)

// Get the QR code as PNG bytes
qrCode, err := tmfa.GenerateQrCode(
    "MyApp",
    "user@example.com",
    encodedKey,
    6,
    tinymfa.SHA1,
    tinymfa.DefaultTimeStep,
)

// Save it yourself
err = os.WriteFile("qrcode.png", qrCode, 0644)

// Or let the library write it to a file directly
err = tmfa.WriteQrCodeImage(
    "MyApp",
    "user@example.com",
    encodedKey,
    6,
    tinymfa.SHA1,
    tinymfa.DefaultTimeStep,
    "qrcode.png",
)

Utility Functions

Base32 Encoding/Decoding
util := utils.NewTinyMfaUtil()

encoded := util.EncodeBase32Key(&secretKey)
fmt.Println(*encoded)

decoded, err := util.DecodeBase32Key(encoded)
AES Encryption/Decryption

Uses AES-GCM under the hood:

util := utils.NewTinyMfaUtil()

data := []byte("sensitive information")
passphrase := []byte("my-secure-passphrase")

// Encrypt / decrypt in memory
encrypted, err := util.Encrypt(&data, &passphrase)
decrypted, err := util.Decrypt(encrypted, &passphrase)

// Or work with files directly
err = util.EncryptFile("secrets.enc", &data, &passphrase)
decrypted, err = util.DecryptFile("secrets.enc", &passphrase)
Bcrypt Hashing
util := utils.NewTinyMfaUtil()

password := []byte("user-password")
hashed, err := util.BcryptHash(password)

err = util.BycrptVerify(hashed, password)
if err == nil {
    fmt.Println("Password is valid")
}

Configuration

Hash Algorithms

Three algorithms are supported, as defined in RFC 6238:

Constant Algorithm Note
tinymfa.SHA1 HMAC-SHA-1 Default, widest compatibility
tinymfa.SHA256 HMAC-SHA-256 Good default for new projects
tinymfa.SHA512 HMAC-SHA-512 Larger key, larger HMAC
Custom Time Parameters

You can change the time step and epoch offset if you need to:

// 60-second time step instead of the default 30
token, err := tmfa.GenerateToken(
    timestamp, &secretKey, tinymfa.Present,
    6, tinymfa.SHA1,
    60, // time step in seconds
    0,  // epoch offset
)

// Custom epoch offset (e.g. Jan 1, 2021)
token, err := tmfa.GenerateToken(
    timestamp, &secretKey, tinymfa.Present,
    6, tinymfa.SHA1,
    tinymfa.DefaultTimeStep,
    1609459200,
)
QR Code Colors
import "github.com/ghmer/go-tiny-mfa/structs"

tmfa := tinymfa.NewTinyMfa()

tmfa.SetQRCodeConfig(structs.QrCodeConfig{
    BgColor: structs.ColorSetting{Red: 255, Green: 255, Blue: 255, Alpha: 255},
    FgColor: structs.ColorSetting{Red: 0, Green: 0, Blue: 255, Alpha: 255},
})

current := tmfa.GetQRCodeConfig()

API Reference

TinyMfa
Method Description
NewTinyMfa() TinyMfaInterface Create a new instance
GenerateStandardSecretKey() (*[]byte, error) 20-byte key
GenerateExtendedSecretKey() (*[]byte, error) 32-byte key
GenerateSuperbSecretKey() (*[]byte, error) 64-byte key
GenerateSecretKey(size int8) (*[]byte, error) Key of a given size
GenerateSecretKeyForAlgorithm(algorithm HashAlgorithm) (*[]byte, error) Key sized for a given algorithm
GenerateToken(...) (int, error) Generate a TOTP token
ValidateToken(...) (bool, error) Validate a TOTP token
ValidateTokenCurrentTimestamp(...) Validation Validate using current time
ValidateTokenWithTimestamp(...) Validation Validate using a specific time
GenerateQrCode(...) ([]byte, error) QR code as PNG bytes
WriteQrCodeImage(...) error Write QR code PNG to a file
BuildPayload(...) string Build an otpauth:// URL
SetQRCodeConfig(structs.QrCodeConfig) Set QR code colors
GetQRCodeConfig() structs.QrCodeConfig Get current QR code colors
GenerateMessageBytes(int64) ([]byte, error) Int64 → big-endian bytes
CalculateHMAC([]byte, *[]byte, HashAlgorithm) ([]byte, error) Compute HMAC
GenerateMessage(int64, uint8, int64, int64) (int64, error) Compute the time counter value
TinyMfaUtil
Method Description
NewTinyMfaUtil() TinyMfaUtilInterface Create a new utility instance
Encrypt(data, passphrase *[]byte) (*[]byte, error) AES-GCM encrypt
Decrypt(data, passphrase *[]byte) (*[]byte, error) AES-GCM decrypt
EncryptFile(path string, data, passphrase *[]byte) error Encrypt to file
DecryptFile(path string, passphrase *[]byte) (*[]byte, error) Decrypt from file
CreateMd5Hash(b *[]byte) *[]byte MD5 hash
EncodeBase32Key(key *[]byte) *string Base32 encode
DecodeBase32Key(encodedKey *string) (*[]byte, error) Base32 decode
BcryptHash(tohash []byte) ([]byte, error) Bcrypt hash
BycrptVerify(comparable, verifiable []byte) error Bcrypt verify
Constants
Constant Value Purpose
SHA1 HMAC-SHA-1
SHA256 HMAC-SHA-256
SHA512 HMAC-SHA-512
KeySizeSHA1 20 Key size for SHA-1
KeySizeSHA256 32 Key size for SHA-256
KeySizeSHA512 64 Key size for SHA-512
Present Current time window
Future Next time window
Past Previous time window
DefaultTimeStep 30 Default time step in seconds
DefaultT0 0 Unix epoch

License

MIT — see LICENSE for details.

Documentation

Index

Constants

View Source
const (
	// Present can be used as an Offset Type
	Present uint8 = iota
	// Future can be used as an Offset Type
	Future
	// Past can be used as an Offset Type
	Past
)
View Source
const (
	// KeySizeSHA1 is the recommended secret key size for SHA-1 (160 bits / 20 bytes).
	// RFC 6238 Section 4 recommends keys be at least as long as the HMAC output.
	KeySizeSHA1 int8 = 20

	// KeySizeSHA256 is the recommended secret key size for SHA-256 (256 bits / 32 bytes).
	// RFC 6238 Section 4 recommends keys be at least as long as the HMAC output.
	KeySizeSHA256 int8 = 32

	// KeySizeSHA512 is the recommended secret key size for SHA-512 (512 bits / 64 bytes).
	// RFC 6238 Section 4 recommends keys be at least as long as the HMAC output.
	KeySizeSHA512 int8 = 64
)
View Source
const (
	// DefaultTimeStep is the default time step size in seconds (RFC 6238 Section 4.1).
	DefaultTimeStep int64 = 30

	// DefaultT0 is the default Unix epoch offset in seconds (RFC 6238 Section 4.1).
	DefaultT0 int64 = 0
)

Variables

This section is empty.

Functions

This section is empty.

Types

type HashAlgorithm added in v0.4.0

type HashAlgorithm uint8

HashAlgorithm represents the hash algorithm used for HMAC computation. RFC 6238 Section 1.2 defines SHA-1, SHA-256, and SHA-512 as valid algorithms.

const (
	// SHA1 selects HMAC-SHA-1 for TOTP computation (RFC 6238 Section 1.2).
	SHA1 HashAlgorithm = iota
	// SHA256 selects HMAC-SHA-256 for TOTP computation (RFC 6238 Section 1.2).
	SHA256
	// SHA512 selects HMAC-SHA-512 for TOTP computation (RFC 6238 Section 1.2).
	SHA512
)

type TinyMfa added in v0.3.0

type TinyMfa struct {
	QRCodeConfig structs.QrCodeConfig
}

func (*TinyMfa) BuildPayload added in v0.3.0

func (tinymfa *TinyMfa) BuildPayload(issuer, username string, secret *string, digits uint8, algorithm HashAlgorithm, timeStep int64) string

BuildPayload builds the otpauth:// URL payload for QR code generation with specified algorithm and timeStep.

func (*TinyMfa) CalculateHMAC added in v0.3.0

func (tinymfa *TinyMfa) CalculateHMAC(message []byte, key *[]byte, algorithm HashAlgorithm) ([]byte, error)

CalculateHMAC calculates the HMAC value for a given message and key using the specified hash algorithm. Supported algorithms are SHA-1, SHA-256, and SHA-512. RFC 2104 defines the HMAC construction. RFC 6238 Section 1.2 specifies the supported hash functions for TOTP.

func (*TinyMfa) ConvertColorSetting added in v0.3.0

func (tinymfa *TinyMfa) ConvertColorSetting(setting structs.ColorSetting) color.Color

func (*TinyMfa) GenerateExtendedSecretKey added in v0.3.0

func (tinymfa *TinyMfa) GenerateExtendedSecretKey() (*[]byte, error)

GenerateExtendedSecretKey returns a 32-byte secret key (SHA-256 recommended size).

func (*TinyMfa) GenerateMessage added in v0.3.0

func (tinymfa *TinyMfa) GenerateMessage(timestamp int64, offsetType uint8, timeStep int64, t0 int64) (int64, error)

GenerateMessage computes the time counter T for TOTP using configurable time step and epoch offset parameters. The counter is calculated as:

T = floor((unixTime + offset - t0) / timeStep)

where offset is determined by offsetType: Present=0, Future=+timeStep, Past=-timeStep. RFC 6238 Section 4.2 defines the time counter computation. RFC 6238 Section 5.2 defines the time step size X (default 30s) and epoch T0 (default 0).

func (*TinyMfa) GenerateMessageBytes added in v0.3.0

func (tinymfa *TinyMfa) GenerateMessageBytes(message int64) ([]byte, error)

GenerateMessageBytes takes in a int64 number and turns it to a BigEndian byte array

func (*TinyMfa) GenerateQrCode added in v0.3.0

func (tinymfa *TinyMfa) GenerateQrCode(issuer, user string, secret *string, digits uint8, algorithm HashAlgorithm, timeStep int64) ([]byte, error)

GenerateQrCode Generates a QRCode of the totp url with specified algorithm and timeStep

func (*TinyMfa) GenerateSecretKey added in v0.3.0

func (tinymfa *TinyMfa) GenerateSecretKey(size int8) (*[]byte, error)

GenerateSecretKey returns a secret key of the specified size. Valid sizes are KeySizeSHA1 (20), KeySizeSHA256 (32), and KeySizeSHA512 (64).

func (*TinyMfa) GenerateSecretKeyForAlgorithm added in v0.4.0

func (tinymfa *TinyMfa) GenerateSecretKeyForAlgorithm(algorithm HashAlgorithm) (*[]byte, error)

GenerateSecretKeyForAlgorithm generates a cryptographically random secret key with the recommended size for the specified hash algorithm. Key sizes follow the recommendation in RFC 6238 Section 4, which states that keys SHOULD be of the length of the HMAC output to facilitate interoperability.

  • SHA-1: 20 bytes (160 bits)
  • SHA-256: 32 bytes (256 bits)
  • SHA-512: 64 bytes (512 bits)

func (*TinyMfa) GenerateStandardSecretKey added in v0.3.0

func (tinymfa *TinyMfa) GenerateStandardSecretKey() (*[]byte, error)

GenerateStandardSecretKey returns a 20-byte secret key (SHA-1 recommended size).

func (*TinyMfa) GenerateSuperbSecretKey added in v0.4.0

func (tinymfa *TinyMfa) GenerateSuperbSecretKey() (*[]byte, error)

GenerateSuperbSecretKey returns a 64-byte secret key (SHA-512 recommended size).

func (*TinyMfa) GenerateToken added in v0.4.0

func (tinymfa *TinyMfa) GenerateToken(unixTimestamp int64, key *[]byte, offsetType uint8, tokenlength uint8, algorithm HashAlgorithm, timeStep int64, t0 int64) (int, error)

GenerateToken generates a TOTP token per RFC 6238 with configurable hash algorithm, time step, and epoch offset. This function implements the full TOTP generation pipeline:

  1. Compute time counter T (RFC 6238 Section 4.2)
  2. Convert T to 8-byte big-endian representation
  3. Compute HMAC using the selected algorithm (RFC 2104)
  4. Apply dynamic truncation (RFC 4226 Section 5.3)
  5. Reduce to the requested number of digits (RFC 4226 Section 5.4)

Supported token lengths are 5-8 digits. Supported algorithms are SHA1, SHA256, SHA512. RFC 6238 Section 4.2 recommends SHA-256 or SHA-512 for new deployments.

func (*TinyMfa) GetQRCodeConfig added in v0.3.0

func (tinymfa *TinyMfa) GetQRCodeConfig() structs.QrCodeConfig

GetQRCodeConfig returns the current QRCodeConfig for the QRCode.

func (*TinyMfa) SetQRCodeConfig added in v0.3.0

func (tinymfa *TinyMfa) SetQRCodeConfig(qrcodeConfig structs.QrCodeConfig)

SetQRCodeConfig sets the QRCodeConfig for the QRCode.

func (*TinyMfa) ValidateToken added in v0.3.0

func (tinymfa *TinyMfa) ValidateToken(token int, key *[]byte, unixTimestamp int64, tokenlength uint8, algorithm HashAlgorithm, timeStep int64, t0 int64) (bool, error)

ValidateToken validates a submitted TOTP token against present, past, and future time windows using the specified hash algorithm and time parameters. The validation checks three consecutive time steps to account for clock drift between client and server. RFC 6238 Section 5.2 recommends validation across a window of time steps.

func (*TinyMfa) ValidateTokenCurrentTimestamp added in v0.3.0

func (tinymfa *TinyMfa) ValidateTokenCurrentTimestamp(token int, key *[]byte, tokenlength uint8, algorithm HashAlgorithm, timeStep int64, t0 int64) Validation

ValidateTokenCurrentTimestamp validates a submitted TOTP token against the current Unix timestamp using the specified algorithm and time parameters. This is a convenience wrapper around ValidateToken that captures the current system time. RFC 6238 Section 5.2 defines the validation procedure.

func (*TinyMfa) ValidateTokenWithTimestamp added in v0.3.0

func (tinymfa *TinyMfa) ValidateTokenWithTimestamp(token int, key *[]byte, timestamp int64, tokenlength uint8, algorithm HashAlgorithm, timeStep int64, t0 int64) Validation

ValidateTokenWithTimestamp validates a submitted TOTP token against a provided Unix timestamp using the specified algorithm and time parameters. This is a convenience wrapper around ValidateToken that returns a Validation struct. RFC 6238 Section 5.2 defines the validation procedure.

func (*TinyMfa) WriteQrCodeImage added in v0.3.0

func (tinymfa *TinyMfa) WriteQrCodeImage(issuer, user string, secret *string, digits uint8, algorithm HashAlgorithm, timeStep int64, filePath string) error

WriteQrCodeImage writes a png to the filesystem with specified algorithm and timeStep

type TinyMfaInterface added in v0.3.0

type TinyMfaInterface interface {
	// GenerateStandardSecretKey returns a 20-byte secret key (SHA-1 recommended size).
	GenerateStandardSecretKey() (*[]byte, error)

	// GenerateExtendedSecretKey returns a 32-byte secret key (SHA-256 recommended size).
	GenerateExtendedSecretKey() (*[]byte, error)

	// GenerateSuperbSecretKey returns a 64-byte secret key (SHA-512 recommended size).
	GenerateSuperbSecretKey() (*[]byte, error)

	// GenerateSecretKey returns a secret key of the specified size.
	// Valid sizes are KeySizeSHA1 (20), KeySizeSHA256 (32), and KeySizeSHA512 (64).
	GenerateSecretKey(size int8) (*[]byte, error)

	// GenerateSecretKeyForAlgorithm generates a secret key with the recommended size
	// for the specified hash algorithm per RFC 6238 Section 4.
	GenerateSecretKeyForAlgorithm(algorithm HashAlgorithm) (*[]byte, error)

	// GenerateMessageBytes takes in a int64 number and turns it to a BigEndian byte array.
	GenerateMessageBytes(message int64) ([]byte, error)

	// CalculateHMAC calculates the HMAC value for a given message and key
	// using the specified hash algorithm (RFC 2104, RFC 6238 Section 1.2).
	CalculateHMAC(message []byte, key *[]byte, algorithm HashAlgorithm) ([]byte, error)

	// GenerateMessage computes the time counter T for TOTP using configurable
	// parameters per RFC 6238 Section 4.2.
	GenerateMessage(timestamp int64, offsetType uint8, timeStep int64, t0 int64) (int64, error)

	// GenerateToken generates a TOTP token per RFC 6238 with configurable hash algorithm,
	// time step, and epoch offset (RFC 6238 Section 4.2).
	GenerateToken(unixTimestamp int64, key *[]byte, offsetType uint8, tokenlength uint8, algorithm HashAlgorithm, timeStep int64, t0 int64) (int, error)

	// ValidateToken validates a submitted TOTP token with configurable algorithm
	// and time parameters per RFC 6238 Section 5.2.
	ValidateToken(token int, key *[]byte, unixTimestamp int64, tokenlength uint8, algorithm HashAlgorithm, timeStep int64, t0 int64) (bool, error)

	// ValidateTokenCurrentTimestamp validates a TOTP token against the current
	// Unix timestamp with configurable parameters (RFC 6238 Section 5.2).
	ValidateTokenCurrentTimestamp(token int, key *[]byte, tokenlength uint8, algorithm HashAlgorithm, timeStep int64, t0 int64) Validation

	// ValidateTokenWithTimestamp validates a TOTP token against a provided
	// Unix timestamp with configurable parameters (RFC 6238 Section 5.2).
	ValidateTokenWithTimestamp(token int, key *[]byte, timestamp int64, tokenlength uint8, algorithm HashAlgorithm, timeStep int64, t0 int64) Validation

	// GenerateQrCode generates a QRCode for the provided issuer, user and secret with specified algorithm and timeStep.
	GenerateQrCode(issuer, user string, secret *string, digits uint8, algorithm HashAlgorithm, timeStep int64) ([]byte, error)

	// ConvertColorSetting converts the ColorSetting struct into a color.Color object.
	ConvertColorSetting(setting structs.ColorSetting) color.Color

	// WriteQrCodeImage writes a QR code PNG to the filesystem with specified algorithm and timeStep.
	WriteQrCodeImage(issuer, user string, secret *string, digits uint8, algorithm HashAlgorithm, timeStep int64, filepath string) error

	// BuildPayload builds the otpauth:// URL payload for QR code generation with specified algorithm and timeStep.
	BuildPayload(issuer, username string, secret *string, digits uint8, algorithm HashAlgorithm, timeStep int64) string

	// SetQRCodeConfig sets the QRCodeConfig for the QRCode.
	SetQRCodeConfig(qrcodeConfig structs.QrCodeConfig)

	// GetQRCodeConfig returns the current QRCodeConfig for the QRCode.
	GetQRCodeConfig() structs.QrCodeConfig
}

func NewTinyMfa added in v0.3.0

func NewTinyMfa() TinyMfaInterface

type Validation added in v0.3.0

type Validation struct {
	Message int64
	Success bool
	Error   error
}

Validation is a struct used to return the result of a token validation

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL