npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@synotech/crypto

v2.0.3

Published

Comprehensive cryptography library with Web3 integration, supporting symmetric/asymmetric encryption, digital signatures, JWT tokens, blockchain address generation, and secure key management

Readme

Cryptography

A comprehensive TypeScript cryptography library providing secure encryption, digital signatures, JWT token management, and Web3-compatible cryptographic operations.

Features

Installation

yarn install

Environment Setup

Quick Start

Generate secure cryptographic keys:

# Generate new .env file with secure keys
node generate.js generate

# Or show keys without saving
node generate.js show

Manual Setup

Create a .env file with the following variables:

CRYPTO_AES_ENCRYPTION_KEY=<64-character-hex-string>
CRYPTO_HMAC_SIGNING_KEY=<64-character-hex-string>

Key Requirements

  • Both keys must be exactly 64 hexadecimal characters (32 bytes)
  • Keys must contain only valid hex characters (0-9, a-f, A-F)
  • Use cryptographically secure random generation

Internal Entropy Generation

The library now features built-in entropy generation that eliminates the need for external key pool files:

  • Self-contained: No external pool.json file required
  • Cryptographically secure: Uses Node.js crypto.randomBytes() for all entropy generation
  • Dynamic generation: Fresh entropy generated on-demand for each operation
  • Backward compatible: Existing code works unchanged

Usage

Basic Usage (Environment Variables)

import { Cryptography } from './index'

const crypto = new Cryptography()

const encrypted = crypto.encrypt('Hello, World!')
const decrypted = crypto.decrypt(encrypted)

const signature = crypto.signature()
const isValid = crypto.signatureVerify(signature)

const token = crypto.jwtIssue({ userId: 123 }, '1h')
const payload = crypto.jwtVerify(token)

const apiKey = crypto.apiKey()
const isValidKey = crypto.apiKeyVerify(apiKey)

// OTP (One-Time Password) functionality
const otpEnrollment = crypto.otpEnrol({ identifier: '[email protected]', issuer: 'MyApp' })
const otpCode = crypto.otpIssue({ secret: otpEnrollment.secret })
const isValidOTP = crypto.otpVerify({ secret: otpEnrollment.secret, token: otpCode })

const slug = crypto.slug()
const customSlug = crypto.slug({ separator: '_', length: 5 })

Custom Configuration

const crypto = new Cryptography({
	encryptKey: 'your64hexencryptionkey...',
	encryptKeySingle: 'your64hexsigningkey...',
})

Mixed Configuration

const crypto = new Cryptography({
	encryptKey: 'explicit64hexkey...',
	// encryptKeySingle will use CRYPTO_HMAC_SIGNING_KEY from environment
})

🎮 Interactive Demo

Experience all cryptography features through our beautiful web interface:

# Start the demo server
npm run demo

# Open in browser: http://localhost:8080

Demo Features

  • 🎲 Key Generation: Random strings, predefined strengths, salts, crypto addresses
  • 🔒 Encryption: AES-256-GCM, one-way encryption, password hashing
  • ✍️ Digital Signatures: Generation, verification, key pairs
  • 🎫 JWT Tokens: Issue and verify tokens with custom payloads
  • 🔑 API Keys: Secure API key generation and verification with custom prefixes
  • 🏷️ Slug Generation: Human-readable unique identifiers with multiple dictionaries
  • 🔧 Utilities: Base64 encoding, environment key generation

The demo interface provides an interactive way to test all cryptographic operations with a modern, responsive design inspired by professional crypto tools.

API Reference

Constructor

The Cryptography class now features internal entropy generation for enhanced security and self-containment.

import { Cryptography } from './index'

// Basic initialization with environment variables
const crypto = new Cryptography()

// Custom configuration with explicit keys
const crypto = new Cryptography({
	encryptKey: 'your64hexencryptionkey...',
	encryptKeySingle: 'your64hexsigningkey...',
})

// Mixed configuration (some explicit, some from environment)
const crypto = new Cryptography({
	encryptKey: 'explicit64hexkey...',
	// encryptKeySingle will use CRYPTO_HMAC_SIGNING_KEY from environment
})

