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.
297 lines (294 loc) • 11.5 kB
JavaScript
import { keyLogger } from '../../keys-logger.js';
import { AlgorithmBackend, KeyDerivationHashFunction, KeyDerivationAlgorithm } from '../../keys-types.js';
import * as crypto from 'crypto';
import { PBKDF2Algo } from './PBKDF2Algo.js';
import { PerformanceUtils } from '../../keys-utils.js';
/**
* 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 = AlgorithmBackend.NODE_CRYPTO;
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 = AlgorithmBackend.EXTERNAL_LIBRARY;
keyLogger.debug("Scrypt", "Using scrypt-js library", {
N,
r,
p,
keyLength,
});
}
// Fallback: PBKDF2 with equivalent security parameters
else {
keyLogger.warn("Scrypt", "Scrypt unavailable, using PBKDF2 fallback");
const equivalentIterations = this.calculateEquivalentPBKDF2Iterations(N, r, p);
const pbkdf2Result = await PBKDF2Algo.derive(password, salt, equivalentIterations, keyLength, KeyDerivationHashFunction.SHA512);
backend = AlgorithmBackend.PURE_JS;
key = pbkdf2Result.key;
}
}
catch (err) {
error = err instanceof Error ? err.message : "Unknown scrypt error";
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: KeyDerivationAlgorithm.SCRYPT,
backend,
executionTime,
memoryUsage: PerformanceUtils.estimateMemoryUsage(key),
iterations: N,
keyLength,
success: !error,
errorMessage: error,
timestamp: Date.now(),
};
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
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.scryptSync(Buffer.from(password), Buffer.from(salt), keyLength, { N, r, p });
backend = AlgorithmBackend.NODE_CRYPTO;
key = new Uint8Array(result);
}
else {
// Fallback to PBKDF2 for sync operation
keyLogger.warn("Scrypt", "Node crypto unavailable, using PBKDF2 fallback");
const equivalentIterations = this.calculateEquivalentPBKDF2Iterations(N, r, p);
const pbkdf2Result = PBKDF2Algo.derive(password, salt, equivalentIterations, keyLength, KeyDerivationHashFunction.SHA512);
backend = 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: KeyDerivationAlgorithm.SCRYPT,
backend,
executionTime,
memoryUsage: PerformanceUtils.estimateMemoryUsage(key),
iterations: N,
keyLength,
success: !error,
errorMessage: error,
timestamp: Date.now(),
};
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.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 !== "undefined" &&
typeof crypto.scrypt === "function" &&
typeof crypto.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;
}
}
}
export { ScryptAlgo };
//# sourceMappingURL=ScryptAlgo.js.map