Documentation
¶
Overview ¶
Package totp is a simple Go package to implement Timebased-One-Time-Password authentication functionality, a.k.a. `TOTP`, to the Go app.
Optionally, it supports ECDH key agreement protocol to share the same secret key between two parties.
Example ¶
package main import ( "fmt" "log" "github.com/KEINOS/go-totp/totp" ) func main() { Issuer := "Example.com" // name of the service AccountName := "alice@example.com" // name of the user // Generate a new secret key with default options. // Compatible with most TOTP authenticator apps. key, err := totp.GenerateKey(Issuer, AccountName) if err != nil { log.Fatal(err) } // Print the default option values. fmt.Println("- Algorithm:", key.Options.Algorithm) fmt.Println("- Period:", key.Options.Period) fmt.Println("- Secret Size:", key.Options.SecretSize) fmt.Println("- Skew (time tolerance):", key.Options.Skew) fmt.Println("- Digits:", key.Options.Digits) // Generate 6 digits passcode (valid for 30 seconds) passcode, err := key.PassCode() if err != nil { log.Fatal(err) } // Validate the passcode if key.Validate(passcode) { fmt.Println("* Validation result: Passcode is valid") } }
Output: - Algorithm: SHA1 - Period: 30 - Secret Size: 128 - Skew (time tolerance): 1 - Digits: 6 * Validation result: Passcode is valid
Example (Advanced) ¶
package main import ( "fmt" "log" "github.com/KEINOS/go-totp/totp" ) func main() { // Options to generate a new key. The secret will be generated randomly. opts := totp.Options{ Issuer: "Example.com", AccountName: "alice@example.com", Algorithm: totp.Algorithm("SHA1"), // Choices are: MD5, SHA1, SHA256 and SHA512 Period: 60, // Validity period in seconds SecretSize: 20, // Secret key size in bytes Skew: 0, // Number of periods before or after the current time to allow. Digits: totp.Digits(8), // Choices are: 6 and 8 } // Generate a new secret key key, err := totp.GenerateKeyCustom(opts) if err != nil { log.Fatal(err) } // Generate 8 digits passcode that are valid for 60 seconds (see options above) passcode, err := key.PassCode() if err != nil { log.Fatal(err) } // Validate the passcode if key.Validate(passcode) { fmt.Println("Passcode is valid") } }
Output: Passcode is valid
Example (Custom) ¶
package main import ( "fmt" "log" "github.com/KEINOS/go-totp/totp" ) func main() { // Generate a new secret key with custom options Issuer := "Example.com" AccountName := "alice@example.com" key, err := totp.GenerateKey(Issuer, AccountName, totp.WithAlgorithm(totp.Algorithm("SHA256")), totp.WithPeriod(15), totp.WithSecretSize(256), totp.WithSkew(5), totp.WithDigits(totp.DigitsEight), ) if err != nil { log.Fatal(err) } // Generate 8 digits passcode (valid for 15 ± 5 seconds) passcode, err := key.PassCode() if err != nil { log.Fatal(err) } // Validate the passcode if key.Validate(passcode) { fmt.Println("Passcode is valid") } }
Output: Passcode is valid
Example (Ecdh) ¶
This example demonstrates how to generate a new TOTP secret key from a shared secret of ECDH (Elliptic Curve Diffie-Hellman) key exchange.
Meaning that two parties can generate a common TOTP passcode by exchanging their ECDH public keys.
The aim of this functionality is to allow the user to generate a common but ephemeral secret value (the generated TOTP passcode) for additional security. Such as time-based salt for hashing, etc.
In this example, we pretend that Alice and Bob want to generate a common TOTP passcode for their communication. And they have exchanged their ECDH public keys securely with no-man-in-the-middle attack.
Let's see how Alice generates a common TOTP passcode between her and Bob!
//nolint:goconst // allow occurrences for readability package main import ( "crypto/ecdh" "crypto/rand" "fmt" "log" "github.com/KEINOS/go-totp/totp" ) // This example demonstrates how to generate a new TOTP secret key from a shared // secret of ECDH (Elliptic Curve Diffie-Hellman) key exchange. // // Meaning that two parties can generate a common TOTP passcode by exchanging // their ECDH public keys. // // The aim of this functionality is to allow the user to generate a common but // ephemeral secret value (the generated TOTP passcode) for additional security. // Such as time-based salt for hashing, etc. // // In this example, we pretend that Alice and Bob want to generate a common TOTP // passcode for their communication. And they have exchanged their ECDH public // keys securely with no-man-in-the-middle attack. // // Let's see how Alice generates a common TOTP passcode between her and Bob! // //nolint:exhaustruct,funlen // allow them for readability func main() { // ------------------------------------------------------------------------ // Pre-agreement between Alice and Bob. // ------------------------------------------------------------------------ // Both parties must use the same protocol (agreed options) so that the // same shared secret is created and use it to generate a same TOTP // passcode within the same time frame. // // The curve type. commonCurve := ecdh.X25519() // A consistent and common context between the two parties. It will be used // as a salt-like value for the TOTP secret key derivation. commonCtx := "example.com alice@example.com bob@example.com TOTP secret v1" // Common options for the TOTP secret key generation. commonOpts := totp.Options{ Algorithm: totp.Algorithm("SHA512"), // Algorithm for passcode generation Digits: totp.DigitsEight, // Number of digits for the passcode Period: 60 * 30, // Interval of the passcode validity SecretSize: 32, // Size of the secret key in bytes Skew: 1, // Number of periods as tolerance (+/-) } // ------------------------------------------------------------------------ // Key exchange between Alice and Bob. // ------------------------------------------------------------------------ // We pretend that Alice and Bob have exchanged their ECDH public keys // securely. In a real-world scenario, you must not expose your private // key to the public by any means. alicePriv, alicePub := testGetECDHKeysForAlice(commonCurve) bobPriv, bobPub := testGetECDHKeysForBob(commonCurve) // ------------------------------------------------------------------------ // Generate a new TOTP key for Alice // ------------------------------------------------------------------------ Issuer := "Example.com" // name of the service AccountName := "alice@example.com" // name of the user key, err := totp.GenerateKey(Issuer, AccountName, // Use the ECDH shared secret between Alice and Bob as the base of the // TOTP secret key. // // A common and consistent context is required. The size of the shared // ECDH secret is 32 bytes. The secret TOTP key is therefore generated // from the hash value of this shared secret and uses the shared context // up to the length of the "totp.WithSecretSize" option. // (FYI, the hash algorithm is BLAKE3). totp.WithECDH(alicePriv, bobPub, commonCtx), // Other options can be set as well. But they must be the same between // the two parties. totp.WithAlgorithm(commonOpts.Algorithm), totp.WithDigits(commonOpts.Digits), totp.WithPeriod(commonOpts.Period), totp.WithSecretSize(commonOpts.SecretSize), totp.WithSkew(commonOpts.Skew), ) if err != nil { log.Fatal(err) } // Alice generates 8 digits of TOTP passcode passcode, err := key.PassCode() if err != nil { log.Fatal(err) } // Bob validates the passcode result := letBobValidate(passcode, bobPriv, alicePub) fmt.Println(result) } //nolint:exhaustruct // field totp.Options.AccountName is missing but leave it as is for simplicity func letBobValidate(alicePasscode string, bobPriv *ecdh.PrivateKey, alicePub *ecdh.PublicKey) string { // Pre-agreement between Alice and Bob. commonCtx := "example.com alice@example.com bob@example.com TOTP secret v1" commonOpts := totp.Options{ Algorithm: totp.Algorithm("SHA512"), // Algorithm for passcode generation Digits: totp.DigitsEight, // Number of digits for the passcode Period: 60 * 30, // Interval of the passcode validity SecretSize: 32, // Size of the secret key in bytes Skew: 1, // Number of periods as tolerance (+/-) } // ------------------------------------------------------------------------ // Generate a new TOTP key for Bob // ------------------------------------------------------------------------ Issuer := "Example.com" AccountName := "bob@example.com" key, err := totp.GenerateKey(Issuer, AccountName, totp.WithECDH(bobPriv, alicePub, commonCtx), totp.WithPeriod(commonOpts.Period), totp.WithAlgorithm(commonOpts.Algorithm), totp.WithSecretSize(commonOpts.SecretSize), totp.WithSkew(commonOpts.Skew), totp.WithDigits(commonOpts.Digits), ) if err != nil { log.Fatal(err) } // Bob validates the passcode if key.Validate(alicePasscode) { return "* Validation result: Passcode is valid" } return "* Validation result: Passcode is invalid" } // This is a dummy function to return Alice's ECDH private key. // In a real-world scenario, you would generate this key securely. // // paramCommon is the curve type agreed between Alice and Bob. func testGetECDHKeysForAlice(paramCommon ecdh.Curve) (*ecdh.PrivateKey, *ecdh.PublicKey) { alicePriv, err := paramCommon.GenerateKey(rand.Reader) if err != nil { log.Fatal(err, "failed to generate Alice's ECDH private key for example") } return alicePriv, alicePriv.PublicKey() } // This is a dummy function to return Bob's ECDH public key. // In a real-world scenario, you would obtain this key securely. // // paramCommon is the curve type agreed between Alice and Bob. func testGetECDHKeysForBob(paramCommon ecdh.Curve) (*ecdh.PrivateKey, *ecdh.PublicKey) { bobPriv, err := paramCommon.GenerateKey(rand.Reader) if err != nil { log.Fatal(err, "failed to generate Alice's ECDH private key for example") } return bobPriv, bobPriv.PublicKey() }
Output: * Validation result: Passcode is valid
Index ¶
- Constants
- func StrToUint(number string) uint
- func Validate(passcode, secret string, options Options) bool
- func ValidateCustom(passcode, secret string, validationTime time.Time, options Options) bool
- type Algorithm
- type Digits
- type FixLevel
- type Key
- func GenKeyFromPEM(pemKey string) (*Key, error)
- func GenKeyFromURI(uri string) (*Key, error)
- func GenerateKey(issuer string, accountName string, opts ...Option) (*Key, error)
- func GenerateKeyCustom(options Options) (*Key, error)
- func GenerateKeyPEM(pemKey string) (*Key, error)deprecated
- func GenerateKeyURI(uri string) (*Key, error)deprecated
- func (k *Key) PEM() (string, error)
- func (k *Key) PassCode() (string, error)
- func (k *Key) PassCodeCustom(genTime time.Time) (string, error)
- func (k *Key) QRCode(fixLevel FixLevel) (*QRCode, error)
- func (k *Key) String() string
- func (k *Key) URI() string
- func (k *Key) Validate(passcode string) bool
- func (k *Key) ValidateCustom(passcode string, validationTime time.Time) bool
- type Option
- type Options
- type QRCode
- type Secret
- type URI
- func (u URI) AccountName() string
- func (u URI) Algorithm() string
- func (u URI) Check() error
- func (u URI) Digits() uint
- func (u URI) Host() string
- func (u URI) Issuer() string
- func (u URI) IssuerFromPath() string
- func (u URI) Path() string
- func (u URI) Period() uint
- func (u URI) Scheme() string
- func (u URI) Secret() Secret
- func (u URI) String() string
Examples ¶
Constants ¶
const ( // FixLevel30 is the highest level of error correction for QR codes, capable // of recovering 30% of the data. FixLevel30 = FixLevel(qr.H) // FixLevel25 is a qualified error correction level for QR codes, which can // recover 25% of the data. FixLevel25 = FixLevel(qr.Q) // FixLevel15 is a medium error correction level for QR codes, capable of // recovering 15% of the data. FixLevel15 = FixLevel(qr.M) // FixLevel7 is the lowest level of error correction for QR codes and can // recover 7% of the data. FixLevel7 = FixLevel(qr.L) // FixLevelDefault is the default error correction level for QR codes. // Currently set to FixLevel15. FixLevelDefault = FixLevel15 )
Error correction level for QR code.
const ( OptionAlgorithmDefault = Algorithm("SHA1") // Google Authenticator does not work other than SHA1. OptionDigitsDefault = Digits(6) // Google Authenticator does not work other than 6 digits. OptionPeriodDefault = uint(30) // 30 seconds is recommended in RFC-6238. OptionSecretSizeDefault = uint(128) // 128 Bytes. OptionSkewDefault = uint(1) // ± 1 period of tolerance. )
Constants for the default values of the options.
const BlockTypeTOTP = "TOTP SECRET KEY"
BlockTypeTOTP is the type of a PEM encoded data block.
Variables ¶
This section is empty.
Functions ¶
func StrToUint ¶
StrToUint converts a string to an unsigned integer. If the string is not a valid integer or out of range of int32, it returns 0.
Example ¶
package main import ( "fmt" "strconv" "github.com/KEINOS/go-totp/totp" ) func main() { str1 := "1234567890" uint1 := totp.StrToUint(str1) fmt.Printf("uint1: %v, type: %T\n", uint1, uint1) // Note that number that overflows the uint will return 0. str2 := strconv.FormatUint(uint64(0xFFFFFFFF+1), 10) uint2 := totp.StrToUint(str2) fmt.Printf("uint2: %v, type: %T\n", uint2, uint2) }
Output: uint1: 1234567890, type: uint uint2: 0, type: uint
func Validate ¶
Validate returns true if the given passcode is valid for the secret and options at the current time.
The passcode should be a string of 6 or 8 digit number and the secret should be a base32 encoded string.
Usually, Key.Validate() method is used to validate the passcode. Use this function if you have the values and simply want to validate the passcode.
Example ¶
Validate function is a short hand of totp.Key.Validate() functionality.
package main import ( "fmt" "log" "github.com/KEINOS/go-totp/totp" ) func main() { // Create a new Key object via URI to obtain the current passcode. uri := "otpauth://totp/Example.com:alice@example.com?algorithm=SHA1&" + "digits=12&issuer=Example.com&period=60&secret=QF7N673VMVHYWATKICRUA7V5MUGFG3Z3" key, err := totp.GenKeyFromURI(uri) if err != nil { log.Fatal(err) } // Get values needed for the function arguments. options := key.Options secret := key.Secret.Base32() passcode, err := key.PassCode() if err != nil { log.Fatal(err) } // Validate the passcode via Key.Validate() method. if key.Validate(passcode) { fmt.Println("Passcode is valid. Checked via Key.Validate() method.") } // Validate the passcode via Validate() function. if totp.Validate(passcode, secret, options) { fmt.Println("Passcode is valid. Checked via Validate() function.") } }
Output: Passcode is valid. Checked via Key.Validate() method. Passcode is valid. Checked via Validate() function.
Types ¶
type Algorithm ¶
type Algorithm string
Algorithm is a string that represents the algorithm used for HMAC.
Example ¶
package main import ( "fmt" "log" "github.com/KEINOS/go-totp/totp" ) func main() { // Create a new Algorithm object from a string. Choices are: // MD5, SHA1, SHA256 and SHA512. algo, err := totp.NewAlgorithmStr("SHA512") if err != nil { log.Fatal(err) } fmt.Println("Algorithm:", algo.String()) fmt.Println("Algorithm ID:", algo.ID()) fmt.Printf("Type: %T\n", algo.OTPAlgorithm()) }
Output: Algorithm: SHA512 Algorithm ID: 2 Type: otp.Algorithm
func NewAlgorithmID ¶
NewAlgorithmID creates a new Algorithm object from an int.
func NewAlgorithmStr ¶
NewAlgorithmStr creates a new Algorithm object from a string. Choices of algo are: MD5, SHA1, SHA256 and SHA512.
func (Algorithm) ID ¶
ID returns the ID of the algorithm which is the same int value as the original OTP library.
Undefined ID will always return 2 (SHA512).
func (Algorithm) IsSupported ¶
IsSupported returns true if the algorithm is supported.
Example ¶
package main import ( "fmt" "github.com/KEINOS/go-totp/totp" ) func main() { // Set unsupported algorithm algo := totp.Algorithm("BLAKE3") // Check if the algorithm is supported if algo.IsSupported() { fmt.Println("Algorithm is supported") } else { fmt.Println("Algorithm is not supported") } }
Output: Algorithm is not supported
func (Algorithm) OTPAlgorithm ¶
OTPAlgorithm is similar to ID() but returns in the original type of the OTP library.
Undefined Algorithm type will always return `otp.AlgorithmSHA512`.
type Digits ¶
type Digits uint
Digits represents the number of digits present in the user's OTP passcode. Six and Eight are the most common values.
Example ¶
package main import ( "fmt" "github.com/KEINOS/go-totp/totp" ) func main() { // Create a new Digits object from a number. Choices are: // 6 and 8. digits := totp.NewDigitsInt(8) fmt.Println("Digits:", digits) fmt.Println("Digits ID:", digits.OTPDigits()) // DigitsEight is equivalent to NewDigits(8) if totp.DigitsEight == totp.NewDigitsInt(8) { fmt.Println("Digit 8", "OK") } // DigitsSix is equivalent to NewDigits(6) if totp.DigitsSix == totp.NewDigitsInt(6) { fmt.Println("Digit 6", "OK") } }
Output: Digits: 8 Digits ID: 8 Digit 8 OK Digit 6 OK
func NewDigitsInt ¶
NewDigitsInt returns a new Digits object from the given value.
func NewDigitsStr ¶
NewDigitsStr returns a new Digits object from the given string in decimal format.
type FixLevel ¶
type FixLevel byte
FixLevel is the error correction level for QR code. Use `FixLevel*` constants to set the level.
type Key ¶
Key is a struct that holds the TOTP secret and its options.
Example ¶
package main import ( "fmt" "log" "github.com/KEINOS/go-totp/totp" ) func main() { // Generate a new secret key with default options. Issuer := "Example.com" AccountName := "alice@example.com" key, err := totp.GenerateKey(Issuer, AccountName) if err != nil { log.Fatal(err) } // Generate 6 digits passcode (valid for 30 seconds) // For generating a passcode for a custom time, use PassCodeCustom() method. passCode, err := key.PassCode() if err != nil { log.Fatal(err) } // Validate the passcode if key.Validate(passCode) { fmt.Println("Given passcode is valid") } }
Output: Given passcode is valid
Example (Regenerate) ¶
In this example, we will re-generate/recover a new Key object from a backed-up secret key value.
The point to recover the Key object is simply to overwrite the secret key value with the backed-up value.
package main import ( "fmt" "log" "github.com/KEINOS/go-totp/totp" ) func main() { // The backed-up secret key value (in case of Base32 encoded) oldSecret := "QF7N673VMVHYWATKICRUA7V5MUGFG3Z3" // Step1: Generate a brand new Key object Issuer := "Example.com" AccountName := "alice@example.com" key, err := totp.GenerateKey(Issuer, AccountName) if err != nil { log.Fatal(err) } // Step2: Cast the backed-up secret key value to a Secret object newSecret, err := totp.NewSecretBase32(oldSecret) if err != nil { log.Fatal(err) } // Step3: Ensure the secret key size is the same as the new key object key.Options.SecretSize = uint(len(newSecret.Bytes())) // Step4: Overwrite the secret key value with the backed-up value key.Secret = newSecret // Step5: Backup the TOTP key object in PEM format this time keyPEM, err := key.PEM() if err != nil { log.Fatal(err) } fmt.Println(keyPEM) // Save this data }
Output: -----BEGIN TOTP SECRET KEY----- Account Name: alice@example.com Algorithm: SHA1 Digits: 6 Issuer: Example.com Period: 30 Secret Size: 20 Skew: 1 gX7ff3VlT4sCakCjQH69ZQxTbzs= -----END TOTP SECRET KEY-----
func GenKeyFromPEM ¶ added in v0.1.1
GenKeyFromPEM creates a new Key object from a PEM formatted string.
Example ¶
package main import ( "fmt" "log" "github.com/KEINOS/go-totp/totp" ) func main() { pemData := ` -----BEGIN TOTP SECRET KEY----- Account Name: alice@example.com Algorithm: SHA1 Digits: 8 Issuer: Example.com Period: 30 Secret Size: 64 Skew: 1 gX7ff3VlT4sCakCjQH69ZQxTbzs= -----END TOTP SECRET KEY-----` key, err := totp.GenKeyFromPEM(pemData) if err != nil { log.Fatal(err) } fmt.Println("AccountName:", key.Options.AccountName) fmt.Println("Algorithm:", key.Options.Algorithm) fmt.Println("Digits:", key.Options.Digits) fmt.Println("Issuer:", key.Options.Issuer) fmt.Println("Period:", key.Options.Period) fmt.Println("Secret Size:", key.Options.SecretSize) fmt.Println("Skew:", key.Options.Skew) fmt.Println("Secret:", key.Secret.Base32()) }
Output: AccountName: alice@example.com Algorithm: SHA1 Digits: 8 Issuer: Example.com Period: 30 Secret Size: 64 Skew: 1 Secret: QF7N673VMVHYWATKICRUA7V5MUGFG3Z3
func GenKeyFromURI ¶ added in v0.2.0
GenKeyFromURI creates a new Key object from an TOTP uri/url. The URL format is documented here:
https://github.com/google/google-authenticator/wiki/Key-Uri-Format
Example ¶
package main import ( "fmt" "log" "github.com/KEINOS/go-totp/totp" ) func main() { origin := "otpauth://totp/Example.com:alice@example.com?algorithm=SHA1&" + "digits=12&issuer=Example.com&period=60&secret=QF7N673VMVHYWATKICRUA7V5MUGFG3Z3" key, err := totp.GenKeyFromURI(origin) if err != nil { log.Fatal(err) } fmt.Println("Issuer:", key.Options.Issuer) fmt.Println("AccountName:", key.Options.AccountName) fmt.Println("Algorithm:", key.Options.Algorithm) fmt.Println("Digits:", key.Options.Digits) fmt.Println("Period:", key.Options.Period) fmt.Println("Secret Size:", key.Options.SecretSize) fmt.Println("Secret:", key.Secret.String()) }
Output: Issuer: Example.com AccountName: alice@example.com Algorithm: SHA1 Digits: 12 Period: 60 Secret Size: 20 Secret: QF7N673VMVHYWATKICRUA7V5MUGFG3Z3
func GenerateKey ¶
GenerateKey creates a new Key object with default options. Which is:
SHA-512 hash for HMAC, 30 seconds of period, 64 byte size of secret and 6 digits of passcode.
To customize the options, use the With* functions from the options.go file. For advanced customization, use GenerateKeyCustom() instead.
func GenerateKeyCustom ¶
GenerateKeyCustom creates a new Key object with custom options.
Usually, `GenerateKey` with options is enough for most cases. But if you need more control over the options, use this function.
func GenerateKeyPEM
deprecated
GenerateKeyPEM creates a new Key object from a PEM formatted string.
Deprecated: Use GenKeyFromPEM() instead. This function will be removed in the next major release. Currently it is an alias to GenKeyFromPEM(). For more information, see: https://github.com/KEINOS/go-totp/issues/14
func GenerateKeyURI
deprecated
GenerateKeyURI creates a new Key object from an TOTP uri/url.
Deprecated: Use GenKeyFromURI() instead. This function will be removed in the next major release. Currently it is an alias to GenKeyFromURI(). For more information, see: https://github.com/KEINOS/go-totp/issues/14
func (*Key) PEM ¶
PEM returns the key in PEM formatted string.
Example ¶
package main import ( "fmt" "log" "github.com/KEINOS/go-totp/totp" ) func main() { origin := "otpauth://totp/Example.com:alice@example.com?algorithm=SHA1&" + "digits=6&issuer=Example.com&period=30&secret=QF7N673VMVHYWATKICRUA7V5MUGFG3Z3" key, err := totp.GenKeyFromURI(origin) if err != nil { log.Fatal(err) } keyPEM, err := key.PEM() if err != nil { log.Fatal(err) } fmt.Println(keyPEM) }
Output: -----BEGIN TOTP SECRET KEY----- Account Name: alice@example.com Algorithm: SHA1 Digits: 6 Issuer: Example.com Period: 30 Secret Size: 20 Skew: 1 gX7ff3VlT4sCakCjQH69ZQxTbzs= -----END TOTP SECRET KEY-----
func (*Key) PassCode ¶
PassCode generates a 6 or 8 digits passcode for the current time. The output string will be eg. "123456" or "12345678".
Example ¶
package main import ( "fmt" "log" "time" "github.com/KEINOS/go-totp/totp" ) func main() { // Generate a new secret key Issuer := "Example.com" AccountName := "alice@example.com" key, err := totp.GenerateKey(Issuer, AccountName) if err != nil { log.Fatal(err) } // Generate 6 digits passcode (valid for 30 seconds) code, err := key.PassCode() if err != nil { log.Fatal(err) } // Validate the passcode if key.Validate(code) { fmt.Println("Passcode is valid with current time") } // Validate the passcode with a custom time validationTime := time.Now().Add(-300 * time.Second) if key.ValidateCustom(code, validationTime) { fmt.Println("Passcode is valid with custom time") } else { fmt.Println("Passcode is invalid with custom time") } }
Output: Passcode is valid with current time Passcode is invalid with custom time
func (*Key) PassCodeCustom ¶ added in v0.2.0
PassCodeCustom is similar to PassCode() but allows you to specify the time to generate the passcode.
Example ¶
package main import ( "fmt" "log" "time" "github.com/KEINOS/go-totp/totp" ) func main() { // Generate a new secret key Issuer := "Example.com" AccountName := "alice@example.com" key, err := totp.GenerateKey(Issuer, AccountName) if err != nil { log.Fatal(err) } timeNow := time.Now() // Generate a passcode for a specific time (300 seconds ago) code, err := key.PassCodeCustom(timeNow.Add(-300 * time.Second)) if err != nil { log.Fatal(err) } // Validating with the current time should fail if key.Validate(code) { fmt.Println("Passcode is valid with current time") } else { fmt.Println("Passcode is invalid with current time") } // To validate a passcode for a specific time, use ValidateCustom() // method. validationTime := timeNow.Add(-300 * time.Second) if key.ValidateCustom(code, validationTime) { fmt.Println("Passcode is valid with custom time") } else { fmt.Println("Passcode is invalid with custom time") } }
Output: Passcode is invalid with current time Passcode is valid with custom time
func (*Key) QRCode ¶
QRCode returns a QR code image of a specified width and height, suitable for registering a user's TOTP URI with many clients, such as Google-Authenticator.
Example ¶
package main import ( "encoding/hex" "fmt" "log" "github.com/KEINOS/go-totp/totp" ) func main() { origin := "otpauth://totp/Example.com:alice@example.com?algorithm=SHA1&" + "digits=6&issuer=Example.com&period=30&secret=QF7N673VMVHYWATKICRUA7V5MUGFG3Z3" // Create a new Key object from a URI key, err := totp.GenKeyFromURI(origin) if err != nil { log.Fatal(err) } // Create QRCode object imgQRCode, err := key.QRCode(totp.FixLevelDefault) if err != nil { log.Fatal(err) } // Get PNG image in bytes pngImage, err := imgQRCode.PNG(100, 100) if err != nil { log.Fatal(err) } actual := hex.EncodeToString(pngImage) expect := `89504e470d0a1a0a0000000d4948445200000064000000641000000000051` + `916cb0000040549444154789ce45ced6edb400c9387bcff2b67180a2f324d5272ff` + `95d59f35f7691f4f12a9047bbddf1561afaae3d0dde76bf631bdeddfdffd5f370fd` + `7c5f99b7df473fef1eff973ecf5f50fbb60ec74b01dfbce93eba7cce6633fae778e` + `617dfc39d310a90101e503bdbff7f55357779ba1727e763ec69f231191c9f094ce1` + `3c5c88363b17f4217db7709e23722c20c917077daa1721aa2be4fd789886cdebe9f` + `6e3796a927b4703f8c722ad7f0e74c43c4f198d3d489bb2c5dc28fa6f90a19fd9c4` + `9883c21f2d33d7ec68fb83994b4c52072bcdf8e1339edd1e76c4f7d42df4536bf5e` + `1a2235a8b6291f4c116c52932c32b13d35a28988d4a033f054a6d355e3e5a318e49` + `53f7eda9210f9ffa7f095d3143762f3a7317d4d75f2ac4d71b83c443668a87c33f1` + `afefa08aa67ceef339061150882ebef77665937e983489da57a1f1b12444b675277` + `7f771ceac1f7ccd6bc316aef5b32444746cf6a75ea6eac1906111ab4424ab07dc2f` + `2f6a3da9064eda5ef988d335389fadc9d6ba5a0c22c7f5fd9c9ea8413b383e44b71` + `e1057be837d79510b4dc56f15a9b67fe33ab8966b57d59bcffa31881085e878ce94` + `6f9efa85426ce387578b41c47c3fc232b8d21e7d8eb29e5f7a5b01b25b34aee8262` + `1a2388dd3112ab239a5c8d639cd2947e74b15f93dfbe6edcb54fe948a448ec4d6c1` + `f570bee263773f4942a408b364a7dfc74cd590d3b61a67529fb8ce7dbf2444d4292` + `8fea3fa3677ba067ee5f290afac2421b2b9df657247895f3738fec5c6d7c0a7fc33` + `2621c24cc575e507cecfd867c59291b7b95ac115bd34442625876dea2ebb0c8fa7e` + `b7899aa9c689f4a4264d20865eeee5431615ac5b16336cead9b19b55cd564527c4e` + `6fa87bddc72aff62151b857a586637954616955c1564f22d77baca7fdc3e95f93df` + `be02365f4b33aedd354b545b5b1bdd83adc921029f1d6ea2ebbaa8932a5f19d8264` + `63b5928d4184fc824e2941759fb7999d459f890de33e257d2c0691d7acbd550c67a` + `69800e366ee84fbbc69cf2f4b42a408ffaf21533b1dcea2df661eeea9d6e53e1983` + `c8717f371769b0ed0903c6cf133a684abf87b15f52fb75bc0bc73afdcdc697d1e3b` + `8a68aa01c993444ca5416ddbd76f7d621d7f798b4067e663e1c94d9e157a6b560a4` + `45eeaa53747d5d954fb62c5a5b1222b31edee9f05a565ea63536ebe0b83c1fb1430` + `ca3ddf84e5fc75544702fa791ea7653621059fc9f0f4f3484e24f8a2594f9b6d865` + `f5fbb82444eac1f77c5d13e058cf4e791f8e719ccd7b731a22356873773fd538869` + `ccb190e15c5bb2a568f4ca62257b72d3b9ea2e471785fc1bd33a3d664131a38ae8c` + `26a9bafb113b75c584d197c27ca4d57e6f5ddfc80d6abc9a37712dd6c77d330d116` + `51839d4bd773ae3b29dc8d893f6708ab2b2aaf10b3df233ec6f000000ffffa0404e` + `fb1e0ab59a0000000049454e44ae426082` // Assert equal image if expect == actual { fmt.Println("OK") } }
Output: OK
func (*Key) String ¶ added in v0.2.0
String returns a string representation of the key in URI format.
It is an implementation of the fmt.Stringer interface.
Example ¶
package main import ( "fmt" "log" "github.com/KEINOS/go-totp/totp" ) func main() { origin := ` -----BEGIN TOTP SECRET KEY----- Account Name: alice@example.com Algorithm: SHA1 Digits: 12 Issuer: Example.com Period: 60 Secret Size: 20 Skew: 0 gX7ff3VlT4sCakCjQH69ZQxTbzs= -----END TOTP SECRET KEY----- ` key, err := totp.GenKeyFromPEM(origin) if err != nil { log.Fatal(err) } expect := "otpauth://totp/Example.com:alice@example.com?algorithm=SHA1&" + "digits=12&issuer=Example.com&period=60&secret=QF7N673VMVHYWATKICRUA7V5MUGFG3Z3" actual := key.String() if expect == actual { fmt.Println("URI returned as expected") } }
Output: URI returned as expected
func (*Key) URI ¶
URI returns the key in OTP URI format.
It re-generates the URI from the values stored in the Key object and will not use the original URI.
Example ¶
package main import ( "fmt" "log" "github.com/KEINOS/go-totp/totp" ) func main() { origin := "otpauth://totp/Example.com:alice@example.com?algorithm=SHA1&" + "digits=12&issuer=Example.com&period=60&secret=QF7N673VMVHYWATKICRUA7V5MUGFG3Z3" key, err := totp.GenKeyFromURI(origin) if err != nil { log.Fatal(err) } expect := origin actual := key.URI() // regenerate URI if expect == actual { fmt.Println("URI returned as expected") } }
Output: URI returned as expected
type Option ¶ added in v0.2.0
Option is a function that applies an option to the given Options. It should return an error if the option is nil or invalid.
func WithAlgorithm ¶ added in v0.2.0
WithAlgorithm sets the Algorithm to use for HMAC (Default: Algorithm("SHA512")).
func WithDigits ¶ added in v0.2.0
WithDigits sets the Digits to request TOTP code. Choices are DigitsSix or DigitsEight (Default: DigitsSix).
func WithECDH ¶ added in v0.2.0
WithECDH sets a hashed ECDH shared secret as the TOTP secret from the given ECDH private key and the correspondent's ECDH public key.
Important:
Both ECDH keys must be generated from the same curve type.
context is required as a consistent string between the two parties.
Both parties must use the same context to generate the same shared secret. The context string can be anything, but it must be consistent between the two parties. The recommended format is "[issuer] [sorted account names] [purpose] [version]".
e.g.) "example.com alice@example.com bob@example.com TOTP secret v1"
func WithPeriod ¶ added in v0.2.0
WithPeriod sets the number of seconds a TOTP hash is valid for (Default: 30 seconds).
func WithSecretSize ¶ added in v0.2.0
WithSecretSize sets the size of the generated Secret (Default: 128 bytes).
type Options ¶
type Options struct { // AccountName is the name of the secret key owner. (eg, email address) AccountName string // Algorithm to use for HMAC to generate the TOTP passcode. // (Default: Algorithm("SHA1")) // // Note that this is not the same hash algorithm used for the secret key // generated via ECDH. Algorithm Algorithm // Digits to request TOTP code. DigitsSix or DigitsEight. (Default: DigitsSix) Digits Digits // Issuer is the name of the issuer of the secret key. // (eg, organization, company, domain) Issuer string // Period is the number of seconds a TOTP hash is valid for. // (Default: 30 seconds) Period uint // SecretSize is the size of the generated Secret. (Default: 128 bytes) SecretSize uint // Skew is the periods before or after the current time to allow. (Default: 1) // // Value of 1 allows up to Period of either side of the specified time. // Values greater than 1 are likely sketchy. Skew uint // contains filtered or unexported fields }
Options is a struct that holds the options for a TOTP key. Use SetDefault() to set the default values.
Example ¶
package main import ( "fmt" "github.com/KEINOS/go-totp/totp" ) func main() { options := totp.Options{ Issuer: "Example.com", AccountName: "alice@example.com", } options.SetDefault() /* List all option and their default values. */ // Issuer is the name of the service who issued the secret. fmt.Println("Issuer:", options.Issuer) // Name of the owner of the secret key. fmt.Println("AccountName:", options.AccountName) // Hash algorithm to generate the passcode as HMAC. fmt.Println("Algorithm:", options.Algorithm) // Length of the passcode. fmt.Println("Digits:", options.Digits) // Valid seconds of passcode issued. fmt.Println("Period:", options.Period) // Size of the secret key in bytes. fmt.Println("Secret Size:", options.SecretSize) // Skew is an acceptable range of time before and after. Value of 1 allows // up to Period of either side of the specified time. fmt.Println("Skew:", options.Skew) }
Output: Issuer: Example.com AccountName: alice@example.com Algorithm: SHA1 Digits: 6 Period: 30 Secret Size: 128 Skew: 1
func NewOptions ¶
NewOptions returns a new Options struct with the default values. Issuer and AccountName are required.
Example ¶
package main import ( "fmt" "log" "github.com/KEINOS/go-totp/totp" ) func main() { // Create a new Options object with default values. opt1, err := totp.NewOptions("Example.com", "alice@example.com") if err != nil { log.Fatal(err) } // For default values, see the example of Options type. fmt.Printf("Type: %T\n", opt1) fmt.Printf("Issuer: %s\n", opt1.Issuer) fmt.Printf("Account Name: %s\n", opt1.AccountName) // Issuer and Account Name are required. opt2, err := totp.NewOptions("", "") // Assert error if err != nil { fmt.Println("Error msg:", err.Error()) } // Assert nil on error if opt2 != nil { log.Fatal("NewOptions() should return nil on error") } }
Output: Type: *totp.Options Issuer: Example.com Account Name: alice@example.com Error msg: issuer and accountName are required
func (*Options) SetDefault ¶
func (opts *Options) SetDefault()
SetDefault sets the undefined options to its default value.
type QRCode ¶
type QRCode struct { URI URI // URI object to be encoded to QR code image. Level FixLevel // Level is the error correction level for the QR code. }
QRCode is a struct that holds the information to create QR code image.
type Secret ¶
type Secret []byte
Secret is a byte slice that represents a secret key.
Example ¶
package main import ( "fmt" "log" "github.com/KEINOS/go-totp/totp" ) func main() { // The below two lines are the same but with different base-encodings. base32Secret := "MZXW6IDCMFZCAYTVPJ5A" base62Secret := "FegjEGvm7g03GQye" // Instantiate a new Secret object from a base32 encoded string. secret32, err := totp.NewSecretBase32(base32Secret) if err != nil { log.Fatal(err) } // Instantiate a new Secret object from a base62 encoded string. secret62, err := totp.NewSecretBase62(base62Secret) if err != nil { log.Fatal(err) } // Once instantiated, you can use the Secret object to get the secret in // different base-encodings. fmt.Println("Get as base62 encoded string:", secret32.Base62()) fmt.Println("Get as base32 encoded string:", secret62.Base32()) // String() method is equivalent to Base32() if secret62.String() == secret62.Base32() { fmt.Println("String() is equivalent to Base32()") } if secret32.String() == secret62.String() { fmt.Println("Two secrets are the same.") } }
Output: Get as base62 encoded string: FegjEGvm7g03GQye Get as base32 encoded string: MZXW6IDCMFZCAYTVPJ5A String() is equivalent to Base32() Two secrets are the same.
func NewSecretBase32 ¶
NewSecretBase32 creates a new Secret object from a base32 encoded string.
func NewSecretBase62 ¶
NewSecretBase62 creates a new Secret object from a base62 encoded string.
func NewSecretBytes ¶
NewSecretBytes creates a new Secret object from a byte slice.
Example ¶
package main import ( "fmt" "github.com/KEINOS/go-totp/totp" ) func main() { data := []byte("some secret") // Generate a new Secret object from a byte slice. secret := totp.NewSecretBytes(data) fmt.Printf("Type: %T\n", secret) fmt.Printf("Value: %#v\n", secret) fmt.Println("Secret bytes:", secret.Bytes()) fmt.Println("Secret string:", secret.String()) fmt.Println("Secret Base32:", secret.Base32()) fmt.Println("Secret Base62:", secret.Base62()) }
Output: Type: totp.Secret Value: totp.Secret{0x73, 0x6f, 0x6d, 0x65, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74} Secret bytes: [115 111 109 101 32 115 101 99 114 101 116] Secret string: ONXW2ZJAONSWG4TFOQ Secret Base32: ONXW2ZJAONSWG4TFOQ Secret Base62: bfF9D3ygDyVQZp2
func (Secret) Base32 ¶
Base32 returns the secret as a base32 encoded string. Which is the standard format used by TOTP URIs.
type URI ¶
type URI string
URI is a string that holds the TOTP URI.
All methods are calculated each time they are called. Therefore, it is recommended to store them. Note also that the values are not validated. For example, `Digits()` method may return any value.
Example ¶
package main import ( "fmt" "log" "github.com/KEINOS/go-totp/totp" ) func main() { origin := "otpauth://totp/Example.com:alice@example.com?algorithm=SHA1&" + "digits=12&issuer=Example.com&period=60&secret=QF7N673VMVHYWATKICRUA7V5MUGFG3Z3" uri := totp.URI(origin) // Check if the URI is correctly formatted with the required fields. if err := uri.Check(); err != nil { log.Fatal(err) } if uri.String() == origin { fmt.Println("Raw URI and String is equal: OK") } fmt.Println("Scheme:", uri.Scheme()) fmt.Println("Host:", uri.Host()) fmt.Println("Issuer:", uri.Issuer()) fmt.Println("Account Name:", uri.AccountName()) fmt.Println("Algorithm:", uri.Algorithm()) fmt.Println("Secret:", uri.Secret().String()) fmt.Println("Period:", uri.Period()) fmt.Println("Digits:", uri.Digits()) }
Output: Raw URI and String is equal: OK Scheme: otpauth Host: totp Issuer: Example.com Account Name: alice@example.com Algorithm: SHA1 Secret: QF7N673VMVHYWATKICRUA7V5MUGFG3Z3 Period: 60 Digits: 12
func NewURI ¶
NewURI returns a new URI object. It simply returns the casted string as a URI object. To validate if the URI is correctly formatted, use the Check() method.
func (URI) AccountName ¶
AccountName returns the account name from the URI.
func (URI) Check ¶
Check returns true if the URI is correctly formatted and required fields are set.
func (URI) Issuer ¶
Issuer returns the issuer from the URI. Similar to IssuerFromPath() but returns the issuer from the query string instead of the path.
func (URI) IssuerFromPath ¶
IssuerFromPath returns the issuer from the URI. Similar to Issuer() but returns the issuer from the path instead of the query string.
Example ¶
package main import ( "fmt" "github.com/KEINOS/go-totp/totp" ) func main() { origin := "otpauth://totp/Example.com:alice@example.com?issuer=Wrong.com" uri := totp.URI(origin) fmt.Println(uri.IssuerFromPath()) }
Output: Example.com
func (URI) Path ¶
Path returns the path from the URI. Which is used as a "label" for the TOTP. See:
https://github.com/google/google-authenticator/wiki/Key-Uri-Format#label
func (URI) Period ¶
Period returns the number of seconds a TOTP hash is valid for from the URI. If the period is not set or the URL is invalid, it returns 0.