totp

package
v0.3.2 Latest Latest
Warning

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

Go to latest
Published: Aug 12, 2025 License: MIT Imports: 20 Imported by: 0

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.

```shellsession
# Install the module
go get github.com/KEINOS/go-totp
```

```go
// Use the package
import "github.com/KEINOS/go-totp/totp"
```
Example

This example demonstrates how to generate a new secret key with default options and validate the passcode.

The generated key should be compatible with most TOTP authenticator apps.

package main

import (
	"fmt"
	"log"

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

func main() {
	Issuer := "Example.com"            // name of the service
	AccountName := "[email protected]" // 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)

This example demonstrates how to generate a new secret key with `GenerateKeyCustom`. But it is recommended to use the `GenerateKey` function with `With*` options.

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: "[email protected]",
		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)

This example demonstrates how to generate a new secret key with custom options and validate the passcode.

Since most TOTP authenticator apps are based on SHA1 hashing algorithm to generate the passcode, this example is useful when you need to generate the passcode with a stronger hash algorithm such as SHA256 and SHA512.

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 := "[email protected]"

	key, err := totp.GenerateKey(Issuer, AccountName,
		totp.WithAlgorithm(totp.Algorithm("SHA256")), // Algorithm for passcode generation (MD5, SHA1, SHA256 and SHA512)
		totp.WithPeriod(15),                          // Interval of the passcode validity
		totp.WithSecretSize(256),                     // Size of the TOTP secret key in bytes
		totp.WithSkew(5),                             // Number of periods as tolerance (+/-)
		totp.WithDigits(totp.DigitsEight),            // Number of digits for the passcode
	)
	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!

package main

import (
	"crypto/ecdh"
	"crypto/rand"
	"fmt"
	"log"

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

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 := "It can be any string but consistent between Alice and Bob."

	// ------------------------------------------------------------------------
	//  Generate a new ECDH keys for 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.

	generateECDHKeys := func(commonCurve ecdh.Curve) (*ecdh.PrivateKey, *ecdh.PublicKey) {
		priv, err := commonCurve.GenerateKey(rand.Reader)
		if err != nil {
			log.Fatal(err, "failed to generate ECDH private key")
		}

		return priv, priv.PublicKey()
	}

	ecdhPrivAlice, ecdhPubAlice := generateECDHKeys(commonCurve)
	ecdhPrivBob, ecdhPubBob := generateECDHKeys(commonCurve)

	// ------------------------------------------------------------------------
	//  Alice Side
	// ------------------------------------------------------------------------
	issuerAlice := "Example.com"            // name of the service
	accountNameAlice := "[email protected]" // name of the user

	totpKeyAlice, err := totp.GenerateKey(issuerAlice, accountNameAlice,
		// 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 derived from this shared secret using the key derivation
		// function (KDF) set in the options to stretch up to the options.SecretSize.
		// The default KDF is BLAKE3.
		totp.WithECDH(ecdhPrivAlice, ecdhPubBob, commonCtx),
	)
	if err != nil {
		log.Fatal(err)
	}

	// Alice generates 8 digits of TOTP passcode
	passcodeAlice, err := totpKeyAlice.PassCode()
	if err != nil {
		log.Fatal(err)
	}

	// ------------------------------------------------------------------------
	//  Bob Side
	// ------------------------------------------------------------------------
	issuerBob := "Example.com"
	accountNameBob := "[email protected]"

	totpKeyBob, err := totp.GenerateKey(issuerBob, accountNameBob,
		totp.WithECDH(ecdhPrivBob, ecdhPubAlice, commonCtx),
	)
	if err != nil {
		log.Fatal(err)
	}

	// Bob generates 8 digits of TOTP passcode
	passcodeBob, err := totpKeyBob.PassCode()
	if err != nil {
		log.Fatal(err)
	}

	// ------------------------------------------------------------------------
	//  Validataion
	// ------------------------------------------------------------------------
	if passcodeAlice == passcodeBob {
		fmt.Println("* Validation result: Passcode matches")
	}
}
Output:

* Validation result: Passcode matches
Example (Ecdh_with_custom_KDF)

This example demonstrates how to use a custom KDF (Key Derivation Function) that derives the TOTP secret key from the ECDH shared secret.

package main

import (
	"crypto/ecdh"
	"crypto/pbkdf2"
	"crypto/rand"
	"crypto/sha3"
	"fmt"
	"log"
	"math"

	"github.com/KEINOS/go-totp/totp"
	"github.com/pkg/errors"
)

func main() {
	// ------------------------------------------------------------------------
	//  Pre-agreement between Alice and Bob.
	// ------------------------------------------------------------------------
	commonCurve := ecdh.X25519()
	commonCtx := "arbitrary string but consistent between Alice and Bob"

	// Custom KDF (Key Derivation Function) for the TOTP secret key.
	//
	// This custom function uses PBKDF2 from the crypto/pbkdf2 package with
	// SHA3-256 and 4096 iterations. The "secret" is the ECDH shared secret.
	// "ctx" is used as the salt to derive the TOTP secret key, and "outLen"
	// is the desired length of the derived TOTP secret key.
	commonKDF := func(secret []byte, ctx []byte, outLen uint) ([]byte, error) {
		const iter = 4096

		// At least 8 bytes is recommended by the RFC.
		if len(ctx) < 8 {
			return nil, errors.New("context too short. PBKDF2 requires at least 8 bytes")
		}

		// Check for potential integer overflow during uint to int conversion
		if outLen == 0 || outLen > math.MaxInt {
			return nil, errors.New("output length is out of valid range for int conversion")
		}

		return pbkdf2.Key(sha3.New256, string(secret), ctx, iter, int(outLen))
	}

	// ------------------------------------------------------------------------
	//  Generate a new ECDH keys for Alice and Bob
	// ------------------------------------------------------------------------
	generateECDHKeys := func(paramCommon ecdh.Curve) (*ecdh.PrivateKey, *ecdh.PublicKey) {
		priv, err := paramCommon.GenerateKey(rand.Reader)
		if err != nil {
			log.Fatal(err, "failed to generate ECDH private key")
		}

		return priv, priv.PublicKey()
	}

	//  We pretend that Alice and Bob have exchanged their ECDH public keys
	ecdhPrivAlice, ecdhPubAlice := generateECDHKeys(commonCurve)
	ecdhPrivBob, ecdhPubBob := generateECDHKeys(commonCurve)

	// ------------------------------------------------------------------------
	//  Alice Side
	// ------------------------------------------------------------------------

	// Generate a new TOTP key for Alice using ECDH key and common context
	totpKeyAlice, err := totp.GenerateKey("Example.com", "[email protected]",
		totp.WithECDH(ecdhPrivAlice, ecdhPubBob, commonCtx),
		// Assign a custom function to derive the TOTP secret key from the ECDH
		// shared secret. If not set, totp.OptionKDFDefault will be used, which
		// is BLAKE3.
		totp.WithECDHKDF(commonKDF),
	)
	if err != nil {
		log.Fatal(err)
	}

	// Alice generates 8 digits of TOTP passcode
	passcodeAlice, err := totpKeyAlice.PassCode()
	if err != nil {
		log.Fatal(err)
	}

	// ------------------------------------------------------------------------
	//  Bob Side
	// ------------------------------------------------------------------------

	// Generate a new TOTP key for Bob using ECDH key and common context
	totpKeyBob, err := totp.GenerateKey("Example.com", "[email protected]",
		totp.WithECDH(ecdhPrivBob, ecdhPubAlice, commonCtx),
		totp.WithECDHKDF(commonKDF),
	)
	if err != nil {
		log.Fatal(err)
	}

	// Bob generates 8 digits of TOTP passcode
	passcodeBob, err := totpKeyBob.PassCode()
	if err != nil {
		log.Fatal(err)
	}

	// ------------------------------------------------------------------------
	//  Validataion
	// ------------------------------------------------------------------------
	if passcodeAlice == passcodeBob {
		fmt.Println("* Validation result: Passcode matches")
	}
}
Output:

* Validation result: Passcode matches
Example (Secret_param_first)

As of v0.3.0, the "totp.Key.URI()" method returns the URI string with the "secret" query parameter first by default.

This is due to avoid a niche reading error bug of Google Authenticator (#55). QR Code generated with other libraries that uses an old encoding method, Google Authenticator fails to read if the URI in the QR Code ends with a "secret" query parameter.

package main

import (
	"fmt"
	"log"

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

func main() {
	// note the order of the query parameters
	origin := "otpauth://totp/Example.com:[email protected]?algorithm=SHA1&" +
		"period=60&issuer=Example.com&digits=12&" +
		"secret=QF7N673VMVHYWATKICRUA7V5MUGFG3Z3"

	key, err := totp.GenKeyFromURI(origin)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(key.URI())
}
Output:

otpauth://totp/Example.com:[email protected]?secret=QF7N673VMVHYWATKICRUA7V5MUGFG3Z3&algorithm=SHA1&digits=12&issuer=Example.com&period=60

Index

Examples

Constants

View Source
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.

View Source
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.
	OptionPrependSecretInURIDefault = true              // Force the "secret" element to be the first query string.
)

Constants for the default values of the options.

View Source
const BlockTypeTOTP = "TOTP SECRET KEY"

BlockTypeTOTP is the type of a PEM encoded data block.

Variables

This section is empty.

Functions

func OptionKDFDefault added in v0.3.0

func OptionKDFDefault(secret, ctx []byte, outLen uint) ([]byte, error)

OptionKDFDefault is the default key derivation function (KDF) for TOTP secret key derivation from ECDH shared secret. The underlying KDF is BLAKE3.

func StrToUint

func StrToUint(number string) uint

StrToUint converts a string to an unsigned integer. If the string is not a valid integer or out of range of uint32, 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

func Validate(passcode, secret string, options Options) bool

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:[email protected]?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.

func ValidateCustom added in v0.2.0

func ValidateCustom(passcode, secret string, validationTime time.Time, options Options) bool

ValidateCustom is like Validate but allows a custom validation time.

Types

type Algorithm

type Algorithm string

Algorithm is a string that represents the algorithm used to generate the passcode (for HMAC).

Example
package main

import (
	"fmt"
	"log"

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

func main() {
	// Create a new Algorithm object from a string for passcode generation.
	// 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

func NewAlgorithmID(algoID int) (Algorithm, error)

NewAlgorithmID creates an Algorithm from its numeric ID used by the underlying library. This constructor is mainly for conversions and checks.

func NewAlgorithmStr

func NewAlgorithmStr(algo string) (Algorithm, error)

NewAlgorithmStr creates an Algorithm value from its name. Available algorithms: MD5, SHA1, SHA256, SHA512.

func (Algorithm) ID

func (algo Algorithm) ID() int

ID returns the numeric ID used by the original OTP library. Returns -1 for undefined/unsupported algorithms.

func (Algorithm) IsSupported

func (algo Algorithm) IsSupported() bool

IsSupported returns true if the algorithm is supported.

Example
package main

import (
	"fmt"

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

func main() {
	// Set unsupported algorithm for passcode generation
	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

func (algo Algorithm) OTPAlgorithm() otp.Algorithm

OTPAlgorithm converts Algorithm to otp.Algorithm. Returns otp.Algorithm(-1) for unsupported algorithms (see issue #6).

func (Algorithm) String

func (algo Algorithm) String() string

String is an implementation of the Stringer interface.

type Digits

type Digits uint

Digits represents the number of digits in the OTP code.

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")
	}

	// Negative input will enforce the default value of 6 digits.
	if totp.DigitsSix == totp.NewDigitsInt(-1) {
		fmt.Println("Negative input is enforced to 6 digits:", "OK")
	}
}
Output:

Digits: 8
Digits ID: 8
Digit 8: OK
Digit 6: OK
Negative input is enforced to 6 digits: OK
const (
	// DigitsSix is the default number of digits in a TOTP passcode.
	DigitsSix Digits = 6
	// DigitsEight is an alternative number of digits in a TOTP passcode.
	DigitsEight Digits = 8
)

func NewDigitsInt

func NewDigitsInt(digits int) Digits

NewDigitsInt returns a new Digits object from the given value. If the value is less than zero, it will return DigitsSix.

func NewDigitsStr

func NewDigitsStr(digits string) Digits

NewDigitsStr returns a new Digits object from the given string in decimal format.

func (Digits) OTPDigits

func (d Digits) OTPDigits() otp.Digits

OTPDigits returns the value in otp.Digits type. Undefined Digits will always return otp.DigitsSix.

func (Digits) String

func (d Digits) String() string

String returns the string representation of the Digits.

type FixLevel

type FixLevel byte

FixLevel represents the QR code error correction level. Use FixLevel* constants.

type Key

type Key struct {
	Secret  Secret  // The secret key.
	Options Options // Options to be stored.
}

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 := "[email protected]"

	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 (Regenerate1)

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.

If you simply want to validate a passcode with a backed-up secret key value, use the totp.Validate() function instead.

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 := "[email protected]"

	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: [email protected]
Algorithm: SHA1
Digits: 6
Issuer: Example.com
Period: 30
Secret Size: 20
Skew: 1

gX7ff3VlT4sCakCjQH69ZQxTbzs=
-----END TOTP SECRET KEY-----
Example (Regenerate2)

In this example, we will re-generate/recover a new Key object from a backed-up secret key value.

This does the same as the previous example but with a different approach. Choose the one that suits your needs.

package main

import (
	"fmt"
	"log"

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

func main() {
	// Step1: Generate a totp.Secret object from a backed-up secret key value
	secret, err := totp.NewSecretBase32("QF7N673VMVHYWATKICRUA7V5MUGFG3Z3")
	if err != nil {
		log.Fatal(err)
	}

	// Step2: Generate a new totp.Options object with default values but with the
	// secret key size set to the same as the backed-up secret key value.
	options, err := totp.NewOptions("Example.com", "[email protected]")
	if err != nil {
		log.Fatal(err)
	}

	options.SecretSize = uint(len(secret.Bytes()))

	// Step3: Generate a new totp.Key object.
	key := totp.Key{
		Secret:  secret,
		Options: *options,
	}

	// Step4: 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: [email protected]
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

func GenKeyFromPEM(pemKey string) (*Key, error)

GenKeyFromPEM creates a Key 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: [email protected]
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: [email protected]
Algorithm: SHA1
Digits: 8
Issuer: Example.com
Period: 30
Secret Size: 64
Skew: 1
Secret: QF7N673VMVHYWATKICRUA7V5MUGFG3Z3

func GenKeyFromURI added in v0.2.0

func GenKeyFromURI(uri string) (*Key, error)

GenKeyFromURI creates a Key from a TOTP URI. 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:[email protected]?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: [email protected]
Algorithm: SHA1
Digits: 12
Period: 60
Secret Size: 20
Secret: QF7N673VMVHYWATKICRUA7V5MUGFG3Z3

func GenerateKey

func GenerateKey(issuer string, accountName string, opts ...Option) (*Key, error)

GenerateKey creates a new Key object.

To customize the options, use the With* functions, if `opt` is empty, it will use the default options.

key, err := totp.GenerateKey("MyIssuer", "MyAccountName",
	totp.WithAlgorithm(totp.Algorithm("SHA256"))
	// for more customization options, see the options.go file.
)

func GenerateKeyCustom

func GenerateKeyCustom(options Options) (*Key, error)

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

func GenerateKeyPEM(pemKey string) (*Key, error)

GenerateKeyPEM creates a Key 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

func GenerateKeyURI(uri string) (*Key, error)

GenerateKeyURI creates a Key from a TOTP URI.

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

func (k *Key) PEM() (string, error)

PEM returns the key as a PEM-formatted string.

Example
package main

import (
	"fmt"
	"log"

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

func main() {
	origin := "otpauth://totp/Example.com:[email protected]?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: [email protected]
Algorithm: SHA1
Digits: 6
Issuer: Example.com
Period: 30
Secret Size: 20
Skew: 1

gX7ff3VlT4sCakCjQH69ZQxTbzs=
-----END TOTP SECRET KEY-----

func (*Key) PassCode

func (k *Key) PassCode() (string, error)

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 := "[email protected]"

	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

func (k *Key) PassCodeCustom(genTime time.Time) (string, error)

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 := "[email protected]"

	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

func (k *Key) QRCode(fixLevel FixLevel) (*QRCode, error)

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:[email protected]?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)
	//nolint:lll // long line is intentional
	expect := `89504e470d0a1a0a0000000d4948445200000064000000641000000000051` +
		`916cb0000041c49444154789ce45cd172e4200c3337fbffbf9c9bce4eba449164a7` + `f7549d5fba2160580496053b7d1d4745d8ab6a2dfdfafc9a7b9dbdecebf3fed7b54` + `3bf5d7dd68f1ee71ffdf277d9ebfd872d303603fbcc9c6db0ecebf3398b7bbbdd2f` + `be67fda9fef938d31029b13eb11c679ead5d8690f285a8611943858f331111672a0` + `a61e4daeb33f4d08fdb2bfb1eeced7f438445a5dd1c6fe0ec2b1499cde93a1111f7` + `ed11091561dc8cabb5cfea2baed1e34c43c4e531bb31dee8d67a879caacf229b1e6` + `712224f137936bbfb733559829a6d1c47f77cb524445cbe7fb2315ba76e1d777cc3` + `187eb72ec7427f598894506138c3bb29be506bde451e9529ab2c98fb894164bdbf1` + `fb2ae62f00eb127c83a7e61c6d4e9e73906914db3637ee3347977aa81ed989fd350` + `abb0f7d59e21a421729acb5e156f9ce6d4639557888af15db67d1d471222fb1a756` + `b5acd9e5afb2a53467fae9dca00eebe931039add3192a4b75a725d56898096794e0` + `a26ba48d41645de757c579349763e173a7e1779f7298a2afcca8b51b8be51d3abbb` + `968a3b8a98a6b17e7fbce298988fc8433dc67b6e6153333d45574e4be63101151eb` + `34a71f587dd716cbbb68f84ce7a421c2f6849a0da7addbee1a4dae78c9b57d5b0c2` + `22f9ebf38e66619b25bcb7b5dad277cd6dbf515b447c469bc62e022eccfea57c327` + `4bdcee76592efaff580c22eb38d4fa479bc47ff5ac7c7d0f639055b8b1e4ee914e3` + `74cf329a56b1c37615f6a8f2e7abf9884885afb4ef5b1b2a91fe7ff49ffb9a72813` + `ed3055892c02765abce0660cfd74e3795b122235b8c353acaad041e6577eca9c3ab` + `268a9fac9e211b6a65d8462b3c3b477998c9631b7db5f8e5f56d6fdc8d231e5895a` + `ec3282dd545ee6b40f433057b3eb9309cfae2c92395fb7ae1bada2ce09f81a4a42a` + `48436de9f6fcd06f7e98e7ba67aa41b4f268f9cf6244752f5260839f5a722533796` + `ac3dd2451515e3591d1771ba28560df7a8f1bd2d0991cba360e749cec4da6359170` + `dd538d858ae7dc720427ed3a858bd865ca018ff38663c81653cdbbdf69bc7234ab9` + `a1b948540231979f617f4fa2dfd56210d9a2d624eb54ccad4cadf5a71aa6eb2f6f8f` + `94c8b5e67a80cf6cb777941f8594de973188c0adee77f1c35bd6dd9cdebe75ff8f77` + `2d47ec1d629173d8bddc6908b7a67faaf7b12d7bbe962721d2ebe15e43b08c7499df` + `6075992d96e3e7bbbf24443a5ee86645a151e6eeef093f313ff7681a83089c34a239` + `65d765cc2eda29c5c9fcb93df8a91783c8e07f3eb0f5ecd67ac707580723a24311fd` + `7edea72152c3138e633bf9eb189ef1883b6141ee717c84ed321171c6325a2c533abd` + `065c84a72c35886c2bfa17749dddd7e4a71cff2aee515ce47886ed557c17b64706a7` + `f135e006952fb9768ab1b11dd7e8e8270691c1ff7c503ac34515b69fd0570d4eddd9` + `331f6f0c22e6970fbfcbfe060000ffff11a9592e12644af00000000049454e44ae42` +
		`6082`

	// Assert equal image
	if expect == actual {
		fmt.Println("OK")
	}
}
Output:

OK

func (*Key) String added in v0.2.0

func (k *Key) String() string

String returns 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: [email protected]
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:[email protected]?" +
		"secret=QF7N673VMVHYWATKICRUA7V5MUGFG3Z3&" +
		"algorithm=SHA1&digits=12&issuer=Example.com&period=60"
	actual := key.String()

	if expect == actual {
		fmt.Println("URI returned as expected")
	}
}
Output:

URI returned as expected

func (*Key) URI

func (k *Key) URI() string

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() {
	// Note that the "secret" query parameter (Base32 encoded) is at the end of
	// the URI.
	origin := "otpauth://totp/Example.com:[email protected]?" +
		"digits=12&algorithm=SHA1&period=60&issuer=Example.com&" +
		"secret=QF7N673VMVHYWATKICRUA7V5MUGFG3Z3"

	key, err := totp.GenKeyFromURI(origin)
	if err != nil {
		log.Fatal(err)
	}

	// As of v0.3.0, the Key.URI() method returns the URI with the secret as the
	// first query parameter. For details see issue #55.
	expect := "otpauth://totp/Example.com:[email protected]?" +
		"secret=QF7N673VMVHYWATKICRUA7V5MUGFG3Z3&" +
		"algorithm=SHA1&digits=12&issuer=Example.com&period=60"

	actual := key.URI() // regenerate URI

	if expect == actual {
		fmt.Println("URI returned as expected")
	}
}
Output:

URI returned as expected

func (*Key) Validate

func (k *Key) Validate(passcode string) bool

Validate returns true if the given passcode is valid for the current time. For custom time, use ValidateCustom() instead.

func (*Key) ValidateCustom added in v0.2.0

func (k *Key) ValidateCustom(passcode string, validationTime time.Time) bool

ValidateCustom returns true if the given passcode is valid for the custom time.

type Option added in v0.2.0

type Option func(*Options) error

Option applies a modification to Options. It should return an error when opts is nil or the argument is invalid.

func WithAlgorithm added in v0.2.0

func WithAlgorithm(algo Algorithm) Option

WithAlgorithm sets the Algorithm to use for HMAC (Default: Algorithm("SHA1")).

func WithDigits added in v0.2.0

func WithDigits(digits Digits) Option

WithDigits sets the number of digits for the TOTP code (DigitsSix or DigitsEight). Default is DigitsSix.

func WithECDH added in v0.2.0

func WithECDH(localKey *ecdh.PrivateKey, remoteKey *ecdh.PublicKey, context string) Option

WithECDH sets a hashed ECDH shared secret as the TOTP secret using the given local private key and the peer's 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 [email protected] [email protected] TOTP secret v1"

func WithECDHKDF added in v0.3.0

func WithECDHKDF(userKDF func(secret, ctx []byte, outLen uint) ([]byte, error)) Option

WithECDHKDF sets a custom key-derivation function (KDF) to derive a TOTP secret from an ECDH shared secret.

The function must match the following signature:

func(secret, ctx []byte, outLen uint) ([]byte, error)

Responsibility: The implemented KDF must deterministically derive and return exactly outLen bytes. If it cannot produce outLen bytes, it should return an error. The ctx should be used as a salt or salt-like value during the key derivation.

func WithPeriod added in v0.2.0

func WithPeriod(period uint) Option

WithPeriod sets the number of seconds a TOTP hash is valid for (Default: 30 seconds).

func WithSecretQueryFirst added in v0.3.0

func WithSecretQueryFirst(choice bool) Option

WithSecretQueryFirst sets whether the secret should be the first query parameter in the URI.

When true (default), secret appears first: "?secret=...&algorithm=..." and other parameters are sorted.

When false, parameters are sorted alphabetically: "?algorithm=...&secret=...".

func WithSecretSize added in v0.2.0

func WithSecretSize(size uint) Option

WithSecretSize sets the size of the generated Secret (Default: 128 bytes).

func WithSkew added in v0.2.0

func WithSkew(skew uint) Option

WithSkew sets the periods before or after the current time to allow.

Value of 1 allows up to Period of either side of the specified time. Defaults to 1 allowed skews. Values greater than 1 are likely sketchy.

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.

Note that "Options" does not hold the actual TOTP "secret" key. The "Key" object holds the secret key with these options.

Example
package main

import (
	"fmt"

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

func main() {
	// You may instantiate Options directly but it's recommended to use
	// NewOptions() for convenience.
	//
	//nolint:exhaustruct // missing fields allowed due to be an example
	options := totp.Options{
		Issuer:      "Example.com",
		AccountName: "[email protected]",
	}

	/* List all exposed options and their values */
	fmt.Printf("Issuer: \"%v\"\n", options.Issuer)
	fmt.Printf("AccountName: \"%v\"\n", options.AccountName)
	fmt.Printf("Algorithm: \"%v\"\n", options.Algorithm)
	fmt.Printf("Digits: \"%v\"\n", options.Digits)
	fmt.Printf("Period: \"%v\"\n", options.Period)
	fmt.Printf("Secret Size: \"%v\"\n", options.SecretSize)
	fmt.Printf("Skew: \"%v\"\n", options.Skew)
}
Output:

Issuer: "Example.com"
AccountName: "[email protected]"
Algorithm: ""
Digits: "0"
Period: "0"
Secret Size: "0"
Skew: "0"

func NewOptions

func NewOptions(issuer, accountName string) (*Options, error)

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", "[email protected]")
	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: [email protected]
Error msg: issuer and accountName are required

func (*Options) SetDefault

func (opts *Options) SetDefault()

SetDefault resets all the options to their default values. Use this method for initializing a new Options struct with default values.

As of v0.3.1, this method will override all options even if they contain values.

- See: https://github.com/KEINOS/go-totp/issues/56

Example
package main

import (
	"fmt"
	"log"

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

func main() {
	options, err := totp.NewOptions("Example.com", "[email protected]")
	if err != nil {
		log.Fatal(err)
	}

	options.SetDefault() // reset all the fields to their default values.

	/* List all exposed options and their values after reset. */
	fmt.Printf("Issuer: \"%v\"\n", options.Issuer)
	fmt.Printf("AccountName: \"%v\"\n", options.AccountName)
	fmt.Printf("Algorithm: \"%v\"\n", options.Algorithm)
	fmt.Printf("Digits: \"%v\"\n", options.Digits)
	fmt.Printf("Period: \"%v\"\n", options.Period)
	fmt.Printf("Secret Size: \"%v\"\n", options.SecretSize)
	fmt.Printf("Skew: \"%v\"\n", options.Skew)
}
Output:

Issuer: ""
AccountName: ""
Algorithm: "SHA1"
Digits: "6"
Period: "30"
Secret Size: "128"
Skew: "1"

type QRCode

type QRCode struct {
	URI   URI      // URI to encode.
	Level FixLevel // Error correction level.
}

QRCode holds information to generate a QR code image.

func (*QRCode) Image

func (q *QRCode) Image(width, height int) (image.Image, error)

Image returns the QR code as image.Image. Note: The underlying library cannot scale images smaller than 49x49. Passing smaller sizes will return a scaling error.

func (*QRCode) PNG

func (q *QRCode) PNG(width, height int) ([]byte, error)

PNG returns a PNG image of the QR code in bytes. See Image() for size constraints when width/height are below 49.

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 lines are the same but with different base-encodings.
	base32Secret := "MZXW6IDCMFZCAYTVPJ5A"
	base62Secret := "FegjEGvm7g03GQye"
	base64Secret := "Zm9vIGJhciBidXp6"

	// 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)
	}

	// Instantiate a new Secret object from a base64 encoded string.
	secret64, err := totp.NewSecretBase64(base64Secret)
	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 base32 encoded string:", secret64.Base32())
	fmt.Println("Get as base62 encoded string:", secret64.Base62())
	fmt.Println("Get as base64 encoded string:", secret32.Base64())

	// String() method is equivalent to Base32()
	if secret62.String() == secret62.Base32() {
		fmt.Println("String() is equivalent to Base32()")
	}

	// To obtain the raw secret value, use the Bytes() method.
	fmt.Printf("Base32 secret: %x\n", secret32.Bytes())
	fmt.Printf("Base62 secret: %x\n", secret62.Bytes())
	fmt.Printf("Base64 secret: %x\n", secret64.Bytes())
}
Output:

Get as base32 encoded string: MZXW6IDCMFZCAYTVPJ5A
Get as base62 encoded string: FegjEGvm7g03GQye
Get as base64 encoded string: Zm9vIGJhciBidXp6
String() is equivalent to Base32()
Base32 secret: 666f6f206261722062757a7a
Base62 secret: 666f6f206261722062757a7a
Base64 secret: 666f6f206261722062757a7a

func NewSecretBase32

func NewSecretBase32(base32string string) (Secret, error)

NewSecretBase32 creates a new Secret object from a base32 encoded string. The string must be encoded with no padding.

func NewSecretBase62

func NewSecretBase62(base62string string) (Secret, error)

NewSecretBase62 creates a new Secret object from a base62 encoded string.

func NewSecretBase64 added in v0.3.0

func NewSecretBase64(base64string string) (Secret, error)

NewSecretBase64 creates a new Secret object from a base64 encoded string. The string must be encoded with standard (RFC 4648) padding.

func NewSecretBytes

func NewSecretBytes(input []byte) Secret

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.Printf("Secret bytes: %#x\n", secret.Bytes())
	fmt.Println("Secret string:", secret.String())
	fmt.Println("Secret Base32:", secret.Base32())
	fmt.Println("Secret Base62:", secret.Base62())
	fmt.Println("Secret Base64:", secret.Base64())
}
Output:

Type: totp.Secret
Value: totp.Secret{0x73, 0x6f, 0x6d, 0x65, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74}
Secret bytes: 0x736f6d6520736563726574
Secret string: ONXW2ZJAONSWG4TFOQ
Secret Base32: ONXW2ZJAONSWG4TFOQ
Secret Base62: bfF9D3ygDyVQZp2
Secret Base64: c29tZSBzZWNyZXQ=

func (Secret) Base32

func (s Secret) Base32() string

Base32 returns the secret as a base32 encoded string with no padding. Which is the standard format used by TOTP URIs.

func (Secret) Base62

func (s Secret) Base62() string

Base62 returns the secret as a base62 encoded string.

func (Secret) Base64 added in v0.3.0

func (s Secret) Base64() string

Base64 returns the secret as a base64 encoded string with padding (RFC 4648). This encoding is used in PEM exports.

Example
package main

import (
	"fmt"
	"log"
	"strings"

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

func main() {
	Issuer := "Example.com"            // name of the service
	AccountName := "[email protected]" // 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)
	}

	// Base64 encoded secret key is the same encoding used in the PEM data.
	secBase64 := key.Secret.Base64()

	pemData, err := key.PEM()
	if err != nil {
		log.Fatal(err)
	}

	// Chunk the base64 encoded secret key to 64 characters per line.
	// Since the secret in PEM data is usually word-wrapped at 64 characters per
	// line.
	const lenSplit = 64

	var lines []string

	for index, char := range secBase64 {
		if index%lenSplit == 0 {
			lines = append(lines, "")
		}

		lines[len(lines)-1] += string(char)
	}

	// Check if the base64 encoded secret key is found in the PEM data.
	for index, line := range lines {
		if strings.Contains(pemData, line) {
			fmt.Println(index+1, "Base64 encoded secret key is found in PEM data")
		} else {
			fmt.Println(pemData)
			fmt.Println(secBase64)
		}
	}
}
Output:

1 Base64 encoded secret key is found in PEM data
2 Base64 encoded secret key is found in PEM data
3 Base64 encoded secret key is found in PEM data

func (Secret) Bytes

func (s Secret) Bytes() []byte

Bytes returns the secret as a byte slice. This is the raw format of the secret.

func (Secret) String

func (s Secret) String() string

String implements fmt.Stringer and returns Base32 encoding.

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:[email protected]?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.
	err := uri.Check()
	if 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: [email protected]
Algorithm: SHA1
Secret: QF7N673VMVHYWATKICRUA7V5MUGFG3Z3
Period: 60
Digits: 12

func NewURI

func NewURI(uri string) URI

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

func (u URI) AccountName() string

AccountName returns the account name from the URI.

func (URI) Algorithm

func (u URI) Algorithm() string

Algorithm returns the algorithm from the URI.

func (URI) Check

func (u URI) Check() error

Check returns true if the URI is correctly formatted and required fields are set.

func (URI) Digits

func (u URI) Digits() uint

Digits returns the number of digits a TOTP hash should have from the URI query. Returns 0 when the value is missing, invalid, or out of the accepted range.

func (URI) Host

func (u URI) Host() string

Host returns the host name from the URI. This should be `totp`.

func (URI) Issuer

func (u URI) Issuer() string

Issuer returns the issuer from the URI.

It requires that the issuer is present in both the path label and the query parameter, and that they match. If they do not, an empty string is returned to indicate an invalid or ambiguous issuer.

func (URI) IssuerFromPath

func (u URI) IssuerFromPath() string

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:[email protected]?issuer=Wrong.com"

	uri := totp.URI(origin)

	fmt.Println(uri.IssuerFromPath())
}
Output:

Example.com

func (URI) Label added in v0.3.2

func (u URI) Label() string

Label returns the LABEL part of the URI in an unescaped form without the leading slash. When the URI is malformed, it returns an empty string.

Based on the following format of Google Authenticator:

otpauth://TYPE/LABEL?PARAMETERS

func (URI) Parameters added in v0.3.2

func (u URI) Parameters(secretFirst bool) string

Parameters returns the query component of the URI without the leading '?'.

When secretFirst is true and a 'secret' parameter exists, it is placed at the beginning; otherwise, parameters are sorted in alphabetical order as per url.Values.Encode(). Spaces are encoded as %20 (not '+'). Returns an empty string when the URI is malformed.

Based on the following format of Google Authenticator:

otpauth://TYPE/LABEL?PARAMETERS

func (URI) Path

func (u URI) Path() string

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

func (u URI) Period() uint

Period returns the number of seconds a TOTP hash is valid for from the URI. Returns 0 when the value is missing, invalid, or out of the accepted range.

func (URI) Scheme

func (u URI) Scheme() string

Scheme returns the scheme/protocol from the URI. This should be `otpauth`.

func (URI) Secret

func (u URI) Secret() Secret

Secret returns the secret key from the URI as a Secret object.

func (URI) String

func (u URI) String() string

String is an implementation of the Stringer interface. It just returns the raw URI.

func (URI) Type added in v0.3.2

func (u URI) Type() string

Type returns the type to distinguish whether the key will be used for HOTP (counter-based) or for TOTP (time-based).

Based on the following format of Google Authenticator:

otpauth://TYPE/LABEL?PARAMETERS

Jump to

Keyboard shortcuts

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