gotp

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Mar 19, 2023 License: MIT Imports: 12 Imported by: 2

README

GoTP: One-time password library for Go

GoTP library provides implementations of one-time password generators and validators.

This implementation supports HMAC-based OTP (RFC 4226) and Time-based OTP (RFC 6238).

This library allows generation and validation of one-time passwords as used by variuos services. It is compatible with Google Authenticator and Authy.

It supports all hash functions in standard library crypto module and provides utility methods to create provisioning URIs from configured OTP generators and parse URIs to configured OTP generators.

HMAC-based One-time Password


    import "github.com/uaraven/gotp"
    ...

    counter := 10
    hotp := gotp.NewDefaultHOTP([]byte("secret key"), counter)
    code := hotp.CurrentOTP(counter)

    if hotp.Verify(code, counter) {
        panic(fmt.Error("invalid OTP code"))
    }

NewHOTP function creates HOTP instance with parameters such as number of digits in the one-time code and truncation offset. You can use NewDefaultHOTP with the sane default parameters (6 digits, dynamic truncation, SHA1 hash function).

Default counter value must be provided every time when instance of HOTP is created. The counter will increment every time new one-time password is requested. Counter can be reset by either setting it directly with HOTP.SetCounter(value) or by calling HOTP.GenerateOTP(counter). In the latter case internal HOTP counter will be updated to the new value and the counter will be incremented after the one-time password is generated.

HOTP also provides basic verification function. Resynchronization and verification throttling are out of scope for this library.

Time-based One-time Password


    import (
        "github.com/uaraven/gotp"
        "time"
    )
    ...

    totp := gotp.NewDefaultTOTP([]byte("secret key"))
    timestamp := time.Date(2021, 12, 20, 11, 28, 13, 0, time.UTC)
    code := totp.At(timestamp)

    if totp.VerifyAt(code, timestamp) {
        panic(fmt.Error("invalid OTP code"))
    }

TOTP parameters, such as number of digits in the resulting code, time step duration and starting time can be configured by using NewTOTP function. NewDefaultTOTP creates a TOTP implementation with default parameters compatible with most authentication services.

TOTP instance provides functions to verify correctness of the one-time password at any time. It also supports verification within the wider window to allow for out-of-sync clocks and network lag.

VerifyWithinWindow(otp, timestamp, validationWindow) will validate otp code within ±validateWindow time steps around given timestamp. It is not recommended to use validationWindow values larger than 1 as this will expose larget window for attacks.

Provisioning URLs

GoTP supports generating and parsing of Google Authenticator-compatible URLs.

To generate a new provisioning URL use ProvisioningUri(label string, issuer string) string function in OTP interface.

To create an OTP generator from URL use OTPFromUri(uri string) (*OTPKeyData, error) function. It will return pointer to OTPKeyData structure that contains instance of the generator and, additionally, label and issuer fields from the URI.

Notes on hash functions

This library will work with all hash functions defined in crypto module. Default hash function used is SHA-1.

If you see following error

panic: crypto: requested hash function #X is unavailable

you need to import corresponding module so that the hash function can register itself with crypto module.

For example if you want to use TOTP with SHA-512 hash

    import (
        "github.com/uaraven/gotp"
        "crypto"
        _ "crypto/sha512" // you need this
    )
    ...

    totp := gotp.NewTOTPHash([]byte("secret key"), DefaultDigits, DefaultTimeStep, 0, crypto.SHA512)
    code := totp.Now()

When generating provisioning URI, algorithm parameter will be included only if the hash function is one of SHA1, SHA256 or SHA512. Note that some of popular one-time passcode generation applications (like Google Authenticator) will ignore algorithm parameter and always use SHA1.

License

This project is distributed under MIT license.

Documentation

Overview

SPDX-FileCopyrightText: 2021 Oleksiy Voronin <me@ovoronin.info> SPDX-License-Identifier: MIT

SPDX-FileCopyrightText: 2021 Oleksiy Voronin <me@ovoronin.info> SPDX-License-Identifier: MIT

SPDX-FileCopyrightText: 2021 Oleksiy Voronin <me@ovoronin.info> SPDX-License-Identifier: MIT

Index

Constants

