Documentation
¶
Index ¶
- Variables
- func DecodeSecret(secret string) ([]byte, error)
- func GenerateHOTP(secret string, counter uint64, param *Param) (string, error)
- func GenerateHOTPURL(param URLParam) (*url.URL, error)
- func GenerateOCRA(secret string, suite Suite, input OCRAInput) (string, error)
- func GenerateTOTP(secret string, t time.Time, param *Param) (string, error)
- func GenerateTOTPURL(param URLParam) (*url.URL, error)
- func IsKnownSuite(raw string) bool
- func LeftPadHex(s string, totalLen int) string
- func ListSuites() []string
- func MustHexPadLeft(hexStr string, size int) []byte
- func ParseDecimal64BigEndian(decStr string) ([]byte, error)
- func ParseDecimalChallengeRFC6287(s string) ([]byte, error)
- func ParseDecimalToBigEndian8(s string) ([]byte, error)
- func ParseHexTimestamp(ts string) ([]byte, error)
- func RandomSecret(algo Algorithm) (string, error)
- func To8ByteBigEndian(v uint64) []byte
- func ValidateHOTP(secret, code string, counter uint64, param *Param) (bool, error)
- func ValidateOCRA(secret, code string, suite Suite, input OCRAInput) (bool, error)
- func ValidateTOTP(secret, code string, t time.Time, param *Param) (bool, error)
- type Algorithm
- type ChallengeFormat
- type Digits
- type OCRAInput
- type Param
- type PasswordHashAlgorithm
- type RawSuite
- type Suite
- type SuiteConfig
- type URLParam
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrUnsupportedAlgorithm = errors.New("unsupported algorithm") ErrInvalidCodeLength = errors.New("invalid code length") ErrInvalidCode = errors.New("invalid otp code") ErrIssuerRequired = errors.New("issuer is required") ErrAccountNameRequired = errors.New("account name is required") ErrSecretRequired = errors.New("secret is required") ErrInvalidSkew = errors.New("invalid skew, a larger Skew increases the chance of a brute-force hit") ErrInvalidRawSuite = errors.New("invalid OCRA suite string") )
var DefaultHOTPParam = &Param{ Digits: SixDigits, Algorithm: SHA1, Period: 0, Skew: 2, }
DefaultHOTPParam provides a default configuration for HOTP generation and validation, using SHA1, 6 digits. Note: Period and Skew are not used in HOTP but included for Param compatibility.
var DefaultTOTPParam = &Param{ Digits: SixDigits, Period: 30, Skew: 0, Algorithm: SHA1, }
DefaultTOTPParam provides a default configuration for TOTP generation and validation, using SHA1, 6 digits, a 30-second period, and zero skew.
var TimeCounterFunc = func(t time.Time, period uint) uint64 { return uint64(t.Unix()) / uint64(period) }
TimeCounterFunc returns the TOTP counter value based on the Unix time and period. It performs integer division of time by the period to produce a moving counter window.
Functions ¶
func DecodeSecret ¶
func GenerateHOTP ¶
GenerateHOTP generates an HOTP code from a given secret and counter. If `param` is nil, DefaultHOTPParam is used.
Example ¶
package main
import (
"fmt"
"github.com/ja7ad/otp"
)
func main() {
secret, err := otp.RandomSecret(otp.SHA1)
if err != nil {
panic(err)
}
code, err := otp.GenerateHOTP(secret, 1, otp.DefaultHOTPParam)
if err != nil {
panic(err)
}
fmt.Println(code)
}
func GenerateHOTPURL ¶
GenerateHOTPURL constructs an otpauth:// URL for configuring HOTP-based authenticators. The URL includes the issuer, account name, secret, algorithm, digits, and counter.
Example output: otpauth://hotp/Example:alice@domain.com?secret=BASE32ENCODEDSECRET&issuer=Example&algorithm=SHA1&digits=6&counter=0
Example ¶
package main
import (
"fmt"
"github.com/ja7ad/otp"
)
func main() {
secret, err := otp.RandomSecret(otp.SHA1)
if err != nil {
panic(err)
}
url, err := otp.GenerateHOTPURL(otp.URLParam{
Issuer: "https://example.com",
Secret: secret,
AccountName: "foobar",
Period: otp.DefaultHOTPParam.Period,
Digits: otp.DefaultHOTPParam.Digits,
Algorithm: otp.DefaultHOTPParam.Algorithm,
})
if err != nil {
panic(err)
}
fmt.Println(url.String())
}
func GenerateOCRA ¶
GenerateOCRA generates a one-time password (OTP) using the OCRA algorithm (RFC 6287) for the given secret, suite, and input parameters.
The `secret` should be a base32-encoded shared key (e.g., from provisioning), and will be decoded into its raw binary form. The `suite` defines the OCRA configuration (create Suite using NewSuite or NewRawSuite). The `input` provides dynamic values like challenge, counter, session data, timestamp, etc.
Returns the generated OTP string or an error if decoding the secret or derivation fails.
Example ¶
package main
import (
"fmt"
"github.com/ja7ad/otp"
)
func main() {
secret, err := otp.RandomSecret(otp.SHA1)
if err != nil {
panic(err)
}
suite := otp.MustRawSuite("OCRA-1:HOTP-SHA1-6:QN08")
code, err := otp.GenerateOCRA(secret, suite, otp.OCRAInput{
Challenge: []byte("12345678"),
})
if err != nil {
panic(err)
}
fmt.Println(code)
}
func GenerateTOTP ¶
GenerateTOTP generates a TOTP code based on the given secret and timestamp. If param is nil, DefaultTOTPParam is used. The secret must be encoded according to the specified algorithm's encoding (e.g., base32 for SHA1).
Example ¶
package main
import (
"fmt"
"github.com/ja7ad/otp"
"time"
)
func main() {
secret, err := otp.RandomSecret(otp.SHA1)
if err != nil {
panic(err)
}
code, err := otp.GenerateTOTP(secret, time.Now(), otp.DefaultTOTPParam)
if err != nil {
panic(err)
}
fmt.Println(code)
}
func GenerateTOTPURL ¶
GenerateTOTPURL constructs an otpauth:// URL for configuring TOTP-based authenticators (e.g., Google Authenticator). The URL includes the issuer, account name, secret, and TOTP parameters.
Example output: otpauth://totp/Example:alice@domain.com?secret=BASE32ENCODEDSECRET&issuer=Example&algorithm=SHA1&digits=6&period=30
Example ¶
package main
import (
"fmt"
"github.com/ja7ad/otp"
)
func main() {
secret, err := otp.RandomSecret(otp.SHA1)
if err != nil {
panic(err)
}
url, err := otp.GenerateTOTPURL(otp.URLParam{
Issuer: "https://example.com",
Secret: secret,
AccountName: "foobar",
Period: otp.DefaultTOTPParam.Period,
Digits: otp.DefaultTOTPParam.Digits,
Algorithm: otp.DefaultTOTPParam.Algorithm,
})
if err != nil {
panic(err)
}
fmt.Println(url.String())
}
func IsKnownSuite ¶
IsKnownSuite reports whether the given raw OCRA suite string is registered in the internal list of known supported suite configurations.
This is useful for validating user input or performing discovery checks.
Example:
if !IsKnownSuite(input) {
return fmt.Errorf("unsupported OCRA suite")
}
func LeftPadHex ¶
LeftPadHex returns the input hex string left-padded with '0' characters until it reaches totalLen characters. If the input string is longer than or equal to totalLen, it returns the rightmost totalLen characters.
func ListSuites ¶
func ListSuites() []string
ListSuites returns all registered and supported OCRA raw suite strings. This is useful for introspection, documentation, CLI display, or API discovery.
func MustHexPadLeft ¶
MustHexPadLeft decodes a hex string after left-padding it to the desired byte length. The size parameter specifies the desired number of bytes; the function left-pads the hex string to size*2 characters. It panics if the hex decoding fails.
func ParseDecimal64BigEndian ¶
ParseDecimal64BigEndian is similar to ParseDecimalToBigEndian8. It converts a decimal string to an 8-byte big-endian slice by parsing the string as a uint64.
func ParseDecimalChallengeRFC6287 ¶
ParseDecimalChallengeRFC6287 converts a decimal challenge string to a 128-byte value as required by RFC 6287. The conversion process is as follows:
- Parse the input as a decimal number into a big.Int.
- Convert the number to an uppercase hexadecimal string.
- Right-pad the hex string with '0's until its length is 256 characters (which corresponds to 128 bytes).
- Decode the padded hex string into a byte slice.
func ParseDecimalToBigEndian8 ¶
ParseDecimalToBigEndian8 converts a decimal string to an 8-byte big-endian representation. It interprets the input string as a base-10 unsigned integer, then returns an 8-byte slice where the most-significant byte is at index 0. This is useful for encoding counters or similar values.
func ParseHexTimestamp ¶
ParseHexTimestamp converts a hex-encoded timestamp string to an 8-byte slice. It left-pads the input string with '0' characters until it is 16 hex digits long (i.e. 8 bytes), then decodes the padded string.
func RandomSecret ¶
RandomSecret returns a base32-encoded random secret for the given algorithm. The secret is of appropriate byte length for RFC-compliant HOTP/TOTP implementations.
func To8ByteBigEndian ¶
To8ByteBigEndian converts a uint64 value into an 8-byte big-endian slice. This is a convenience function for encoding counters or time values.
func ValidateHOTP ¶
ValidateHOTP checks whether the given HOTP code is valid for the provided secret and counter. Returns true if valid, false otherwise. Uses constant-time comparison internally. If `param` is nil, DefaultHOTPParam is used.
Example ¶
package main
import (
"fmt"
"github.com/ja7ad/otp"
)
func main() {
secret, err := otp.RandomSecret(otp.SHA1)
if err != nil {
panic(err)
}
counter := uint64(1)
code, err := otp.GenerateHOTP(secret, counter, otp.DefaultHOTPParam)
if err != nil {
panic(err)
}
fmt.Println(code)
ok, err := otp.ValidateHOTP(secret, code, counter, otp.DefaultTOTPParam)
if err != nil {
panic(err)
}
fmt.Println(ok)
}
func ValidateOCRA ¶
ValidateOCRA checks whether a provided OTP code is valid for the given OCRA suite, secret, and input parameters.
The `secret` should be a base32-encoded key, which is decoded before validation. The `code` is the OTP to validate (usually entered by the user). The `suite` and `input` define the expected context in which the OTP should have been generated.
Returns true if the code is valid, false otherwise. An error is returned if decoding the secret fails or if validation encounters a critical issue.
Example ¶
package main
import (
"fmt"
"github.com/ja7ad/otp"
)
func main() {
secret, err := otp.RandomSecret(otp.SHA1)
if err != nil {
panic(err)
}
suite := otp.MustRawSuite("OCRA-1:HOTP-SHA1-6:QN08")
code, err := otp.GenerateOCRA(secret, suite, otp.OCRAInput{
Challenge: []byte("12345678"),
})
if err != nil {
panic(err)
}
ok, err := otp.ValidateOCRA(code, secret, suite, otp.OCRAInput{
Challenge: []byte("12345678"),
})
if err != nil {
panic(err)
}
fmt.Println(ok)
}
func ValidateTOTP ¶
ValidateTOTP checks whether the given TOTP code is valid for the specified time and secret. It uses constant-time comparison to avoid timing attacks. Returns true if the code is valid, false otherwise. If param is nil, DefaultTOTPParam is used.
Example ¶
package main
import (
"fmt"
"github.com/ja7ad/otp"
"time"
)
func main() {
secret, err := otp.RandomSecret(otp.SHA1)
if err != nil {
panic(err)
}
t := time.Now()
code, err := otp.GenerateTOTP(secret, t, otp.DefaultTOTPParam)
if err != nil {
panic(err)
}
fmt.Println(code)
ok, err := otp.ValidateTOTP(secret, code, t, otp.DefaultTOTPParam)
if err != nil {
panic(err)
}
fmt.Println(ok)
}
Types ¶
type Algorithm ¶
type Algorithm uint8
Algorithm defines the hashing algorithm used in the HMAC function. Supported values are SHA1, SHA256, and SHA512, per RFC 6238.
func AlgorithmFromStr ¶
type ChallengeFormat ¶
type ChallengeFormat int
ChallengeFormat enumerates the possible challenge formats.
const ( ChallengeNone ChallengeFormat = iota ChallengeNumeric08 // QN08 → typically 8-digit numeric ChallengeNumeric10 // QN10 → typically 10-digit numeric ChallengeAlpha08 // QA08 → typically 8-char alphanumeric ChallengeAlpha10 // QA10 → typically 10-char alphanumeric ChallengeHex08 // QH08 → typically 8-hex-digit challenge ChallengeHex10 // QH10 → typically 10-hex-digit challenge )
type OCRAInput ¶
type OCRAInput struct {
// Counter is an 8-byte big-endian value, used if the suite has IncludeCounter=true.
// Typically incremented for each new OCRA calculation.
Counter []byte
// Challenge is the raw challenge data. If the suite has IncludeChallenge=true,
// we typically expect a certain minimum length (8 or 10 bytes) and a max of 128 bytes.
Challenge []byte
// Password is the hashed PIN or passphrase. If the suite has IncludePassword=true
// and the suite's PasswordHash is e.g. PasswordSHA1, we expect the length to match
// that hash (20 bytes for SHA-1, etc.).
Password []byte
// SessionInfo is optional user or system data (e.g. channel binding info), up to 128 bytes.
// Only used if IncludeSession=true.
SessionInfo []byte
// Timestamp is an 8-byte big-endian representation of the time-step
// if IncludeTimestamp=true (e.g. for T1M). Must be exactly 8 bytes.
Timestamp []byte
}
OCRAInput holds the raw data that will be concatenated (in the order defined by RFC6287) for the HMAC computation. Which fields are required or optional depends on the SuiteConfig (cfg).
func HexInputToOCRA ¶
func (OCRAInput) Validate ¶
func (in OCRAInput) Validate(cfg SuiteConfig) error
Validate checks that the input matches the suite's requirements.
type Param ¶
type Param struct {
// Digits is the length of the generated OTP code (commonly 6 or 8).
Digits Digits
// Period is the time step in seconds for TOTP (e.g., 30 means OTP changes every 30s).
// This is not used in HOTP.
Period uint
// Skew is the allowed number of time steps (forward/backward) during TOTP validation
// to account for clock drift between client and server.
// security: A larger Skew increases the chance of a brute-force hit, max 10.
// default for hotp is 2
Skew uint
// Algorithm specifies which HMAC hashing algorithm to use (SHA1, SHA256, SHA512).
Algorithm Algorithm
}
Param defines configuration parameters for generating and validating OTPs.
type PasswordHashAlgorithm ¶
type PasswordHashAlgorithm int
PasswordHashAlgorithm enumerates the possible PIN/password hash types.
const ( PasswordNone PasswordHashAlgorithm = iota PasswordSHA1 PasswordSHA256 PasswordSHA512 )
type RawSuite ¶
type RawSuite struct {
SuiteConfig
}
func MustRawSuite ¶
MustRawSuite is like NewRawSuite but panics if the suite string is invalid or unknown. It is intended for use in tests, internal registrations, or hardcoded trusted values where failure is not expected.
Example:
suite := MustRawSuite("OCRA-1:HOTP-SHA1-6:QN08")
func (RawSuite) Config ¶
func (r RawSuite) Config() SuiteConfig
type Suite ¶
type Suite interface {
// Config returns the parsed SuiteConfig from this suite.
Config() SuiteConfig
// String returns the raw string representation of the suite.
String() string
// Validate checks whether the suite definition is structurally and semantically valid.
// For RawSuite, this includes parsing the raw string and validating the resulting SuiteConfig.
// For SuiteConfig, this includes checks on hash algorithm, digit length, challenge format,
// and consistency of included input fields.
//
// It returns an error if the suite is invalid or unsupported.
Validate() error
}
Suite represents an abstract OCRA suite definition. It allows OCRA-related functions to accept either a raw string suite (e.g., "OCRA-1:HOTP-SHA1-6:QN08") or a pre-parsed SuiteConfig.
The Config method parses and returns the normalized SuiteConfig structure, enabling efficient downstream processing. The String method returns the original suite string representation.
This interface enables both ergonomic and performance-friendly usage:
DeriveOCRA(RawSuite("OCRA-1:HOTP-SHA1-6:QN08"), secret, inputs)
DeriveOCRA(ParsedSuite{...}, secret, inputs)
All internal logic operates on SuiteConfig, regardless of input type.
func NewRawSuite ¶
NewRawSuite returns a Suite instance based on a raw OCRA suite string. It looks up the string in the list of known, pre-registered suite definitions and returns the corresponding RawSuite.
If the raw suite string is not found or the associated SuiteConfig is invalid, it returns an appropriate error.
This is the recommended way to safely create a Suite from raw input at runtime. You can find raw suites by ListSuites function.
func NewSuite ¶
func NewSuite(cfg SuiteConfig) (Suite, error)
NewSuite returns a validated Suite implementation from a given SuiteConfig. The function first verifies the internal consistency of the config via Validate(), then ensures that the SuiteConfig corresponds to a known, pre-registered raw suite.
If the suite is valid and known, it returns a Suite interface that can be used for deriving OCRA codes. Otherwise, it returns an appropriate error.
This method is useful for users who construct SuiteConfig programmatically but still want strict adherence to known suite definitions.
Returns ErrInvalidRawSuite if the suite's Raw field is not registered.
type SuiteConfig ¶
type SuiteConfig struct {
// Original suite string (useful for logging, debug, etc.)
Raw string `json:"-"`
// OTP parameters
Hash Algorithm `json:"hash"` // SHA1, SHA256, SHA512
Digits int `json:"digits"` // OTP digits: 6, 7, 8
// Challenge type
Challenge ChallengeFormat `json:"challenge"` // QN08, QA10, etc.
// Input flags (used to determine which inputs are expected)
IncludeCounter bool `json:"include_counter,omitempty"` // C
IncludeChallenge bool `json:"include_challenge,omitempty"` // Q (always true if Challenge is set)
IncludePassword bool `json:"include_password,omitempty"` // P
IncludeSession bool `json:"include_session,omitempty"` // S
IncludeTimestamp bool `json:"include_timestamp,omitempty"` // T
// Extra metadata
PasswordHash PasswordHashAlgorithm `json:"password_hash"` // PSHA1, PSHA256, PSHA512 (optional)
TimeStep int `json:"time_step,omitempty"` // T1, T2, etc. (in seconds; 0 = not used)
}
func SuiteConfigFromRaws ¶
func SuiteConfigFromRaws(rawSuite string) SuiteConfig
func (SuiteConfig) Config ¶
func (cfg SuiteConfig) Config() SuiteConfig
func (SuiteConfig) String ¶
func (cfg SuiteConfig) String() string
func (SuiteConfig) Validate ¶
func (cfg SuiteConfig) Validate() error
type URLParam ¶
type URLParam struct {
// Name of the issuing Organization/Company.
Issuer string
// Name of the User's Account (eg, email address)
AccountName string
// Number of seconds a TOTP hash is valid for. Defaults to 30 seconds.
Period uint
// Secret to store. Defaults to a randomly generated secret of SecretSize. You should generally leave this empty.
Secret string
// Digits to request. Defaults to 6.
Digits Digits
// Algorithm to use for HMAC. Defaults to SHA1.
Algorithm Algorithm
}