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
JavaScript
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
;