Security-focused OTP library in Go. Full implementation of HOTP (RFC 4226) and TOTP (RFC 6238) plus advanced features: context binding, per-context replay isolation, and clock skew detection.
- ✅ Passes all RFC 4226 & RFC 6238 test vectors (SHA1/256/512)
- ✅ Replay protection + rate limiting with bounded memory
- ✅ Constant-time comparison to prevent timing attacks
- ✅ Context binding - OTP codes bound to (IP, device, session, origin)
- ✅ Coarse location context - region / geo bucket / distance class helpers
- ✅ Per-context replay isolation - code collisions between users don't block each other
- ✅ Anti-phishing origin binding - origin URL automatically normalized
- ✅ SecretProvider support - additive path for wrapped or externally-resolved secrets
- ✅ HMACProvider support - non-exportable key path for HSM-native OTP
- ✅ Explicit batch verification - additive per-item verify APIs for bulk workloads
- ✅ Clock skew detector with opt-in auto-adjust
- ✅ Compatible with Google Authenticator / Authy / Microsoft Authenticator (default mode)
- ✅ Comprehensive test coverage
go get github.com/robby031/genotp-gopackage main
import (
"fmt"
"github.com/robby031/genotp-go"
)
func main() {
secret, _ := genotp.CreateSecret()
code, _ := genotp.GenTotpDefault(secret)
valid, _ := genotp.VerifyTotpDefault(secret, code)
fmt.Printf("Code: %s, Valid: %v\n", code, valid)
}secret, _ := genotp.CreateSecret()
totp, _ := genotp.NewTotpBuilder().
Secret(secret).
Algorithm(genotp.SHA1).
Digits(6).
Period(30).
Build()
code, _ := totp.Generate(nil)
valid, _ := totp.Verify(code, nil, 1)uri := genotp.NewOtpAuthUri(genotp.TotpType, "ACME:alice@example.com", genotp.EncodeBase32(secret)).
Issuer("ACME").
Algorithm(genotp.SHA1).
Digits(6).
Period(30).
Build()
// Render `uri` to QR code (e.g., with a QR code library)accounts := []genotp.OtpAuthMigrationAccount{
{
Type: genotp.TotpType,
Label: "alice@example.com",
Issuer: "Example",
SecretB32: "JBSWY3DPEHPK3PXP",
Algorithm: genotp.SHA1,
Digits: 6,
Period: 30,
},
}
uri, err := genotp.BuildOtpAuthMigrationURI(accounts, &genotp.OtpAuthMigrationOptions{
Version: 1,
BatchSize: 1,
BatchIndex: 0,
BatchID: 123456,
})
if err != nil {
log.Fatal(err)
}
payload, err := genotp.ParseOtpAuthMigrationURI(uri)
if err != nil {
log.Fatal(err)
}
fmt.Println(payload.Accounts[0].Label) // alice@example.comFor comprehensive usage examples covering all features — including HOTP/TOTP, builder/config patterns, context binding, verifier, clock skew detection, metrics, and production recommendations — see
docs/usage.md.
For external key-manager integrations, see
docs/provider_adapters.md for adapter patterns
covering wrapped-secret and non-exportable HSM/KMS flows.
provider := genotp.SecretProviderFunc(func() ([]byte, error) {
// Example: unwrap from KMS or fetch from a secure external store.
return wrappedSecretResolver()
})
totp, err := genotp.NewTOTPFromSecretProvider(provider, genotp.SHA1, 6, 30)
if err != nil {
log.Fatal(err)
}This mode is additive and keeps the existing API intact. In this v1 design, the provider resolves secret material on demand for each OTP operation, then the temporary buffer is zeroed after use. It improves encrypted-at-rest and external-secret-manager workflows, while leaving room for a future non-exportable HSM-backed HMAC mode.
provider := genotp.HMACProviderFunc(func(algorithm genotp.Algorithm, message []byte) ([]byte, error) {
// Example: delegate to an HSM-native or remote HMAC service.
return hsmSign(algorithm, message)
})
hotp, err := genotp.NewHOTPFromHMACProvider(provider, genotp.SHA256, 6)
if err != nil {
log.Fatal(err)
}This path is intended for non-exportable OTP keys. The library sends only the counter/time-step message to the provider and receives the HMAC output back.
hotp, _ := genotp.NewHOTP(secret, genotp.SHA1, 6)
// Server binds code to (session + IP hash) of user at issue time:
ctx := genotp.NewOtpContextBuilder().
Session("login-abc123").
IP("hash_of_user_ip").
Build()
code, _ := hotp.GenBound(counter, ctx)
// Send `code` via any channel (SMS, email, WhatsApp, Telegram, push notif, ...).
// When user submits:
if hotp.VerifyBound(form.Code, counter, ctx) {
// ✓ code correct AND context matches
}
// Attacker who intercepts code from different IP/session -> automatically rejected.go test ./tests -run=^$ -bench=. -benchmem
goos: darwin
goarch: arm64
pkg: github.com/robby031/genotp-go/tests
cpu: Apple M4
BenchmarkHOTPGenerate-10 11958049 100.7 ns/op 8 B/op 1 allocs/op
BenchmarkHOTPVerify-10 12271438 98.93 ns/op 0 B/op 0 allocs/op
BenchmarkTOTPGenerate-10 9337573 132.1 ns/op 8 B/op 1 allocs/op
BenchmarkTOTPGenerateAtFixedTime-10 11920894 100.5 ns/op 8 B/op 1 allocs/op
BenchmarkTOTPVerify-10 4001732 303.8 ns/op 0 B/op 0 allocs/op
BenchmarkTOTPVerifyWindow0-10 12422841 98.22 ns/op 0 B/op 0 allocs/op
BenchmarkGenerateSecretDefault-10 6406161 185.3 ns/op 24 B/op 1 allocs/op
BenchmarkGenerateSecret256-10 6735511 179.3 ns/op 32 B/op 1 allocs/op
BenchmarkBase32Encode-10 61364334 18.57 ns/op 32 B/op 1 allocs/op
BenchmarkBase32Decode-10 24849778 49.38 ns/op 0 B/op 0 allocs/op
BenchmarkProvisioningURITOTP-10 3556134 331.0 ns/op 512 B/op 12 allocs/op
BenchmarkProvisioningURIHOTP-10 3594416 331.2 ns/op 512 B/op 12 allocs/op
BenchmarkReplayProtectionVerify-10 5728464 213.9 ns/op 76 B/op 1 allocs/op
BenchmarkRateLimiterContention-10 186141 6520 ns/op 112 B/op 5 allocs/op
BenchmarkConcurrentHOTPVerify-10 43528 27301 ns/op 208 B/op 5 allocs/op
BenchmarkTOTPVerifyParallel-10 71319079 16.08 ns/op 0 B/op 0 allocs/op
BenchmarkVerifierParallelFreshCodes-10 2907163 377.3 ns/op 76 B/op 1 allocs/op
PASS
ok github.com/robby031/genotp-go/tests 24.891s
- Generate and verify HMAC-based One-Time Passwords
- Look-ahead resynchronization for counter drift
- Context binding for enhanced security
- Time-based One-Time Passwords with configurable period
- Window-based verification for clock skew tolerance
- Support for SHA1, SHA256, and SHA512 algorithms
- Context binding and clock skew tracking
Bind OTP codes to specific contexts:
- IP address (or hash thereof)
- Device identifier
- Session ID
- Origin URL (anti-phishing)
- Region or geo bucket (coarse location context)
- Distance class (
same_area,nearby,far) - Custom fields
For production use, prefer coarse, application-defined location labels over raw latitude/longitude. This keeps OTP flows more stable across GPS jitter, platform differences, and privacy constraints.
Two modes:
- HMAC binding: Different contexts produce different OTP codes
- Verifier-stored: Standard OTP codes, but server validates context
Track and compensate for clock drift between client and server:
- Passive mode: only reports statistics
- Active mode: automatically adjusts verification window
- Recommends for window sizing or NTP sync
Prevent OTP code reuse with:
- Per-context replay isolation
- Configurable rate limiting
- Bounded memory usage
Algorithm: SHA1, SHA256, SHA512HOTP: HMAC-based OTP implementationTOTP: Time-based OTP implementationOtpContext: Context binding dataClockSkewDetector: Clock drift trackingVerifier: Replay protection and rate limiting (per-instance)ReplayStore: pluggable backend untuk replay-set (default = in-memory bounded + TTL; untuk multi-replica deployment implement dengan Redis / etcd / sql — lihatdocs/distributed_replay_protection.md)
CreateSecret(): Generate a random 160-bit secretGenHotpDefault(): Generate HOTP with default parametersGenTotpDefault(): Generate TOTP with default parametersVerifyHotpDefault(): Verify HOTP with default parametersVerifyTotpDefault(): Verify TOTP with default parametersEncodeBase32(data []byte) string: Encode bytes to Base32 (RFC 4648, no padding)DecodeBase32(dst []byte, src string) (int, error): Decode Base32 ke buffer caller. Strip ASCII whitespace,-, dan=otomatis. Mengembalikan jumlah byte yang ditulis. ReturnsErrDstTooSmalljikadstkekecilan,ErrInvalidSecretjika ada karakter invalid.
# Run all tests
go test ./tests
# Run tests with coverage
go test -v -race -coverprofile=coverage.out ./tests/
# Run fuzz tests (locally)
go test -fuzz=FuzzHOTPGenerate -fuzztime=1m ./fuzz/
go test -fuzz=FuzzTOTPVerify -fuzztime=1m ./fuzz/All RFC test vectors are included and verified:
- RFC 4226 HOTP test vectors
- RFC 6238 TOTP test vectors (SHA1, SHA256, SHA512)
The project uses GitHub Actions for automated testing on every push to main and pull requests:
CI Workflow includes:
- ✅ Tests - Run on Go 1.21, 1.22, and 1.23 with race detection
- ✅ Fuzz Tests - 30 seconds per fuzz target (10 fuzz functions total)
- ✅ Linting - golangci-lint with 15+ enabled linters
- ✅ Security Scan - gosec static analysis
- ✅ Build Verification - Ensures code compiles and go.mod is tidy
- ✅ Code Coverage - Uploaded to Codecov
See .github/workflows/ci.yml for the full configuration.
MIT — see LICENSE