TOTP codes, an encrypted secret envelope, and recovery codes for Go services.
- RFC 6238 TOTP with HMAC-SHA-1, six digits, and a 30-second period.
- A drift window of one step in each direction, with a replay guard.
- An AES-256-GCM envelope with a version byte, a key ID, a random nonce, and an AAD binding to the user ID.
- Ten recovery codes with 16 random bytes each. Store only the SHA-256 hashes.
go get github.com/m1chlcz/mfakit
| Item | Value |
|---|---|
| Secret | 20 bytes |
| Encryption key | 32 bytes |
| Code | 6 digits |
| Period | 30 seconds |
| Drift | one step in each direction |
| Recovery codes | 10 |
| Recovery code | 16 bytes |
secret, err := mfakit.NewSecret(rand.Reader)
envelope, err := mfakit.EncryptSecret(keyID, key, userID, secret)
secret, err = mfakit.DecryptSecret(keys, userID, envelope)
code := mfakit.TOTPCode(secret, time.Now().Unix()/mfakit.PeriodSeconds)
step, ok := mfakit.MatchTOTPStep(secret, input, time.Now(), lastUsed)
codes, hashes, err := mfakit.NewRecoveryCodes(rand.Reader)
canonical, err := mfakit.CanonicalRecoveryCode(input)- Generate the encryption key with a random source. Keep it in a secret manager.
- Rotate a key with a new ID.
DecryptSecretreads the ID from the envelope. - Store only the recovery hashes. Compare a canonical code with
subtle.ConstantTimeCompare. - Store the matched step and pass it as
lastUsedon the next check. The package cannot store it for you. - The package does not log secrets and does not send data anywhere.
go test -race ./...
MIT. See LICENSE.