Skip to main content
Glama
secure-encryption.ts8.56 kB
import { randomBytes, pbkdf2Sync, createCipheriv, createDecipheriv, timingSafeEqual } from 'crypto'; import { logger } from '../../utils/logger'; export interface EncryptedSecret { data: string; iv: string; tag: string; salt: string; algorithm: string; iterations: number; keyLength: number; digest: string; timestamp: number; } export interface SecureMFAConfig { keyDerivation: { algorithm: 'pbkdf2'; iterations: number; keyLength: number; digest: string; }; encryption: { algorithm: string; ivLength: number; tagLength: number; saltLength: number; }; totp: { secretLength: number; window: number; step: number; }; } export class SecureEncryption { private readonly config: SecureMFAConfig = { keyDerivation: { algorithm: 'pbkdf2', iterations: 100000, // NIST recommended minimum keyLength: 32, // 256 bits digest: 'sha256' }, encryption: { algorithm: 'aes-256-gcm', ivLength: 16, // 128 bits tagLength: 16, // 128 bits saltLength: 32 // 256 bits }, totp: { secretLength: 32, window: 1, step: 30 } }; /** * Encrypt MFA secret using authenticated encryption with PBKDF2 key derivation */ public async encryptMFASecret(secret: string, userPassword: string): Promise<EncryptedSecret> { try { // Generate cryptographically secure salt const salt = this.generateSecureSalt(); // Derive encryption key using PBKDF2 const derivedKey = this.deriveKey(userPassword, salt); // Generate IV for GCM mode const iv = randomBytes(this.config.encryption.ivLength); // Create GCM cipher with IV const cipher = createCipheriv(this.config.encryption.algorithm, derivedKey, iv) as any; // Encrypt the secret let encrypted = cipher.update(secret, 'utf8', 'hex'); encrypted += cipher.final('hex'); // Get authentication tag const tag = cipher.getAuthTag(); const result: EncryptedSecret = { data: encrypted, iv: iv.toString('hex'), tag: tag.toString('hex'), salt: salt.toString('hex'), algorithm: this.config.encryption.algorithm, iterations: this.config.keyDerivation.iterations, keyLength: this.config.keyDerivation.keyLength, digest: this.config.keyDerivation.digest, timestamp: Date.now() }; // Clear sensitive data from memory derivedKey.fill(0); logger.debug('MFA secret encrypted with authenticated encryption'); return result; } catch (error) { logger.error('Failed to encrypt MFA secret', { error }); throw new Error('Encryption failed'); } } /** * Decrypt MFA secret with integrity verification */ public async decryptMFASecret(encrypted: EncryptedSecret, userPassword: string): Promise<string> { try { // Validate encrypted data structure this.validateEncryptedData(encrypted); // Parse components const salt = Buffer.from(encrypted.salt, 'hex'); const iv = Buffer.from(encrypted.iv, 'hex'); const tag = Buffer.from(encrypted.tag, 'hex'); const data = encrypted.data; // Derive the same key using stored parameters const derivedKey = this.deriveKey( userPassword, salt, encrypted.iterations, encrypted.keyLength, encrypted.digest ); // Create GCM decipher with IV const decipher = createDecipheriv(encrypted.algorithm, derivedKey, iv) as any; decipher.setAuthTag(tag); // Decrypt and verify integrity let decrypted = decipher.update(data, 'hex', 'utf8'); decrypted += decipher.final('utf8'); // Clear sensitive data from memory derivedKey.fill(0); logger.debug('MFA secret decrypted and verified'); return decrypted; } catch (error) { logger.error('Failed to decrypt MFA secret', { error }); throw new Error('Decryption failed or data corrupted'); } } /** * Encrypt data with additional authenticated data (AAD) */ public encryptWithAAD(data: string, key: Buffer, aad: string): EncryptedSecret { try { const iv = randomBytes(this.config.encryption.ivLength); const cipher = createCipheriv(this.config.encryption.algorithm, key, iv) as any; cipher.setAAD(Buffer.from(aad, 'utf8')); let encrypted = cipher.update(data, 'utf8', 'hex'); encrypted += cipher.final('hex'); const tag = cipher.getAuthTag(); return { data: encrypted, iv: iv.toString('hex'), tag: tag.toString('hex'), salt: '', // Not used for direct key encryption algorithm: this.config.encryption.algorithm, iterations: 0, // Not used for direct key encryption keyLength: key.length, digest: 'n/a', timestamp: Date.now() }; } catch (error) { logger.error('Failed to encrypt with AAD', { error }); throw new Error('AAD encryption failed'); } } /** * Decrypt data with additional authenticated data (AAD) verification */ public decryptWithAAD(encrypted: EncryptedSecret, key: Buffer, aad: string): string { try { const iv = Buffer.from(encrypted.iv, 'hex'); const tag = Buffer.from(encrypted.tag, 'hex'); const decipher = createDecipheriv(encrypted.algorithm, key, iv) as any; decipher.setAuthTag(tag); decipher.setAAD(Buffer.from(aad, 'utf8')); let decrypted = decipher.update(encrypted.data, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } catch (error) { logger.error('Failed to decrypt with AAD', { error }); throw new Error('AAD decryption failed or authentication failed'); } } /** * Generate cryptographically secure salt */ private generateSecureSalt(): Buffer { return randomBytes(this.config.encryption.saltLength); } /** * Derive key using PBKDF2 with secure parameters */ private deriveKey( password: string, salt: Buffer, iterations?: number, keyLength?: number, digest?: string ): Buffer { const iter = iterations || this.config.keyDerivation.iterations; const len = keyLength || this.config.keyDerivation.keyLength; const dig = digest || this.config.keyDerivation.digest; // Validate parameters meet security requirements if (iter < 100000) { throw new Error('Insufficient PBKDF2 iterations for security'); } if (len < 32) { throw new Error('Insufficient key length for security'); } return pbkdf2Sync(password, salt, iter, len, dig); } /** * Validate encrypted data structure and integrity */ private validateEncryptedData(encrypted: EncryptedSecret): void { const required = ['data', 'iv', 'tag', 'salt', 'algorithm', 'iterations', 'keyLength', 'digest']; for (const field of required) { if (!(field in encrypted) || encrypted[field as keyof EncryptedSecret] === undefined) { throw new Error(`Missing required field: ${field}`); } } // Validate algorithm if (encrypted.algorithm !== this.config.encryption.algorithm) { throw new Error('Unsupported encryption algorithm'); } // Validate key derivation parameters if (encrypted.iterations < 100000) { throw new Error('Insufficient iterations for security'); } if (encrypted.keyLength < 32) { throw new Error('Insufficient key length for security'); } // Validate hex encoding const hexFields = ['data', 'iv', 'tag', 'salt']; for (const field of hexFields) { const value = encrypted[field as keyof EncryptedSecret] as string; if (!/^[a-f0-9]*$/i.test(value)) { throw new Error(`Invalid hex encoding in field: ${field}`); } } } /** * Constant-time comparison for cryptographic operations */ public constantTimeEquals(a: string, b: string): boolean { if (a.length !== b.length) { return false; } const bufferA = Buffer.from(a, 'utf8'); const bufferB = Buffer.from(b, 'utf8'); return timingSafeEqual(bufferA, bufferB); } /** * Secure memory clear for sensitive data */ public clearSensitiveData(buffer: Buffer): void { buffer.fill(0); } /** * Get configuration for external validation */ public getConfig(): SecureMFAConfig { return { ...this.config }; // Return copy to prevent modification } } export const secureEncryption = new SecureEncryption();

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