Constructor Options:

  • encryptKey?: string - AES-256 encryption key (64 hex chars) or uses CRYPTO_AES_ENCRYPTION_KEY env var
  • encryptKeySingle?: string - HMAC signing key (64 hex chars) or uses CRYPTO_HMAC_SIGNING_KEY env var

Internal Entropy Features:

  • Automatic entropy generation: No external files required
  • Cryptographically secure: Uses Node.js crypto.randomBytes()
  • Dynamic key generation: Fresh entropy for each operation
  • Backward compatible: Existing code works unchanged

Random String Generation

random(options: RandomOptions): string

Generate cryptographically secure random strings with customizable character sets.

Options:

  • length?: number - String length (default: 32)
  • useLowerCase?: boolean - Include lowercase letters (default: true)
  • useUpperCase?: boolean - Include uppercase letters (default: true)
  • useNumbers?: boolean - Include numbers (default: true)
  • useSpecial?: boolean - Include special characters (default: false)
  • useHex?: boolean - Include hex characters (default: false)
const random = crypto.random({})

console.log(random) // 'a1B2c3D4e5F6g7H8i9J0k1L2m3N4o5P6'

const password = crypto.random({
	length: 20,
	useLowerCase: true,
	useUpperCase: true,
	useNumbers: true,
	useSpecial: true,
})

console.log(password) // '*ajz:74,*ak0mN$9Hx2!'

const lowercase = crypto.random({
	length: 15,
	useLowerCase: true,
	useUpperCase: false,
	useNumbers: false,
	useSpecial: false,
})

console.log(lowercase) // 'abcdefghijklmno'

const hexString = crypto.random({
	length: 12,
	useLowerCase: false,
	useUpperCase: false,
	useNumbers: false,
	useSpecial: false,
	useHex: true,
})

console.log(hexString) // 'A1B2C3D4E5F6'

const edgeCase = crypto.random({ length: 1 })
console.log(edgeCase.length) // 1

const largeRandom = crypto.random({ length: 1000 })
console.log(largeRandom.length) // 1000

const uniqueResults = []
for (let i = 0; i < 100; i++) {
	uniqueResults.push(crypto.random({ length: 20 }))
}

console.log(new Set(uniqueResults).size) // 100 (all unique)

get(strength: KeyStrength): string

Generate predefined strength keys for specific use cases.

Strength Options:

  • decent_pw - 10 characters, alphanumeric
  • strong_pw - 15 characters, alphanumeric + special
  • ft_knox_pw - 30 characters, alphanumeric + special
  • ci_key - 32 characters, alphanumeric
  • 160_wpa - 20 characters, alphanumeric + special
  • 504_wpa - 63 characters, alphanumeric + special
  • 64_wep - 5 characters, hex only
  • 128_wep - 13 characters, hex only
  • 152_wep - 16 characters, hex only
  • 256_wep - 29 characters, hex only
crypto.get('decent_pw')
crypto.get('strong_pw') // 'i=SQ_qa3W[<RxoM'
crypto.get('ft_knox_pw') // 'P}U%H\OOYAYb;wc"3hgI,3Lz[gd-z]'
crypto.get('ci_key') // 'CeXHpM3nDgzdv0o3AkMCs3OuxzepLGW8'
crypto.get('160_wpa') // 'oHI#gR8z#h7BS>cZ!zH('
crypto.get('504_wpa') // '<os[g`s}u06jqt"Ea]t11,HsI[UipHD)%F";:9RhJ@kTU8GknLpMAXtoCzsJjT`'
crypto.get('64_wep') // '8911B'
crypto.get('128_wep') // '9F4E4F933BCCC'
crypto.get('152_wep') // '695E1EE96E483961'
crypto.get('256_wep') // 'AC7E866246BA6B71BF5D88A6861AB'

salt(): string

Generate cryptographic salt for password hashing.

const salt = crypto.salt()

console.log(salt) // '5eb63bbbe01eeed093cb22bb8f5acdc3'
console.log(salt.length) // 32 (16 bytes hex-encoded)

Encryption Methods

encrypt(data: string): string

Two-way AES-256-GCM encryption with authentication.