View Source
const (

	// DefaultDigits is the default length of the one-time passcode
	DefaultDigits = 6
	// SHA1 is the default hash algorithm used with HMAC
	SHA1 = "SHA1"
)
View Source
const (
	// DefaultTimeStep is the default time step and is equal to 30 seconds
	DefaultTimeStep = 30
	// DefaultStartTime is the default epoch start
	DefaultStartTime = 0
)
View Source
const DefaultTransactionOffset = -1

Variables

This section is empty.

Functions

func DecodeKey

func DecodeKey(key string) ([]byte, error)

DecodeKey converts a Base32-encoded key to a byte slice

key does not have to have proper '=' padding

func EncodeKey

func EncodeKey(key []byte) string

EncodeKey converts a key to Base32 representation

Padding symbols '=' are stripped from the end of string

func HashAlgorithmName added in v0.0.4

func HashAlgorithmName(algorithm crypto.Hash) (string, error)

Types

type HOTP

type HOTP struct {
	OTP

	// Hash is the Hash function used by HMAC
	Hash crypto.Hash
	// Secret is the original shared secret
	Secret []byte
	// Key is the secret padded to the required size
	Key []byte
	// Digits is a number of digits in the resulting code
	Digits int

	// TruncationOffset is offset value for truncation function
	TruncationOffset int
	// contains filtered or unexported fields
}

HOTP is an implementation of RFC4226, HMAC-based one-time password algorithm

func NewDefaultHOTP

func NewDefaultHOTP(key []byte, counter int64) *HOTP

NewDefaultHOTP crates an instance of Hotp with default parameters: Number of OTP digits is 6, SHA1 for hashing and using dynamic truncation offset

key is the shared secret key

func NewHOTP

func NewHOTP(key []byte, counter int64, digits int, truncationOffset int) *HOTP

NewHOTP allows to create an instance of Hotp and set the parameters

key is the shared secret key

digits is the number of digits in the resulting one-time password code

algorithm is the hash function to use with HMAC, crypto.SHA1 is recommended

truncationOffset is used by truncation function that is used to extract 4-byte dynamic binary code from HMAC result. The truncation offset value must be in range [0..HMAC result size in bytes). If truncationOffset value is outside of that range, then dynamic value will be used. By default value of truncationOffset is -1 and it is recommended to keep it this way

func NewHOTPDigits

func NewHOTPDigits(key []byte, counter int64, digits int) *HOTP

NewHOTPDigits creates an instance of Hotp with given number of digits for the OTP Maximum number of digits supported is 10.

key is the shared secret key digits is the number of digits in the resulting one-time password code

func NewHOTPHash

func NewHOTPHash(key []byte, counter int64, digits int, truncationOffset int, algorithm crypto.Hash) *HOTP

NewHOTPHash allows to create an instance of Hotp, set the parameters and chose hash function to be used in underlying HMAC

key is the shared secret key

digits is the number of digits in the resulting one-time password code

algorithm is the hash function to use with HMAC, crypto.SHA1 is recommended

truncationOffset is used by truncation function that is used to extract 4-byte dynamic binary code from HMAC result. The truncation offset value must be in range [0..HMAC result size in bytes). If truncationOffset value is outside of that range, then dynamic value will be used. By default value of truncationOffset is -1 and it is recommended to keep it this way

hash is a hash function, one of crypto.* constants. You might need to add an import for selected hash function, otherwise you might see crypto: requested hash function is unavailable panic message. For example, if you want to use SHA512, then use crypto.SHA512 as a parameter and add 'import _ "crypto/sha512"' statement.

func (*HOTP) CurrentOTP

func (h *HOTP) CurrentOTP() string

CurrentOTP generates a string containing numeric one-time password code based on the internal counter value

Counter is incremented automatically

func (*HOTP) GenerateOTP

func (h *HOTP) GenerateOTP(counter int64) string

GenerateOTP generates a string containing numeric one-time password code based on the counter value

HOTP internal counter is set to the provided counter value before generating the new OTP code. Internal counter will be incremented after the code is generated

func (*HOTP) GetCounter

func (h *HOTP) GetCounter() int64

GetCounter gets current internal counter value

func (*HOTP) GetDigits added in v0.0.5

func (h *HOTP) GetDigits() int

func (*HOTP) GetHash added in v0.0.5

func (h *HOTP) GetHash() crypto.Hash

func (*HOTP) GetSecret added in v0.0.5

