Skip to content

RFC Implementations

otplib is fully compliant with the following RFCs:

RFC 4226 - HOTP: An HMAC-Based One-Time Password Algorithm

Key Requirements

RequirementStatusNotes
HMAC-SHA-1YesDefault hash algorithm
Counter as 8-byte big-endianYescounterToBytes() in core
Dynamic truncationYesdynamicTruncate() in core
Digit extraction (6-8 digits)YesConfigurable digits parameter
Look-ahead synchronizationYesConfigurable window parameter
Secret minimum 128 bitsYesvalidateSecret() enforces this
Secret recommended 160 bitsYesgenerateSecret() uses 20 bytes

Test Vectors

otplib passes all RFC 4226 Appendix D test vectors:

CounterExpectedActual
0755224755224
1287082287082
2359152359152
3969429969429
4338314338314
5254676254676
6287922287922
7162583162583
8399871399871
9520489520489

Intermediate HMAC-SHA-1 Values

otplib also verifies the intermediate HMAC-SHA-1 computation against RFC 4226 Appendix D. This ensures the crypto plugin is functioning correctly before truncation.

CounterHMAC-SHA-1 (hex)
0cc93cf18508d94934c64b65d8ba7667fb7cde4b0
175a48a19d4cbe100644e8ac1397eea747a2d33ab
20bacb7fa082fef30782211938bc1c5e70416ff44
366c28227d03a2d5529262ff016a1e6ef76557ece
4a904c900a64b35909874b33e61c5938a8e15ed1c
5a37e783d7b7233c083d4f62926c7a25f238d0316
6bc9cd28561042c83f219324d3c607256c03272ae
7a4fb960c0bc06e1eabb804e5b397cdc4b45596fa
81b3c89f65e6c9e883012052823443f048b4332db
91637409809a679dc698207310c8c7fc07290d9e5

Test File: packages/hotp/src/rfc4226.test.ts

RFC 6238 - TOTP: Time-Based One-Time Password Algorithm

Key Requirements

RequirementStatusNotes
Based on HOTPYesExtends HOTP implementation
Unix time (T0) = 0YesDefault epoch start
Time step (X) = 30YesDefault, configurable
SHA-1 supportYesDefault algorithm
SHA-256 supportYesOptional algorithm
SHA-512 supportYesOptional algorithm
Time drift toleranceYesConfigurable window

Test Vectors

otplib passes all RFC 6238 Appendix B test vectors:

Time (sec)ModeSHAExpectedActual
59TOTPSHA19428708294287082
59TOTPSHA2564611924646119246
59TOTPSHA5129069393690693936
1111111109TOTPSHA10708180407081804
1111111109TOTPSHA2566808477468084774
1111111109TOTPSHA5122509120125091201
1111111111TOTPSHA11405047114050471
1234567890TOTPSHA18900592489005924
2000000000TOTPSHA16927903769279037
20000000000TOTPSHA16535313065353130

Intermediate Time Step (T) Values

otplib verifies the time step calculation against the "Value of T (hex)" from RFC 6238 Appendix B. This ensures that time-to-counter conversion is accurate.

Time (sec)Value of T (hex)
590000000000000001
111111110900000000023523ec
111111111100000000023523ed
1234567890000000000273ef07
20000000000000000003f940aa
200000000000000000027bc86aa

RFC 4648 - The Base16, Base32, and Base64 Data Encodings

Key Requirements

RequirementStatusNotes
RFC 4648 Section 6 (Base32)ImplementedStandard alphabet
Padding character (=)ImplementedOptional padding
Case-insensitive decodingImplementedUppercase conversion
No padding for GAImplementedConfigurable
Test vectorsPassAll Section 10 vectors

Test Vectors

otplib passes all RFC 4648 Section 10 test vectors:

InputExpected (with padding)Expected (no padding)Actual (with padding)Actual (no padding)Status
"" (empty)""""""""Pass
"f""MY======""MY""MY======""MY"Pass
"fo""MZXQ====""MZXQ""MZXQ====""MZXQ"Pass
"foo""MZXW6===""MZXW6""MZXW6===""MZXW6"Pass
"foob""MZXW6YQ=""MZXW6YQ""MZXW6YQ=""MZXW6YQ"Pass
"fooba""MZXW6YTB""MZXW6YTB""MZXW6YTB""MZXW6YTB"Pass
"foobar""MZXW6YTBOI======""MZXW6YTBOI""MZXW6YTBOI======""MZXW6YTBOI"Pass

Google Authenticator Compatibility

Google Authenticator expects no padding and uses the standard RFC 4648 alphabet:

typescript
// GA-compatible encoding
const secret = base32.encode(randomBytes(20), { padding: false });
// Example: "JBSWY3DPEHPK3PXP"

otpauth:// URI Format

otplib fully implements the otpauth:// URI format used by Google Authenticator:

otpauth://TYPE/LABEL?PARAMETERS

Supported Parameters:

ParameterRequiredTypeDescriptionDefault
secretYesstringBase32-encoded secret-
issuerRecommendedstringProvider name-
algorithmNostringHash algorithmSHA1
digitsNonumberOTP length6
counterNo (HOTP only)numberInitial counter0
periodNo (TOTP only)numberTime step in seconds30

URI Generation

typescript
import { generateURI } from "otplib";
import { NodeCryptoPlugin } from "@otplib/plugin-crypto-node";
import { ScureBase32Plugin } from "@otplib/plugin-base32-scure";

const crypto = new NodeCryptoPlugin();
const base32 = new ScureBase32Plugin();

const uri = generateURI({
  issuer: "MyService",
  label: "user@example.com",
  secret: "JBSWY3DPEHPK3PXP",
  algorithm: "sha1",
  digits: 6,
  period: 30,
  crypto,
  base32,
});
// otpauth://totp/MyService:user@example.com?secret=...&issuer=MyService&algorithm=SHA1&digits=6&period=30

Test Integration

  1. Generate secret and URI in your app
  2. Display QR code from URI
  3. Scan with Google Authenticator
  4. Verify tokens generated by app

Security Considerations

RFC 4226 Section 7.3 - Throttling

Recommendation: Implement exponential backoff after failed attempts.

typescript
// Example rate limiting strategy
const MAX_ATTEMPTS = 3;
const LOCKOUT_DURATION = 30 * 60; // 30 minutes

function checkRateLimit(userId: string): boolean {
  const attempts = getFailedAttempts(userId);
  if (attempts >= MAX_ATTEMPTS) {
    const lockoutEnd = getLockoutEndTime(userId);
    if (Date.now() < lockoutEnd) {
      return false; // Locked out
    }
  }
  return true;
}

RFC 6238 Section 5.2 - Tolerance

Recommendation: Use smallest tolerance that provides good UX.

typescript
// RFC-compliant (past only, for transmission delay)
epochTolerance: [5, 0];

// Standard (allow ±30 seconds)
epochTolerance: 30;

// Strict (current period only)
epochTolerance: 0;

Timing Attack Prevention

otplib uses constant-time comparison for all token verifications:

typescript
function constantTimeEqual(a: string | Uint8Array, b: string | Uint8Array): boolean {
  const bufA = typeof a === "string" ? new TextEncoder().encode(a) : a;
  const bufB = typeof b === "string" ? new TextEncoder().encode(b) : b;

  if (bufA.length !== bufB.length) {
    return false;
  }

  let result = 0;
  for (let i = 0; i < bufA.length; i++) {
    result |= bufA[i] ^ bufB[i];
  }

  return result === 0;
}

Compliance Testing

All RFC compliance is verified through automated tests, see the rfc-test-vectors.ts file for more details.

References

Released under the MIT License.