Table of Contents

Hotp Class

Definition

Namespace
Bodu.Security.Cryptography
Assembly
Bodu.Security.Cryptography.dll
Package
Bodu.Security.Cryptography 1.2.0
Source
Hotp.GenerateCode.cs

Provides the HMAC-based One-Time Password (HOTP) algorithm defined in RFC 4226, generating and verifying the counter-based codes used for two-factor authentication. This class cannot be instantiated.

public static class Hotp
Inheritance
Hotp
Inherited Members

Examples

byte[] secret = Base32.Decode("JBSWY3DPEHPK3PXP");  // from an otpauth:// URI

string code = Hotp.GenerateCode(secret, counter: 0);          // "996554" (6 digits, SHA-1)
bool ok = Hotp.VerifyCode(secret, userInput, counter: 0);

Remarks

HOTP derives a short decimal code from a shared secret and a monotonically increasing counter: it computes an HMAC of the 8-byte big-endian counter under the secret, applies the RFC 4226 §5.3 dynamic-truncation function to select a 31-bit value from the MAC, and reduces that value modulo 10^digits to produce a zero-padded decimal string. The counter is advanced by one on each successful authentication; Totp layers a time-derived counter on top of the same construction.

The shared secret is supplied as raw key bytes. Authenticator applications conventionally exchange the secret as a Base32 string inside an otpauth:// URI; decode it to bytes before calling these methods (for example with Bodu.Text.Encoding.Base32) - this type deliberately takes no dependency on a particular text encoding. RFC 4226 recommends a secret of at least 128 bits, and 160 bits (the SHA-1 output length) for full strength.

Like the rest of the library, this implementation offers best-effort side-channel resistance and has not been independently audited. Code verification uses FixedTimeEquals(ReadOnlySpan<byte>, ReadOnlySpan<byte>) so that a valid-but-wrong code is not distinguishable from an invalid one by comparison timing.

Methods

GenerateCode(ReadOnlySpan<byte>, long, int, OtpHashAlgorithm)

Generates the RFC 4226 HOTP code for the specified secret and counter.

public static string GenerateCode(ReadOnlySpan<byte> secret, long counter, int digits = 6, OtpHashAlgorithm algorithm = OtpHashAlgorithm.Sha1)

Parameters

secret ReadOnlySpan<byte>

The shared secret key, as raw bytes.

counter long

The moving counter value, advanced by one on each successful authentication.

digits int

The number of decimal digits in the returned code.

algorithm OtpHashAlgorithm

The HMAC hash algorithm to use.

Returns

string

The zero-padded decimal code, exactly digits characters long.

Remarks

The result is returned as a string rather than an integer because a code's leading zeros are significant - for example a computed value of 84204 is the six-digit code "084204".

Exceptions

ArgumentOutOfRangeException

digits is less than 6 or greater than 8, or algorithm is not a defined OtpHashAlgorithm value.

VerifyCode(ReadOnlySpan<byte>, ReadOnlySpan<char>, long, int, OtpHashAlgorithm)

Verifies a candidate code against the RFC 4226 HOTP code for the specified secret and counter.

public static bool VerifyCode(ReadOnlySpan<byte> secret, ReadOnlySpan<char> code, long counter, int digits = 6, OtpHashAlgorithm algorithm = OtpHashAlgorithm.Sha1)

Parameters

secret ReadOnlySpan<byte>

The shared secret key, as raw bytes.

code ReadOnlySpan<char>

The candidate code supplied by the user.

counter long

The counter value to test.

digits int

The expected number of decimal digits.

algorithm OtpHashAlgorithm

The HMAC hash algorithm to use.

Returns

bool

true if code matches the code computed for counter; otherwise, false.

Remarks

The comparison is performed in constant time. A code whose length differs from digits returns false immediately, since the length is not secret.

Exceptions

ArgumentOutOfRangeException

digits is less than 6 or greater than 8, or algorithm is not a defined OtpHashAlgorithm value.

VerifyCode(ReadOnlySpan<byte>, ReadOnlySpan<char>, long, int, out long, int, OtpHashAlgorithm)

Verifies a candidate code against a window of counter values, supporting the RFC 4226 §7.4 resynchronization strategy, and reports the counter that matched.

public static bool VerifyCode(ReadOnlySpan<byte> secret, ReadOnlySpan<char> code, long counter, int lookAhead, out long matchedCounter, int digits = 6, OtpHashAlgorithm algorithm = OtpHashAlgorithm.Sha1)

Parameters

secret ReadOnlySpan<byte>

The shared secret key, as raw bytes.

code ReadOnlySpan<char>

The candidate code supplied by the user.

counter long

The first (lowest) counter value to test.

lookAhead int

The number of additional counter values to test beyond counter.

matchedCounter long

When this method returns true, the counter value that matched; otherwise, -1. The server should store matchedCounter + 1 as the next expected counter.

digits int

The expected number of decimal digits.

algorithm OtpHashAlgorithm

The HMAC hash algorithm to use.

Returns

bool

true if code matches any counter in the inclusive range [counter, counter + lookAhead]; otherwise, false.

Remarks

The full window is always scanned - matching does not short-circuit - so the loop's duration does not reveal which counter matched. A wide lookAhead weakens security by admitting more candidate codes; RFC 4226 recommends a small look-ahead combined with throttling of failed attempts.

Exceptions

ArgumentOutOfRangeException

digits is less than 6 or greater than 8, lookAhead is negative, or algorithm is not a defined OtpHashAlgorithm value.

Applies to

ProductVersions
.NET8, 10