func (h *HOTP) GetSecret() []byte

func (*HOTP) ProvisioningUri

func (h *HOTP) ProvisioningUri(accountName string, issuer string) string

ProvisioningUri generates provisioning URI with the configured parameters as described in https://github.com/google/google-authenticator/wiki/Key-Uri-Format

Limitations:

  • truncationOffset cannot be added to provisioning URI
  • Only SHA1, SHA256 and SHA512 algorithms could be added to the URI, if HOTP is configured to use any other hashing function, no algorithm will be added to the URI Note that many OTP generating applications (i.e. Google Authenticator) will ignore algorithm key and always use SHA1
  • Current counter value will be added to URI, use SetCounter() to update it before generating URI

func (*HOTP) SetCounter

func (h *HOTP) SetCounter(newCounter int64)

SetCounter sets internal counter value

func (*HOTP) Verify

func (h *HOTP) Verify(otp string, counter int64) bool

Verify checks if provided otp code is valid for the value of counter

otp - otp code to verify counter - counter value agaist which the code will be verified

Verify will either return false immediately if otp length is different from the number of digits this Hotp is configured for or will perform constant-time comparision of the provided code and the expected code.

func (*HOTP) VerifyCurrent

func (h *HOTP) VerifyCurrent(otp string) bool

type OTP

type OTP interface {
	// GenerateOTP generates new one-time password based on counter data
	GenerateOTP(counter int64) string
	// Verify confirms validity of provided otp code against given counter
	Verify(otp string, counter int64) bool
	// ProvisioningUri generates a provisioning URI for this OTP instance
	//
	// accountName identifies an account for which the URI is generated
	// issuer identifies the entity that performs authentication
	ProvisioningUri(accountName string, issuer string) string

	// GetHash returns hash algorithm used for HMAC, this is a constant from crypto module.
	GetHash() crypto.Hash
	// GetSecret returns shared secret
	GetSecret() []byte
	// GetDigits returns the number of digits in the OTP code
	GetDigits() int
}

OTP defines common functions for the one-time passwords

type OTPKeyData

type OTPKeyData struct {
	// OTP implementation, either *HOTP or *TOTP
	OTP OTP
	// Account contains the account Id for the OTP
	Account string
	// Issuer contains the name of the issuer of the OTP
	Issuer string
}

OTPKeyData contains data parsed from otpauth URL

func NewHOTPFromUri

func NewHOTPFromUri(uri string) (*OTPKeyData, error)

NewHOTPFromUri creates an instance of HOTP with the parameters specified in URL

func NewTOTPFromUri

func NewTOTPFromUri(uri string) (*OTPKeyData, error)

NewTOTPFromUri creates an instance of TOTP with the parameters specified in URL

func OTPFromUri

func OTPFromUri(uri string) (*OTPKeyData, error)

OTPFromUri returns a pointer to OTPKeyData structure that contains instance of one-time password implementation (eitehr HOTP or TOTP, depending on URL) and label and issuer information from the URI

func (*OTPKeyData) GetLabelRepr added in v0.1.0

func (okd *OTPKeyData) GetLabelRepr() string

GetLabelRepr returns OTP label in human readable format DO NOT use it for constructing otpauth: URIs

type TOTP

type TOTP struct {
	OTP
	// Hash is a Hash function to be used for HMAC
	Hash crypto.Hash
	// Secret is the original shared secret
	Secret []byte
	// Key is the secret padded to the required size
	Key []byte
	// StartTime, usually 0
	StartTime int64
	// Digits is a number of digits in the resulting code
	Digits int
	// TimeStep is an interval for which the one-time password is valid
	TimeStep int
}

TOTP is an implementation of RFC6238, Time-based one-time password algorithm

func NewDefaultTOTP

func NewDefaultTOTP(key []byte) *TOTP

NewDefaultTOTP creates an instance of Totp with the provided key and default parameters.

Number of digits is 6, time step is 30 seconds and reference time is equal to Unix Epoch

key is the shared secret key

func NewTOTP

func NewTOTP(key []byte, digits int, interval int, startTime int64) *TOTP

NewTOTP creates an instance of Totp with the provided key and TOTP parameter values

key is the shared secret key

digits is the number of digits in the resulting one-time password code

interval is the time step to use

