UNPKG

tl-shared-security

Version:

Enterprise-grade security module for frontend and backend applications with comprehensive protection against XSS, CSRF, SQL injection, and other security vulnerabilities

230 lines 8.97 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.cryptoService = exports.CryptoService = void 0; const tslib_1 = require("tslib"); const crypto = tslib_1.__importStar(require("crypto")); const bcrypt = tslib_1.__importStar(require("bcrypt")); class CryptoService { constructor() { this.defaultEncryptionOptions = { algorithm: 'aes-256-gcm', ivLength: 16, keyLength: 32, }; this.defaultHashingOptions = { algorithm: 'sha512', iterations: 100000, keyLength: 64, saltLength: 16, }; this.defaultSigningOptions = { algorithm: 'sha256', }; } /** * Encrypts data using a password * @param data - Data to encrypt * @param password - Password to use for encryption * @param options - Encryption options * @returns Encrypted data as a string (format: iv:salt:authTag:encryptedData) */ encrypt(data, password, options) { const opts = { ...this.defaultEncryptionOptions, ...options }; // Generate salt and derive key const salt = crypto.randomBytes(opts.ivLength); const key = crypto.pbkdf2Sync(password, salt, this.defaultHashingOptions.iterations, opts.keyLength, this.defaultHashingOptions.algorithm); // Generate initialization vector const iv = crypto.randomBytes(opts.ivLength); // Create cipher const cipher = crypto.createCipheriv(opts.algorithm, key, iv); // Encrypt data let encrypted = cipher.update(data, 'utf8', 'hex'); encrypted += cipher.final('hex'); // Get authentication tag (for GCM mode) const authTag = cipher.getAuthTag ? cipher.getAuthTag().toString('hex') : ''; // Format: iv:salt:authTag:encryptedData return `${iv.toString('hex')}:${salt.toString('hex')}:${authTag}:${encrypted}`; } /** * Decrypts data using a password * @param encryptedData - Data to decrypt (format: iv:salt:authTag:encryptedData) * @param password - Password to use for decryption * @param options - Encryption options * @returns Decrypted data as a string */ decrypt(encryptedData, password, options) { const opts = { ...this.defaultEncryptionOptions, ...options }; // Parse encrypted data const parts = encryptedData.split(':'); if (parts.length !== 4) { throw new Error('Invalid encrypted data format'); } const [ivHex, saltHex, authTagHex, encrypted] = parts; if (!ivHex || !saltHex) { throw new Error('Invalid encrypted data format'); } // Convert hex to buffers const iv = Buffer.from(ivHex, 'hex'); const salt = Buffer.from(saltHex, 'hex'); // Derive key const key = crypto.pbkdf2Sync(password, salt, this.defaultHashingOptions.iterations, opts.keyLength, this.defaultHashingOptions.algorithm); // Create decipher const decipher = crypto.createDecipheriv(opts.algorithm, key, iv); // Set authentication tag (for GCM mode) if (authTagHex && decipher.setAuthTag) { decipher.setAuthTag(Buffer.from(authTagHex, 'hex')); } // Decrypt data let decrypted = decipher.update(encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } /** * Hashes a password using bcrypt (recommended for production) * @param password - Password to hash * @param saltRounds - Number of salt rounds (default: 12) * @returns Hashed password */ hashPassword(password, saltRounds = 12) { return bcrypt.hashSync(password, saltRounds); } /** * Hashes a password using bcrypt asynchronously * @param password - Password to hash * @param saltRounds - Number of salt rounds (default: 12) * @returns Promise resolving to hashed password */ async hashPasswordAsync(password, saltRounds = 12) { return bcrypt.hash(password, saltRounds); } /** * Hashes a password using PBKDF2 (legacy method) * @param password - Password to hash * @param options - Hashing options * @returns Hashed password (format: salt:iterations:hash) */ hashPasswordPBKDF2(password, options) { const opts = { ...this.defaultHashingOptions, ...options }; // Generate salt const salt = crypto.randomBytes(opts.saltLength); // Hash password const hash = crypto.pbkdf2Sync(password, salt, opts.iterations, opts.keyLength, opts.algorithm); // Format: salt:iterations:hash return `${salt.toString('hex')}:${opts.iterations}:${hash.toString('hex')}`; } /** * Verifies a password against a bcrypt hash * @param password - Password to verify * @param hashedPassword - Bcrypt hashed password * @returns True if password matches hash, false otherwise */ verifyPassword(password, hashedPassword) { // Check if it's a bcrypt hash (starts with $2a$, $2b$, $2x$, or $2y$) if (hashedPassword.startsWith('$2')) { try { return bcrypt.compareSync(password, hashedPassword); } catch (error) { return false; } } else { // Try PBKDF2 for backward compatibility return this.verifyPasswordPBKDF2(password, hashedPassword); } } /** * Verifies a password against a bcrypt hash asynchronously * @param password - Password to verify * @param hashedPassword - Bcrypt hashed password * @returns Promise resolving to true if password matches hash, false otherwise */ async verifyPasswordAsync(password, hashedPassword) { try { return await bcrypt.compare(password, hashedPassword); } catch (error) { // If bcrypt fails, try PBKDF2 for backward compatibility return this.verifyPasswordPBKDF2(password, hashedPassword); } } /** * Verifies a password against a PBKDF2 hash (legacy method) * @param password - Password to verify * @param hashedPassword - Hashed password (format: salt:iterations:hash) * @returns True if password matches hash, false otherwise */ verifyPasswordPBKDF2(password, hashedPassword) { try { // Parse hashed password const [saltHex, iterationsStr, hashHex] = hashedPassword.split(':'); if (!saltHex || !iterationsStr || !hashHex) { return false; } // Convert hex to buffers const salt = Buffer.from(saltHex, 'hex'); const iterations = parseInt(iterationsStr, 10); // Hash password const hash = crypto.pbkdf2Sync(password, salt, iterations, Buffer.from(hashHex, 'hex').length, this.defaultHashingOptions.algorithm); // Compare hashes return crypto.timingSafeEqual(hash, Buffer.from(hashHex, 'hex')); } catch (error) { return false; } } /** * Generates a random token * @param length - Length of token in bytes * @returns Random token as a hex string */ generateToken(length = 32) { return crypto.randomBytes(length).toString('hex'); } /** * Signs data using HMAC * @param data - Data to sign * @param secret - Secret key * @param options - Signing options * @returns Signature as a hex string */ sign(data, secret, options) { const opts = { ...this.defaultSigningOptions, ...options }; return crypto .createHmac(opts.algorithm, secret) .update(data) .digest('hex'); } /** * Verifies a signature * @param data - Data that was signed * @param signature - Signature to verify * @param secret - Secret key * @param options - Signing options * @returns True if signature is valid, false otherwise */ verifySignature(data, signature, secret, options) { const calculatedSignature = this.sign(data, secret, options); return crypto.timingSafeEqual(Buffer.from(calculatedSignature, 'hex'), Buffer.from(signature, 'hex')); } /** * Generates a secure random string * @param length - Length of string * @param encoding - Encoding to use (hex, base64, etc.) * @returns Random string */ generateSecureRandomString(length = 32, encoding = 'hex') { return crypto.randomBytes(length).toString(encoding); } /** * Generates a UUID v4 * @returns UUID v4 string */ generateUUID() { return crypto.randomUUID(); } } exports.CryptoService = CryptoService; // Export singleton instance exports.cryptoService = new CryptoService(); //# sourceMappingURL=crypto.service.js.map