hotp

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Jul 18, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package hotp implements HMAC-based one-time passwords as defined in RFC 4226, used for counter-based two-factor authentication codes. It is a standard-library port of the npm "hotp" / "otplib" style HOTP primitive, exposing the two operations applications actually need: Generate, which derives a one-time code from a shared secret and a counter, and Verify, which checks a user-supplied code against the expected value.

HOTP was standardized by the IETF in RFC 4226 (2005) as the foundation of the Initiative for Open Authentication (OATH). It exists so that a server and a hardware token or mobile app, sharing only a secret key, can independently arrive at the same short numeric code without any network round trip. HOTP is the counter-based cousin of TOTP: where TOTP feeds the current time window into the algorithm, HOTP feeds a monotonically increasing counter that both sides advance on each successful use. This port targets the same behavior as the JavaScript reference implementations and validates against the RFC 4226 Appendix D test vectors.

The algorithm works by computing an HMAC-SHA1 of the 8-byte big-endian counter using the secret as the key, then applying the "dynamic truncation" described in RFC 4226 section 5.3. The low four bits of the final HMAC byte select an offset into the 20-byte digest; four bytes are read from that offset, the top bit is masked off to avoid sign ambiguity, and the resulting 31-bit integer is reduced modulo 10^digits. The remainder is formatted as a zero-padded decimal string, so a 6-digit code such as "007236" keeps its leading zeros. HMAC-SHA1 is mandated by the standard here even though SHA-1 is deprecated for general hashing; HOTP's security rests on the HMAC construction and the secrecy of the key, not on SHA-1's collision resistance.

Generate takes the raw secret bytes, the counter and the desired number of digits, and returns the code. The digits argument follows the common default: any value less than or equal to zero is treated as 6, the canonical HOTP length, while callers may request other lengths (typically 6 to 8). The secret is used exactly as provided; if your secret is stored in Base32 (as it usually is in authenticator apps), decode it before calling Generate. Verify recomputes the expected code with the same parameters and compares it against the candidate using crypto/subtle.ConstantTimeCompare, so a mismatch cannot be distinguished from a match by timing. A wrong code, or a code computed for a different counter or digit length, causes Verify to report false.

Parity with the Node ecosystem is deliberate but narrow. This package implements only the standard HMAC-SHA1 HOTP path and offers no counter resynchronization window, no configurable hash algorithm, and no Base32 handling or otpauth:// URI generation; those concerns are left to the caller. Because Verify checks a single counter value, servers that must tolerate a token drifting ahead should call Verify across a small look-ahead range of counters and, on success, advance their stored counter past the matched value to prevent code reuse.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Generate

func Generate(secret []byte, counter uint64, digits int) string

Generate computes an RFC 4226 HOTP value for the given secret and counter, returning a zero-padded decimal string of the requested number of digits.

Example

ExampleGenerate derives an RFC 4226 one-time password from a shared secret and a counter. The secret is used exactly as provided, so a Base32-encoded secret from an authenticator app must be decoded to raw bytes first. The counter is a value both the client and server track and advance on each use. Requesting 6 digits produces the canonical HOTP length, and the result is zero-padded so it always has exactly that many characters. This example uses the RFC 4226 Appendix D test secret, whose counter 0 code is "755224".

package main

import (
	"fmt"

	"github.com/malcolmston/express/hotp"
)

func main() {
	secret := []byte("12345678901234567890")
	code := hotp.Generate(secret, 0, 6)
	fmt.Println(code)
}
Output:
755224

func Verify

func Verify(secret []byte, counter uint64, code string, digits int) bool

Verify reports whether code matches the HOTP value for the secret and counter.

Example

ExampleVerify checks a user-supplied code against the expected value for a given secret and counter. Verify recomputes the code with Generate and compares the two in constant time, so a mismatch cannot be distinguished from a match by timing. It returns true only when the candidate matches the code for that exact secret, counter and digit length. Here the correct code for counter 0 verifies successfully while an obviously wrong code does not.

package main

import (
	"fmt"

	"github.com/malcolmston/express/hotp"
)

func main() {
	secret := []byte("12345678901234567890")
	fmt.Println(hotp.Verify(secret, 0, "755224", 6))
	fmt.Println(hotp.Verify(secret, 0, "000000", 6))
}
Output:
true
false

Types

This section is empty.

Jump to

Keyboard shortcuts

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