startTime is a Unix epoch timestamp to be used as a reference point

func NewTOTPDigits

func NewTOTPDigits(key []byte, digits int) *TOTP

NewTOTPDigits creates an instance of Totp with the provided key and desired number of digits in the resulting code. Other TOTP parameters will use default values

key is the shared secret key

digits is the number of digits in the resulting one-time password code

func NewTOTPHash

func NewTOTPHash(key []byte, digits int, interval int, startTime int64, hash crypto.Hash) *TOTP

NewTOTPHash creates an instance of Totp with the provided key and TOTP parameter values. This function also allows to configure any hash function to be used in underlying HMAC

key is the shared secret key

digits is the number of digits in the resulting one-time password code

interval is the time step to use

startTime is a Unix epoch timestamp to be used as a reference point

hash is a hash function, one of crypto.* constants. You might need to add an import for selected hash function, otherwise you might see crypto: requested hash function is unavailable panic message. For example, if you want to use SHA512, then use crypto.SHA512 as a parameter and add 'import _ "crypto/sha512"' statement.

func (*TOTP) At

func (t *TOTP) At(moment time.Time) string

At generates an one-time password at the given time

moment is a time for which to generate the one-time password

func (*TOTP) GenerateOTP

func (t *TOTP) GenerateOTP(timestamp int64) string

GenerateOTP generates a string containing numeric one-time password code based on the timestamp value

func (*TOTP) GetDigits added in v0.0.5

func (t *TOTP) GetDigits() int

func (*TOTP) GetHash added in v0.0.5

func (t *TOTP) GetHash() crypto.Hash

func (*TOTP) GetSecret added in v0.0.5

func (t *TOTP) GetSecret() []byte

func (*TOTP) GetStartTime added in v0.0.5

func (t *TOTP) GetStartTime() int64

GetStartTime returns a Unix epoch timestamp to be used as a reference point

func (*TOTP) GetTimeStep added in v0.0.5

func (t *TOTP) GetTimeStep() int

GetTimeStep returns the time step of the TOTP function

func (*TOTP) Now

func (t *TOTP) Now() string

Now generates an one-time password based on current time

func (*TOTP) ProvisioningUri

func (t *TOTP) ProvisioningUri(accountName string, issuer string) string

ProvisioningUri Generates provisioning URL with the configured parameters as described in https://github.com/google/google-authenticator/wiki/Key-Uri-Format

Limitations:

  • startTime cannot be added to provisioning URL
  • Only SHA1, SHA256 and SHA512 algorithms could be added to the URI, if TOTP is configured to use any other hashing function, no algorithm will be added to the URI Note that many OTP generating applications (i.e. Google Authenticator) will ignore algorithm key and always use SHA1

func (*TOTP) Verify

func (t *TOTP) Verify(otp string, timestamp int64) bool

Verify checks if the provided otp code is valid for the value of the given timestamp

otp - otp code to verify timestamp - Unix epoch timestamp in seconds, agaist which the code will be verified

Verify will either return false immediately if otp length is different from the number of digits this Totp is configured for or will perform constant-time comparision of the provided code and the expected code.

func (*TOTP) VerifyAt

func (t *TOTP) VerifyAt(otp string, date time.Time) bool

VerifyAt is similar to Verify, but accepts time.Time object instead of epoch timestamp

otp - otp code to verify date - Date and time, agaist which the code will be verified

func (*TOTP) VerifyAtWithinWindow

func (t *TOTP) VerifyAtWithinWindow(otp string, date time.Time, validationWindow int) bool

VerifyAtWithinWindow is similar to VerifyWithinWindow, but accepts time.Time object instead of epoch timestamp

func (*TOTP) VerifyNow

func (t *TOTP) VerifyNow(otp string) bool

VerifyNow checks if the provided otp code is valid for current time

otp - otp code to verify

func (*TOTP) VerifyWithinWindow

func (t *TOTP) VerifyWithinWindow(otp string, timestamp int64, validationWindow int) bool

VerifyWithinWindow checks if provided otp code is valid for the range of the ±validationWindow time-steps windows centered at timestamp

This allows validation to pass if client's time is out of sync with server's time and to account for network delays

It is recommended to keep validationWindow to a minimum to avoid exposing larger window for attack.

Jump to

Keyboard shortcuts

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