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
secretReadOnlySpan<byte>The shared secret key, as raw bytes.
counterlongThe moving counter value, advanced by one on each successful authentication.
digitsintThe number of decimal digits in the returned code.
algorithmOtpHashAlgorithmThe HMAC hash algorithm to use.
Returns
- string
The zero-padded decimal code, exactly
digitscharacters 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
digitsis less than 6 or greater than 8, oralgorithmis 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
secretReadOnlySpan<byte>The shared secret key, as raw bytes.
codeReadOnlySpan<char>The candidate code supplied by the user.
counterlongThe counter value to test.
digitsintThe expected number of decimal digits.
algorithmOtpHashAlgorithmThe HMAC hash algorithm to use.
Returns
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
digitsis less than 6 or greater than 8, oralgorithmis 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
secretReadOnlySpan<byte>The shared secret key, as raw bytes.
codeReadOnlySpan<char>The candidate code supplied by the user.
counterlongThe first (lowest) counter value to test.
lookAheadintThe number of additional counter values to test beyond
counter.matchedCounterlongWhen this method returns true, the counter value that matched; otherwise,
-1. The server should storematchedCounter + 1as the next expected counter.digitsintThe expected number of decimal digits.
algorithmOtpHashAlgorithmThe HMAC hash algorithm to use.
Returns
- bool
true if
codematches 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
digitsis less than 6 or greater than 8,lookAheadis negative, oralgorithmis not a defined OtpHashAlgorithm value.
Applies to
| Product | Versions |
|---|---|
| .NET | 8, 10 |