UNPKG

fortify2-js

Version:

MOST POWERFUL JavaScript Security Library! Military-grade cryptography + 19 enhanced object methods + quantum-resistant algorithms + perfect TypeScript support. More powerful than Lodash with built-in security.

318 lines (312 loc) 12.3 kB
'use strict'; var keysLogger = require('../../keys-logger.js'); var keysTypes = require('../../keys-types.js'); var crypto = require('crypto'); var PBKDF2Algo = require('./PBKDF2Algo.js'); var keysUtils = require('../../keys-utils.js'); function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var crypto__namespace = /*#__PURE__*/_interopNamespaceDefault(crypto); /** * Scrypt key derivation algorithm implementation with multiple backend support. * * This class provides a robust Scrypt implementation that automatically selects * the best available backend for optimal performance: * 1. Node.js crypto (fastest, native implementation) * 2. scrypt-js library (JavaScript implementation) * 3. PBKDF2 fallback (when Scrypt is unavailable) * * @example * ```typescript * const password = new TextEncoder().encode('mypassword'); * const salt = crypto.randomBytes(32); * const result = await ScryptAlgo.derive(password, salt, 14, 32); * console.log('Derived key:', result.key); * console.log('Backend used:', result.backend); * ``` */ class ScryptAlgo { /** * Derives a key using the Scrypt algorithm with automatic backend selection. * * This method automatically selects the most appropriate backend based on * the runtime environment and available libraries. It provides comprehensive * metrics and error handling for production use. * * @param password - The password to derive the key from * @param salt - Cryptographic salt (should be at least 16 bytes) * @param cost - Cost parameter (N = 2^cost). Recommended: 14-20 for production * @param keyLength - Desired length of the derived key in bytes * @returns Promise containing the derived key, backend used, and performance metrics * * @throws {Error} When all backends fail or invalid parameters are provided * * @example * ```typescript * // High security (slow) * const highSec = await ScryptAlgo.derive(password, salt, 16, 32); * * // Balanced (recommended) * const balanced = await ScryptAlgo.derive(password, salt, 14, 32); * * // Fast (for testing only) * const fast = await ScryptAlgo.derive(password, salt, 10, 32); * ``` */ static async asyncDerive(password, salt, cost, keyLength) { // Input validation this.validateInputs(password, salt, cost, keyLength); const startTime = performance.now(); const N = Math.pow(2, cost); const r = 8; // Standard block size factor const p = 1; // Standard parallelization factor let backend; let key; let error; try { // Primary: Node.js crypto (fastest, native implementation) if (this.canUseNodeCrypto()) { key = await this.deriveWithNodeCrypto(password, salt, N, r, p, keyLength); backend = keysTypes.AlgorithmBackend.NODE_CRYPTO; keysLogger.keyLogger.debug("Scrypt", "Using Node.js crypto backend", { N, r, p, keyLength, }); } // Secondary: scrypt-js library (JavaScript implementation) else if (await this.canUseScryptLibrary()) { key = await this.deriveWithLibrary(password, salt, N, r, p, keyLength); backend = keysTypes.AlgorithmBackend.EXTERNAL_LIBRARY; keysLogger.keyLogger.debug("Scrypt", "Using scrypt-js library", { N, r, p, keyLength, }); } // Fallback: PBKDF2 with equivalent security parameters else { keysLogger.keyLogger.warn("Scrypt", "Scrypt unavailable, using PBKDF2 fallback"); const equivalentIterations = this.calculateEquivalentPBKDF2Iterations(N, r, p); const pbkdf2Result = await PBKDF2Algo.PBKDF2Algo.derive(password, salt, equivalentIterations, keyLength, keysTypes.KeyDerivationHashFunction.SHA512); backend = keysTypes.AlgorithmBackend.PURE_JS; key = pbkdf2Result.key; } } catch (err) { error = err instanceof Error ? err.message : "Unknown scrypt error"; keysLogger.keyLogger.error("Scrypt", "Key derivation failed", { error, cost, keyLength, }); throw new Error(`Scrypt key derivation failed: ${error}`); } const executionTime = performance.now() - startTime; const metrics = { algorithm: keysTypes.KeyDerivationAlgorithm.SCRYPT, backend, executionTime, memoryUsage: keysUtils.PerformanceUtils.estimateMemoryUsage(key), iterations: N, keyLength, success: !error, errorMessage: error, timestamp: Date.now(), }; keysLogger.keyLogger.logMetrics(metrics); return { key, backend, metrics }; } /** * Synchronous version of derive() for backward compatibility. * * **Deprecated**: Use the async version for better performance and error handling. * This method is provided for backward compatibility but may block the event loop. * * @param password - The password to derive the key from * @param salt - Cryptographic salt * @param cost - Cost parameter (N = 2^cost) * @param keyLength - Desired length of the derived key in bytes * @returns Object containing the derived key, backend used, and performance metrics * * @deprecated Use the async derive() method instead */ static derive(password, salt, cost, keyLength) { // For backward compatibility, we'll use a synchronous approach // but log a deprecation warning keysLogger.keyLogger.warn("Scrypt", "Using deprecated synchronous derive method. Consider upgrading to async version."); this.validateInputs(password, salt, cost, keyLength); const startTime = performance.now(); const N = Math.pow(2, cost); const r = 8; const p = 1; let backend; let key; let error; try { if (this.canUseNodeCrypto()) { const result = crypto__namespace.scryptSync(Buffer.from(password), Buffer.from(salt), keyLength, { N, r, p }); backend = keysTypes.AlgorithmBackend.NODE_CRYPTO; key = new Uint8Array(result); } else { // Fallback to PBKDF2 for sync operation keysLogger.keyLogger.warn("Scrypt", "Node crypto unavailable, using PBKDF2 fallback"); const equivalentIterations = this.calculateEquivalentPBKDF2Iterations(N, r, p); const pbkdf2Result = PBKDF2Algo.PBKDF2Algo.derive(password, salt, equivalentIterations, keyLength, keysTypes.KeyDerivationHashFunction.SHA512); backend = keysTypes.AlgorithmBackend.PURE_JS; key = pbkdf2Result.key; } } catch (err) { error = err instanceof Error ? err.message : "Unknown error"; throw new Error(`Scrypt key derivation failed: ${error}`); } const executionTime = performance.now() - startTime; const metrics = { algorithm: keysTypes.KeyDerivationAlgorithm.SCRYPT, backend, executionTime, memoryUsage: keysUtils.PerformanceUtils.estimateMemoryUsage(key), iterations: N, keyLength, success: !error, errorMessage: error, timestamp: Date.now(), }; keysLogger.keyLogger.logMetrics(metrics); return { key, backend, metrics }; } /** * Validates input parameters for the Scrypt algorithm. * * @private * @param password - Password to validate * @param salt - Salt to validate * @param cost - Cost parameter to validate * @param keyLength - Key length to validate * @throws {Error} When parameters are invalid */ static validateInputs(password, salt, cost, keyLength) { if (!password || password.length === 0) { throw new Error("Password cannot be empty"); } if (!salt || salt.length < 16) { throw new Error("Salt must be at least 16 bytes"); } if (cost < 1 || cost > 31) { throw new Error("Cost parameter must be between 1 and 31"); } if (keyLength < 1 || keyLength > 1024) { throw new Error("Key length must be between 1 and 1024 bytes"); } } /** * Derives key using Node.js native crypto implementation. * * @private * @param password - Password bytes * @param salt - Salt bytes * @param N - Cost parameter * @param r - Block size factor * @param p - Parallelization factor * @param keyLength - Desired key length * @returns Promise resolving to derived key */ static async deriveWithNodeCrypto(password, salt, N, r, p, keyLength) { return new Promise((resolve, reject) => { crypto__namespace.scrypt(Buffer.from(password), Buffer.from(salt), keyLength, { N, r, p }, (err, result) => { if (err) { reject(err); } else { resolve(new Uint8Array(result)); } }); }); } /** * Derives key using the scrypt-js library. * * @private * @param password - Password bytes * @param salt - Salt bytes * @param N - Cost parameter * @param r - Block size factor * @param p - Parallelization factor * @param keyLength - Desired key length * @returns Promise resolving to derived key */ static async deriveWithLibrary(password, salt, N, r, p, keyLength) { const scryptJs = await import('scrypt-js'); return scryptJs.scrypt(password, salt, N, r, p, keyLength); } /** * Calculates equivalent PBKDF2 iterations for similar security level. * * This is an approximation based on computational complexity analysis. * The actual security depends on many factors including hardware characteristics. * * @private * @param N - Scrypt cost parameter * @param r - Scrypt block size factor * @param p - Scrypt parallelization factor * @returns Equivalent PBKDF2 iteration count */ static calculateEquivalentPBKDF2Iterations(N, r, p) { // Conservative approximation: Scrypt's memory-hard property makes it // roughly equivalent to PBKDF2 with significantly more iterations const baseIterations = N * r * p; const securityMultiplier = 10; // Conservative estimate return Math.min(1000000, baseIterations * securityMultiplier); } /** * Checks if Node.js crypto scrypt is available. * * @private * @returns True if Node.js crypto scrypt is available */ static canUseNodeCrypto() { try { return (typeof crypto__namespace !== "undefined" && typeof crypto__namespace.scrypt === "function" && typeof crypto__namespace.scryptSync === "function"); } catch { return false; } } /** * Checks if scrypt-js library is available. * * @private * @returns Promise resolving to true if scrypt-js is available */ static async canUseScryptLibrary() { try { await import('scrypt-js'); return true; } catch { return false; } } } exports.ScryptAlgo = ScryptAlgo; //# sourceMappingURL=ScryptAlgo.js.map