@otplib/core
Core types, interfaces, and utilities for the otplib OTP library suite.
Overview
@otplib/core provides the foundational abstractions for all otplib packages. It includes:
- Type Definitions: TypeScript interfaces for OTP operations
- Error Classes: Hierarchical error types for validation and crypto operations
- Validation Utilities: Input validation for secrets, counters, time, and tokens
- Crypto Abstraction: Pluggable crypto backend via
CryptoContext - Base32 Abstraction: Pluggable Base32 encoding/decoding via
Base32Context
Installation
npm install @otplib/core
pnpm add @otplib/core
yarn add @otplib/coreCore Concepts
Plugin Architecture
otplib uses a plugin architecture for both cryptographic operations and Base32 encoding:
import type { CryptoPlugin, Base32Plugin } from "@otplib/core";
// Crypto plugins implement HMAC and random byte generation
interface CryptoPlugin {
name: string;
hmac(
algorithm: HashAlgorithm,
key: Uint8Array,
data: Uint8Array,
): Uint8Array | Promise<Uint8Array>;
randomBytes(length: number): Uint8Array;
}
// Base32 plugins implement encoding and decoding
interface Base32Plugin {
name: string;
encode(data: Uint8Array, options?: Base32EncodeOptions): string;
decode(str: string): Uint8Array;
}CryptoContext
The CryptoContext class provides a unified interface for crypto operations:
import { createCryptoContext } from "@otplib/core";
import { NodeCryptoPlugin } from "@otplib/plugin-crypto-node";
const crypto = createCryptoContext(new NodeCryptoPlugin());
// Async HMAC computation
const digest = await crypto.hmac("sha1", key, data);
// Sync HMAC computation
const digest = crypto.hmacSync("sha1", key, data);
// Random bytes
const secret = crypto.randomBytes(20);Base32Context
The Base32Context class provides a unified interface for Base32 operations:
import { createBase32Context } from "@otplib/core";
import { ScureBase32Plugin } from "@otplib/plugin-base32-scure";
const base32 = createBase32Context(new ScureBase32Plugin());
// Encode binary data to Base32
const encoded = base32.encode(new Uint8Array([1, 2, 3]), { padding: false });
// Decode Base32 string to binary
const decoded = base32.decode("MFRGGZDFMZTWQ");Validation Utilities
Secret Validation
import { validateSecret, MIN_SECRET_BYTES, RECOMMENDED_SECRET_BYTES } from "@otplib/core";
try {
validateSecret(secretBytes);
} catch (error) {
if (error instanceof SecretTooShortError) {
console.error(`Secret must be at least ${MIN_SECRET_BYTES} bytes`);
} else if (error instanceof SecretTooLongError) {
console.error(`Secret must not exceed ${RECOMMENDED_SECRET_BYTES} bytes`);
}
}Counter Validation
import { validateCounter, MAX_COUNTER } from "@otplib/core";
try {
validateCounter(123n);
validateCounter(0);
} catch (error) {
if (error instanceof CounterNegativeError) {
console.error("Counter cannot be negative");
} else if (error instanceof CounterOverflowError) {
console.error(`Counter exceeds maximum (${MAX_COUNTER})`);
}
}Time and Period Validation
import { validateTime, validatePeriod, MIN_PERIOD, MAX_PERIOD } from "@otplib/core";
validateTime(Math.floor(Date.now() / 1000));
validatePeriod(30); // Default TOTP periodToken Validation
import { validateToken } from "@otplib/core";
try {
validateToken("123456", 6);
} catch (error) {
if (error instanceof TokenLengthError) {
console.error("Token has incorrect length");
} else if (error instanceof TokenFormatError) {
console.error("Token must contain only digits");
}
}Utility Functions
Counter Conversion
import { counterToBytes } from "@otplib/core";
// Convert counter to 8-byte big-endian array
const counterBytes = counterToBytes(42n);
// Output: Uint8Array [0, 0, 0, 0, 0, 0, 0, 42]Dynamic Truncation (RFC 4226)
import { dynamicTruncate } from '@otplib/core';
// Extract 31-bit integer from HMAC result
const hmacResult = new Uint8Array([...]); // 20 bytes for SHA-1
const truncated = dynamicTruncate(hmacResult);OTP Generation
import { truncateDigits } from "@otplib/core";
// Convert truncated value to OTP string
const otp = truncateDigits(123456789, 6);
// Output: "456789"Constant-Time Comparison
import { constantTimeEqual } from "@otplib/core";
// Timing-safe comparison to prevent timing attacks
const isValid = constantTimeEqual("123456", "123456");
const isValid = constantTimeEqual(uint8Array1, uint8Array2);Error Handling
All errors extend from OTPError:
import {
OTPError,
SecretError,
SecretTooShortError,
SecretTooLongError,
CounterError,
CounterNegativeError,
CounterOverflowError,
TimeError,
PeriodError,
TokenError,
TokenLengthError,
TokenFormatError,
CryptoError,
HMACError,
RandomBytesError,
} from "@otplib/core";
// Check error types
try {
// ... OTP operation
} catch (error) {
if (error instanceof SecretTooShortError) {
// Handle short secret
} else if (error instanceof CryptoError) {
// Handle crypto failure
}
}Type Definitions
Hash Algorithms
type HashAlgorithm = "sha1" | "sha256" | "sha512";OTP Digits
type Digits = 6 | 7 | 8;HOTP Options
interface HOTPOptions {
secret: Uint8Array;
counter: number | bigint;
algorithm?: HashAlgorithm;
digits?: Digits;
}TOTP Options
interface TOTPOptions {
secret: Uint8Array;
epoch?: number; // Unix time in seconds
algorithm?: HashAlgorithm;
digits?: Digits;
period?: number; // Time step in seconds (default: 30)
}Verification Options
interface HOTPVerifyOptions extends HOTPOptions {
token: string;
counterTolerance?: number | number[]; // Look-ahead window
}
interface TOTPVerifyOptions extends TOTPOptions {
token: string;
epochTolerance?: number | [number, number]; // Time tolerance in seconds
}
interface VerifyResult {
valid: boolean;
delta?: number; // Counter/time steps from expected value
}Related Packages
@otplib/hotp- HOTP implementation@otplib/totp- TOTP implementation@otplib/plugin-crypto-node- Node.js crypto plugin@otplib/plugin-crypto-web- Web Crypto API plugin@otplib/plugin-base32-scure- Base32 plugin using @scure/base
Documentation
Full documentation available at otplib.yeojz.dev:
License
MIT © 2026 Gerald Yeo