const plaintext = 'Hello, World!'
const encrypted = crypto.encrypt(plaintext)

const unicode = crypto.encrypt('🚀 Hello 世界 🌍')
const special = crypto.encrypt('!@#$%^&*()_+-=[]{}|;:,.<>?`~')

const number = crypto.encrypt(12345) // '12345'
const boolean = crypto.encrypt(true) // 'true'
const nullValue = crypto.encrypt(null) // 'null'
const undefinedValue = crypto.encrypt(undefined) // 'undefined'

const largeText = crypto.encrypt('a'.repeat(10000))

const jsonData = crypto.encrypt(
	JSON.stringify({
		message: 'Hello',
		number: 42,
		nested: { array: [1, 2, 3] },
	})
)

const multilineText = crypto.encrypt(`Line 1
Line 2
Line 3`)

const empty = crypto.encrypt('')

const differentForSameInput1 = crypto.encrypt('test')
const differentForSameInput2 = crypto.encrypt('test')

console.log(differentForSameInput1 !== differentForSameInput2) // true

decrypt(encryptedData: string): string

Decrypt AES-256-GCM encrypted data with authentication verification.

const plaintext = 'Hello, World!'
const encrypted = crypto.encrypt(plaintext)
const decrypted = crypto.decrypt(encrypted)

console.log(decrypted) // 'Hello, World!'

const original = '🚀 Unicode test 中文'
const encrypted = crypto.encrypt(original)
const decrypted = crypto.decrypt(encrypted)

console.log(decrypted === original) // true

try {
	crypto.decrypt('invalid:format:here')
} catch (error) {
	console.log('Decryption failed - invalid format')
}

encryptSingle(data: string): string

One-way HMAC-based encryption (deterministic output).

const data = 'Hello, World!'
const hash1 = crypto.encryptSingle(data)
const hash2 = crypto.encryptSingle(data)

console.log(hash1 === hash2) // true

const hash3 = crypto.encryptSingle('Different data')

console.log(hash1 !== hash3) // true
console.log(hash1) // 'YWxnQmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejEyMzQ1Ng=='

const numericHash = crypto.encryptSingle(12345)
const booleanHash = crypto.encryptSingle(true)
const nullHash = crypto.encryptSingle(null)

Digital Signatures

signature(): string

Generate encrypted digital signature using internal keys.

const signature = crypto.signature()

console.log(signature.split(':').length) // 3 components

signatureVerify(signature: string): boolean

Verify digital signature authenticity.

const signature = crypto.signature()
const isValid = crypto.signatureVerify(signature)

console.log(isValid) // true

try {
	const invalidSig = crypto.encrypt('unknown1 unknown2')
	crypto.signatureVerify(invalidSig)
} catch (error) {
	console.log('Unknown identifier')
}

Cryptographic Address Generation

address(): string

Generate blockchain-compatible crypto addresses.

const address = crypto.address()

console.log(address.length) // 32 characters
console.log(address.startsWith('k')) // true
console.log(address.match(/^k[a-zA-Z0-9]+$/)) // Valid format

const addresses = Array.from({ length: 5 }, () => crypto.address())
console.log(new Set(addresses).size) // 5 (all unique)

Password Hashing

password(password: string, salt: string): string

Generate secure password hash using PBKDF2.

const password = 'mySecurePassword123'
const salt = crypto.salt()
const hash = crypto.password(password, salt)
console.log(hash.length) // 512 characters (256 bytes hex-encoded)

const hash1 = crypto.password('test', salt)
const hash2 = crypto.password('test', salt)
console.log(hash1 === hash2) // true

const salt1 = crypto.salt()
const salt2 = crypto.salt()
const hash3 = crypto.password('test', salt1)
const hash4 = crypto.password('test', salt2)
console.log(hash3 !== hash4) // true

const unicodeHash = crypto.password('🚀 Password 世界', salt)
const specialHash = crypto.password('!@#$%^&*()', salt)

Key Pair Generation

privateKey(): string

Generate elliptic curve private key (secp521r1).

