Skip to main content
Glama
key-derivation.ts9.98 kB
import { randomBytes, pbkdf2Sync, scryptSync, timingSafeEqual } from 'crypto'; import { logger } from '../../utils/logger'; export interface KeyDerivationParams { password: string; salt: Buffer; iterations: number; keyLength: number; digest: string; } export interface KeyDerivationResult { key: Buffer; salt: Buffer; iterations: number; keyLength: number; digest: string; derivationTime: number; } export interface ScryptParams { password: string; salt: Buffer; keyLength: number; options: { cost: number; blockSize: number; parallelization: number; maxmem?: number; }; } export class SecureKeyDerivation { // NIST SP 800-63B compliant minimum parameters private static readonly MIN_PBKDF2_ITERATIONS = 100000; private static readonly MIN_KEY_LENGTH = 32; // 256 bits private static readonly MIN_SALT_LENGTH = 32; // 256 bits private static readonly RECOMMENDED_DIGEST = 'sha256'; // Scrypt parameters (RFC 7914 recommendations) private static readonly SCRYPT_PARAMS = { cost: 32768, // N parameter (CPU/memory cost) blockSize: 8, // r parameter (block size) parallelization: 1, // p parameter (parallelization) maxmem: 128 * 1024 * 1024 // 128MB memory limit }; /** * Derive key using PBKDF2 with secure parameters and timing validation */ public static deriveKey(params: KeyDerivationParams): Buffer { try { // Validate parameters this.validateParameters(params); const startTime = performance.now(); // Perform PBKDF2 key derivation const derivedKey = pbkdf2Sync( params.password, params.salt, params.iterations, params.keyLength, params.digest ); const endTime = performance.now(); const derivationTime = endTime - startTime; logger.debug('PBKDF2 key derivation completed', { iterations: params.iterations, keyLength: params.keyLength, digest: params.digest, derivationTime: `${derivationTime.toFixed(2)}ms` }); // Validate minimum derivation time for security if (derivationTime < 100) { logger.warn('Key derivation completed too quickly - may indicate weak parameters', { derivationTime, iterations: params.iterations }); } return derivedKey; } catch (error) { logger.error('Key derivation failed', { error }); throw new Error('Key derivation failed'); } } /** * Derive key with automatic parameter tuning for target derivation time */ public static deriveKeyWithTiming( password: string, salt: Buffer, targetTime: number = 250, // Target 250ms derivation time keyLength: number = 32, digest: string = 'sha256' ): KeyDerivationResult { try { // Start with minimum secure iterations let iterations = this.MIN_PBKDF2_ITERATIONS; let derivationTime = 0; // Benchmark to find optimal iterations for target time const testParams: KeyDerivationParams = { password, salt, iterations, keyLength, digest }; const startTime = performance.now(); const derivedKey = this.deriveKey(testParams); const endTime = performance.now(); derivationTime = endTime - startTime; // Adjust iterations if derivation is too fast if (derivationTime < targetTime) { const scaleFactor = targetTime / derivationTime; iterations = Math.floor(iterations * scaleFactor); // Ensure we don't go below minimum iterations = Math.max(iterations, this.MIN_PBKDF2_ITERATIONS); // Re-derive with adjusted parameters testParams.iterations = iterations; const adjustedStartTime = performance.now(); const finalKey = this.deriveKey(testParams); const adjustedEndTime = performance.now(); derivationTime = adjustedEndTime - adjustedStartTime; // Clear the test key derivedKey.fill(0); return { key: finalKey, salt, iterations, keyLength, digest, derivationTime }; } return { key: derivedKey, salt, iterations, keyLength, digest, derivationTime }; } catch (error) { logger.error('Timed key derivation failed', { error }); throw new Error('Timed key derivation failed'); } } /** * Derive key using scrypt (alternative to PBKDF2 for memory-hard derivation) */ public static deriveKeyScrypt(params: ScryptParams): Buffer { try { this.validateScryptParams(params); const startTime = performance.now(); const derivedKey = scryptSync( params.password, params.salt, params.keyLength, params.options ); const endTime = performance.now(); const derivationTime = endTime - startTime; logger.debug('Scrypt key derivation completed', { keyLength: params.keyLength, cost: params.options.cost, blockSize: params.options.blockSize, parallelization: params.options.parallelization, derivationTime: `${derivationTime.toFixed(2)}ms` }); return derivedKey; } catch (error) { logger.error('Scrypt key derivation failed', { error }); throw new Error('Scrypt key derivation failed'); } } /** * Generate cryptographically secure salt */ public static generateSalt(length: number = this.MIN_SALT_LENGTH): Buffer { if (length < this.MIN_SALT_LENGTH) { throw new Error(`Salt length must be at least ${this.MIN_SALT_LENGTH} bytes`); } return randomBytes(length); } /** * Validate PBKDF2 parameters meet security requirements */ public static validateParameters(params: KeyDerivationParams): boolean { const errors: string[] = []; // Validate iterations if (params.iterations < this.MIN_PBKDF2_ITERATIONS) { errors.push(`Iterations must be at least ${this.MIN_PBKDF2_ITERATIONS}`); } // Validate key length if (params.keyLength < this.MIN_KEY_LENGTH) { errors.push(`Key length must be at least ${this.MIN_KEY_LENGTH} bytes`); } // Validate salt length if (params.salt.length < this.MIN_SALT_LENGTH) { errors.push(`Salt length must be at least ${this.MIN_SALT_LENGTH} bytes`); } // Validate digest algorithm const supportedDigests = ['sha256', 'sha384', 'sha512']; if (!supportedDigests.includes(params.digest)) { errors.push(`Digest must be one of: ${supportedDigests.join(', ')}`); } // Validate password is not empty if (!params.password || params.password.length === 0) { errors.push('Password cannot be empty'); } if (errors.length > 0) { throw new Error(`Invalid key derivation parameters: ${errors.join(', ')}`); } return true; } /** * Validate scrypt parameters */ private static validateScryptParams(params: ScryptParams): boolean { const errors: string[] = []; // Validate key length if (params.keyLength < this.MIN_KEY_LENGTH) { errors.push(`Key length must be at least ${this.MIN_KEY_LENGTH} bytes`); } // Validate salt length if (params.salt.length < this.MIN_SALT_LENGTH) { errors.push(`Salt length must be at least ${this.MIN_SALT_LENGTH} bytes`); } // Validate scrypt parameters if (params.options.cost < 16384) { errors.push('Scrypt cost parameter too low (minimum 16384)'); } if (params.options.blockSize < 8) { errors.push('Scrypt block size too low (minimum 8)'); } if (params.options.parallelization < 1) { errors.push('Scrypt parallelization must be at least 1'); } // Validate password is not empty if (!params.password || params.password.length === 0) { errors.push('Password cannot be empty'); } if (errors.length > 0) { throw new Error(`Invalid scrypt parameters: ${errors.join(', ')}`); } return true; } /** * Compare derived keys in constant time to prevent timing attacks */ public static constantTimeCompare(a: Buffer, b: Buffer): boolean { if (a.length !== b.length) { return false; } return timingSafeEqual(a, b); } /** * Estimate optimal PBKDF2 iterations for target derivation time */ public static estimateOptimalIterations( targetTime: number = 250, password: string = 'test', keyLength: number = 32, digest: string = 'sha256' ): number { try { const testSalt = this.generateSalt(); const testIterations = 10000; // Baseline test const startTime = performance.now(); pbkdf2Sync(password, testSalt, testIterations, keyLength, digest); const endTime = performance.now(); const testTime = endTime - startTime; const scaleFactor = targetTime / testTime; const estimatedIterations = Math.floor(testIterations * scaleFactor); // Ensure minimum security requirements return Math.max(estimatedIterations, this.MIN_PBKDF2_ITERATIONS); } catch (error) { logger.error('Failed to estimate optimal iterations', { error }); return this.MIN_PBKDF2_ITERATIONS; } } /** * Get recommended scrypt parameters for different security levels */ public static getScryptParams(securityLevel: 'standard' | 'high' | 'maximum' = 'standard') { const params = { standard: { cost: 16384, blockSize: 8, parallelization: 1, maxmem: 64 * 1024 * 1024 // 64MB }, high: { cost: 32768, blockSize: 8, parallelization: 1, maxmem: 128 * 1024 * 1024 // 128MB }, maximum: { cost: 65536, blockSize: 8, parallelization: 2, maxmem: 256 * 1024 * 1024 // 256MB } }; return params[securityLevel]; } } export default SecureKeyDerivation;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/perfecxion-ai/secure-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server