const privateKey = crypto.privateKey()
console.log(privateKey.includes('-----BEGIN PRIVATE KEY-----')) // true
console.log(privateKey.includes('-----END PRIVATE KEY-----')) // true
console.log(privateKey.length > 200) // true

const key1 = crypto.privateKey()
const key2 = crypto.privateKey()
console.log(key1 !== key2) // true

publicKey(privateKey: string): string

Derive public key from private key.

const privateKey = crypto.privateKey()
const publicKey = crypto.publicKey(privateKey)

console.log(publicKey.includes('-----BEGIN PUBLIC KEY-----')) // true
console.log(publicKey.includes('-----END PUBLIC KEY-----')) // true

const publicKey1 = crypto.publicKey(privateKey)
const publicKey2 = crypto.publicKey(privateKey)
console.log(publicKey1 === publicKey2) // true

try {
	crypto.publicKey('invalid-key')
} catch (error) {
	console.log('Invalid private key format')
}

publicKeyVerify({ privateKey, publicKey }): boolean

Verify key pair authenticity using digital signatures.

const privateKey = crypto.privateKey()
const publicKey = crypto.publicKey(privateKey)
const isValid = crypto.publicKeyVerify({ privateKey, publicKey })
console.log(isValid) // true

const privateKey1 = crypto.privateKey()
const privateKey2 = crypto.privateKey()
const publicKey2 = crypto.publicKey(privateKey2)

try {
	crypto.publicKeyVerify({
		privateKey: privateKey1,
		publicKey: publicKey2,
	})
} catch (error) {
	console.log('Failed to authenticate the public key')
}

JWT Token Management

jwtIssue(payload: object, expiresIn: string): string

Create signed JWT tokens with custom payloads.

const payload = {
	userId: '12345',
	username: 'testUser',
	role: 'admin',
}
const token = crypto.jwtIssue(payload, '1h')
console.log(token.split('.').length) // 3 (header.payload.signature)

const token1h = crypto.jwtIssue(payload, '1h') // 1 hour
const token30m = crypto.jwtIssue(payload, '30m') // 30 minutes
const token7d = crypto.jwtIssue(payload, '7d') // 7 days

const complexPayload = {
	userId: 'user_12345',
	username: '[email protected]',
	roles: ['admin', 'user', 'moderator'],
	permissions: {
		read: true,
		write: true,
		delete: false,
	},
	metadata: {
		lastLogin: '2025-07-10T10:00:00Z',
		loginCount: 42,
	},
}
const complexToken = crypto.jwtIssue(complexPayload, '2h')

const minimalToken = crypto.jwtIssue({}, '1h')

const specialPayload = {
	specialChars: '!@#$%^&*()_+-=[]{}|;\':",./<>?',
	unicode: '🔐 Security test 안전',
}
const specialToken = crypto.jwtIssue(specialPayload, '1h')

jwtVerify(token: string): object

Verify and decode JWT tokens.

const payload = { userId: '12345', role: 'admin' }
const token = crypto.jwtIssue(payload, '1h')
const verified = crypto.jwtVerify(token)

console.log(verified.userId) // '12345'
console.log(verified.role) // 'admin'
console.log(verified.iat) // Issue time (timestamp)
console.log(verified.exp) // Expiry time (timestamp)

const complexPayload = {
	array: [1, 2, 3],
	nested: { level1: { level2: { level3: 'deep' } } },
}
const complexToken = crypto.jwtIssue(complexPayload, '1h')
const complexVerified = crypto.jwtVerify(complexToken)

console.log(complexVerified.array) // [1, 2, 3]
console.log(complexVerified.nested.level1.level2.level3) // 'deep'

try {
	crypto.jwtVerify('invalid.token.format')
} catch (error) {
	console.log('Invalid token')
}

try {
	crypto.jwtVerify('header.payload.wrongsignature')
} catch (error) {
	console.log('Invalid signature')
}

Slug Generation

slug(options?: SlugOptions): string

Generate unique, human-readable slugs using multiple dictionaries.

TypeScript Interface:

interface SlugOptions {
	separator?: string
	length?: number
}

Options:

  • separator?: string - Separator between words (default: '-')
  • length?: number - Number of words in slug (default: 3)

Available Dictionaries: Uses all available dictionaries automatically: adjectives, colors, animals, names, languages, starWars, and countries for maximum variety and uniqueness.

// Basic slug generation
const slug = crypto.slug()
console.log(slug) // 'happy-blue-elephant'

// Custom separator and length
const customSlug = crypto.slug({
	separator: '_',
	length: 5,
})
console.log(customSlug) // 'brave_red_tiger_john_french'

// Different separators
const dotSlug = crypto.slug({ separator: '.', length: 4 })
console.log(dotSlug) // 'quick.green.vader.spain'

const spaceSlug = crypto.slug({ separator: ' ', length: 2 })
console.log(spaceSlug) // 'dark purple'

// Generate multiple unique slugs
const slugs = Array.from({ length: 5 }, () => crypto.slug())
console.log(new Set(slugs).size) // 5 (all unique)

// Handle length exceeding available dictionaries
const maxSlug = crypto.slug({ length: 10 })
console.log(maxSlug.split('-').length) // 7 (limited to available dictionaries)

// Single word slug
const singleSlug = crypto.slug({ length: 1 })
console.log(singleSlug) // 'magnificent'

Base64 Utilities

base64Encode(data: string): string

Encode strings to Base64 format.

const data = 'Hello, World!'
const encoded = crypto.base64Encode(data)

console.log(encoded) // 'SGVsbG8sIFdvcmxkIQ=='

const unicode = crypto.base64Encode('🚀 Hello 世界 🌍')
console.log(unicode) // Base64 representation of unicode string

const special = crypto.base64Encode('!@#$%^&*()_+-=[]{}|;:,.<>?`~')

const empty = crypto.base64Encode('')
console.log(empty) // ''

const large = crypto.base64Encode('a'.repeat(1000))
console.log(large.length > 1000) // true

base64Decode(encodedString: string): string

Decode Base64 strings to original format.

const encoded = 'SGVsbG8sIFdvcmxkIQ=='
const decoded = crypto.base64Decode(encoded)
console.log(decoded) // 'Hello, World!'

const original = 'Hello, World! 🚀'
const encoded = crypto.base64Encode(original)
const decoded = crypto.base64Decode(encoded)
console.log(decoded === original) // true

const largeData = 'a'.repeat(1000)
const encodedLarge = crypto.base64Encode(largeData)
const decodedLarge = crypto.base64Decode(encodedLarge)
console.log(decodedLarge === largeData) // true

try {
	crypto.base64Decode('invalid base64!')
} catch (error) {
	console.log('Invalid base64 string')
}

isBase64Encoded(data: string): boolean

Validate Base64 string format.

console.log(crypto.isBase64Encoded('SGVsbG8sIFdvcmxkIQ==')) // true
console.log(crypto.isBase64Encoded('YW55')) // true (no padding)
console.log(crypto.isBase64Encoded('')) // true (empty)

console.log(crypto.isBase64Encoded('Hello, World!')) // false
console.log(crypto.isBase64Encoded('SGVsbG8@IFdvcmxkIQ==')) // false (invalid char)

const testData = 'Test validation'
const encoded = crypto.base64Encode(testData)
console.log(crypto.isBase64Encoded(encoded)) // true
console.log(crypto.isBase64Encoded(testData)) // false

const testCases = ['Hello', 'SGVsbG8=', '123456', 'MTIzNDU2']
testCases.forEach((test) => {
	console.log(`${test}: ${crypto.isBase64Encoded(test)}`)
})

API Key Management

apiKey(options?: ApiKeyOptions): string

Generate secure API keys with HMAC-based authentication.

Options:

  • prefix?: string - 3-letter uppercase prefix (default: 'SYN')
  • length?: number - Hash length in characters, 16-128 (default: 48)
// Generate default API key
const apiKey = crypto.apiKey()
console.log(apiKey) // 'SYN_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6'

// Custom prefix
const devKey = crypto.apiKey({ prefix: 'DEV' })
console.log(devKey) // 'DEV_x1y2z3a4b5c6d7e8f9g0h1i2j3k4l5m6n7o8p9q0r1s2t3u4v5w6'

// Custom length
const longKey = crypto.apiKey({ length: 64 })
console.log(longKey) // 'SYN_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2'

// Custom prefix and length
const customKey = crypto.apiKey({ prefix: 'XYZ', length: 32 })
console.log(customKey) // 'XYZ_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6'

apiKeyVerify(apiKey: string, options?: { prefix?: string }): boolean

Verify the authenticity and format of API keys.

Options:

  • prefix?: string - Expected prefix to validate against
// Basic verification
const apiKey = crypto.apiKey()
const isValid = crypto.apiKeyVerify(apiKey)
console.log(isValid) // true

// Verify with custom prefix
const synKey = crypto.apiKey({ prefix: 'SYN' })
const isValidSyn = crypto.apiKeyVerify(synKey)
console.log(isValidSyn) // true

// Verify with prefix validation
const isValidWithPrefix = crypto.apiKeyVerify(synKey, { prefix: 'SYN' })
console.log(isValidWithPrefix) // true

// Prefix mismatch throws error
try {
	crypto.apiKeyVerify(synKey, { prefix: 'API' })
} catch (error) {
	console.log('API key prefix does not match expected prefix')
}

Security Features:

  • HMAC-SHA256 based key generation for cryptographic security
  • Entropy validation to prevent weak patterns
  • Format validation for prefix and hash structure
  • Constant-time comparison for security against timing attacks
  • Support for custom prefixes and variable lengths

OTP (One-Time Password)

The library provides comprehensive Time-based One-Time Password (TOTP) functionality compatible with popular authenticator apps like Google Authenticator, Authy, and Microsoft Authenticator.

otpEnrol(options: OTPEnrolOptions): { secret: string; uri: string; qr: string }

Enroll a user for OTP by generating a secret and returning configuration for authenticator apps.

Options:

  • identifier: string - Unique identifier for the user (email, username, etc.) - required
  • issuer?: string - The issuer name (app/service name). Defaults to 'Synotech Ai'
  • label?: string - Label for the OTP entry. Defaults to identifier
  • algorithm?: 'SHA1' | 'SHA256' | 'SHA512' - HMAC algorithm. Defaults to 'SHA1'
  • digits?: number - Number of digits in OTP code. Defaults to 5
  • period?: number - Time period for TOTP in seconds. Defaults to 120
// Basic enrollment
const enrollment = crypto.otpEnrol({
	identifier: '[email protected]',
})

console.log(enrollment)
// {
//     secret: 'JBSWY3DPEHPK3PXP',
//     uri: 'otpauth://totp/Synotech Ai:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=Synotech%20Ai&digits=5&period=120',
//     qr: 'otpauth://totp/Synotech Ai:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=Synotech%20Ai&digits=5&period=120'
// }

// Custom configuration
const customEnrollment = crypto.otpEnrol({
	identifier: '[email protected]',
	issuer: 'MyApp',
	label: 'John Doe - Production',
	algorithm: 'SHA256',
	digits: 6,
	period: 30,
})

// Use the QR code URI to generate QR codes for users
const qrCodeData = enrollment.uri
// Display this in a QR code for users to scan with their authenticator app

otpIssue(options: OTPIssueOptions): string

Generate a time-based OTP code using the provided secret.

Options:

  • secret: string - The base32 encoded secret key - required
  • timestamp?: number - Optional timestamp for code generation. Defaults to current time
// Generate current OTP code
const code = crypto.otpIssue({
	secret: 'JBSWY3DPEHPK3PXP',
})

console.log(code) // '12345' (5-digit code, valid for 120 seconds)

// Generate code for specific timestamp
const historicalCode = crypto.otpIssue({
	secret: 'JBSWY3DPEHPK3PXP',
	timestamp: 1640995200000,
})

console.log(historicalCode) // '78901'

// Use in authentication flow
const userSecret = enrollment.secret
const currentCode = crypto.otpIssue({ secret: userSecret })
console.log(`Current OTP: ${currentCode}`)

otpVerify(options: OTPVerifyOptions): boolean

Verify a time-based OTP code against the provided secret.

Options:

  • secret: string - The base32 encoded secret key - required
  • token: string - The OTP code to verify - required
  • window?: number - Time window for verification (number of periods). Defaults to 1
  • timestamp?: number - Optional timestamp for verification. Defaults to current time
// Basic verification
const isValid = crypto.otpVerify({
	secret: 'JBSWY3DPEHPK3PXP',
	token: '12345',
})

console.log(isValid) // true or false

// Verification with time window (allows codes from adjacent time periods)
const isValidWithWindow = crypto.otpVerify({
	secret: 'JBSWY3DPEHPK3PXP',
	token: '12345',
	window: 2, // Accepts codes from 2 periods before/after current time
})

// Complete authentication flow
const userSecret = 'JBSWY3DPEHPK3PXP'
const userInputCode = '12345'

if (crypto.otpVerify({ secret: userSecret, token: userInputCode })) {
	console.log('Authentication successful')
	// Grant access
} else {
	console.log('Invalid OTP code')
	// Deny access
}

// Verify historical code
const historicalVerification = crypto.otpVerify({
	secret: 'JBSWY3DPEHPK3PXP',
	token: '78901',
	timestamp: 1640995200000,
})

Complete OTP Workflow Example:

// 1. User enrollment
const enrollment = crypto.otpEnrol({
	identifier: '[email protected]',
	issuer: 'MySecureApp',
})

// 2. Store the secret securely (encrypted in database)
const encryptedSecret = crypto.encrypt(enrollment.secret)
// Save encryptedSecret to user's record

// 3. Show QR code to user for setup
console.log('Scan this QR code with your authenticator app:')
console.log(enrollment.uri)

// 4. During login - user provides OTP code
const userEnteredCode = '12345' // From user input

// 5. Retrieve and decrypt user's secret
const decryptedSecret = crypto.decrypt(encryptedSecret)

// 6. Verify the code
const isAuthenticated = crypto.otpVerify({
	secret: decryptedSecret,
	token: userEnteredCode,
	window: 1, // Allow 1 period tolerance for clock drift
})

if (isAuthenticated) {
	// User is authenticated, proceed with login
	console.log('OTP verification successful')
} else {
	// Authentication failed
	console.log('Invalid OTP code')
}

Security Features:

  • RFC 6238 compliant TOTP implementation
  • Cryptographically secure secret generation
  • Configurable time windows for clock drift tolerance
  • Support for multiple hash algorithms (SHA1, SHA256, SHA512)
  • Base32 encoded secrets for compatibility with authenticator apps
  • Customizable code length and validity periods

Testing

Run the complete test suite:

npm test

Tests cover:

  • Constructor validation and environment variable handling
  • Encryption/decryption operations
  • JWT token management
  • API key generation and verification
  • OTP enrollment, generation, and verification
  • Digital signature operations
  • Key generation and validation

Security Features

  • AES-256-GCM: Authenticated encryption with integrity verification
  • Internal Entropy Generation: Built-in cryptographically secure random number generation using Node.js crypto.randomBytes()
  • Dynamic Key Generation: Fresh entropy generated on-demand for each operation
  • Key Validation: Automatic validation of key length and format
  • Environment Security: Secure key management via environment variables
  • Buffer Safety: Proper buffer allocation and cleanup
  • Self-contained Security: No external file dependencies for entropy generation

Development

Demo Interface

# Start interactive demo
npm run demo

# Generate environment keys
npm run demo:keys

# Show usage examples
npm run demo:show

Key Generation Script

# Generate new .env file
node generate.js generate

# Show new keys without saving
node generate.js show

# Show help
node generate.js help

Demo Script

# Run usage demonstration
node dev.js

Project Structure

├── index.ts              # Main Cryptography class with internal entropy generation
├── __tests__/            # Test suite
│   ├── __config.ts       # Test configuration
│   ├── __constructor.spec.ts
│   ├── __encrypt.spec.ts
│   └── __jwt.spec.ts
├── generate.js      # Key generation utility
├── dev.js              # Usage demonstration
└── .env                 # Environment variables (AES & HMAC keys only)

License

ISC License

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass: npm test
  5. Submit a pull request

Security Notice

⚠️ Important: Never commit cryptographic keys to version control. Always use environment variables or secure key management systems in production.