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.

660 lines (653 loc) 30.7 kB
'use strict'; var constants = require('../utils/constants.js'); var encoding = require('../utils/encoding.js'); var stats = require('../utils/stats.js'); var randomCore = require('./random/random-core.js'); require('./random/random-types.js'); var crypto = require('crypto'); require('./random/random-sources.js'); require('nehonix-uri-processor'); require('../utils/memory/index.js'); require('../types.js'); var validators = require('./validators.js'); var hashCore = require('./hash/hash-core.js'); require('./hash/hash-types.js'); require('argon2'); require('../algorithms/hash-algorithms.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); // Note: childProcess and argon2 are imported dynamically when needed /** * Key derivation functionality */ class Keys { /** * Derive a key from a password or other input * @param input - The input to derive a key from * @param options - Key derivation options * @returns The derived key as a hex string */ static deriveKey(input, options = {}) { const startTime = Date.now(); const { algorithm = "pbkdf2", iterations = constants.SECURITY_DEFAULTS.PBKDF2_ITERATIONS, salt, keyLength = constants.SECURITY_DEFAULTS.KEY_LENGTH, hashFunction = "sha256", } = options; // Validate inputs validators.Validators.validateAlgorithm(algorithm, ["pbkdf2", "scrypt", "argon2"]); validators.Validators.validateIterations(iterations, 1000, 10000000); // Generate random salt if not provided let saltBytes; if (salt) { if (typeof salt === "string") { saltBytes = new TextEncoder().encode(salt); } else { saltBytes = salt; } } else { saltBytes = randomCore.SecureRandom.getRandomBytes(16); } // Convert input to bytes if it's a string let inputBytes; if (typeof input === "string") { inputBytes = new TextEncoder().encode(input); } else { inputBytes = input; } // Derive the key using the specified algorithm let derivedKey; switch (algorithm.toLowerCase()) { case "pbkdf2": derivedKey = Keys.pbkdf2(inputBytes, saltBytes, iterations, keyLength, hashFunction); break; case "scrypt": derivedKey = Keys.scrypt(inputBytes, saltBytes, iterations, keyLength); break; case "argon2": derivedKey = Keys.argon2(inputBytes, saltBytes, iterations, keyLength); break; default: throw new Error(`${constants.ERROR_MESSAGES.INVALID_ALGORITHM}: ${algorithm}`); } // Convert the derived key to hex const result = encoding.bufferToHex(derivedKey); // Track statistics const endTime = Date.now(); stats.StatsTracker.getInstance().trackKeyDerivation(endTime - startTime, keyLength * 8 // Entropy bits ); return result; } /** * PBKDF2 key derivation * @param password - Password input * @param salt - Salt value * @param iterations - Number of iterations * @param keyLength - Desired key length in bytes * @param hashFunction - Hash function to use * @returns Derived key */ static pbkdf2(password, salt, iterations, keyLength, hashFunction) { // Try to use the pbkdf2 library if available try { if (typeof require === "function") { try { // Import the pbkdf2 library const pbkdf2Lib = require("pbkdf2"); // Map hash function name to format expected by the library const digest = hashFunction === "sha512" ? "sha512" : "sha256"; // Use the synchronous version of the library const result = pbkdf2Lib.pbkdf2Sync(Buffer.from(password), Buffer.from(salt), iterations, keyLength, digest); return new Uint8Array(result); } catch (e) { console.warn("pbkdf2 library not available:", e); // Fall back to Node.js crypto } } } catch (e) { console.warn("Error using pbkdf2 library:", e); // Fall back to Node.js crypto } // Try to use Node.js crypto if available try { if (typeof crypto__namespace !== "undefined" && typeof crypto__namespace.pbkdf2Sync === "function") { return Keys.nodePbkdf2(password, salt, iterations, keyLength, hashFunction); } } catch (e) { console.warn("Node.js crypto pbkdf2 not available:", e); // Fall back to pure JS implementation } // Fall back to pure JS implementation console.warn("Using pure JS PBKDF2 implementation (less efficient)"); return Keys.purePbkdf2(password, salt, iterations, keyLength, hashFunction); // Note: We're not using Web Crypto API here because it's async and would require // restructuring the entire library to be async-compatible } // Note: Web Crypto API implementation removed since it's async and our API is sync /** * PBKDF2 using Node.js crypto * @param password - Password input * @param salt - Salt value * @param iterations - Number of iterations * @param keyLength - Desired key length in bytes * @param hashFunction - Hash function to use * @returns Derived key */ static nodePbkdf2(password, salt, iterations, keyLength, hashFunction) { // Map hash function name to Node.js format const digest = hashFunction === "sha512" ? "sha512" : "sha256"; // Use the pbkdf2 function from Node.js crypto const derivedKey = crypto__namespace.pbkdf2Sync(password, salt, iterations, keyLength, digest); return new Uint8Array(derivedKey); } /** * Pure JavaScript implementation of PBKDF2 * @param password - Password input * @param salt - Salt value * @param iterations - Number of iterations * @param keyLength - Desired key length in bytes * @param hashFunction - Hash function to use * @returns Derived key */ static purePbkdf2(password, salt, iterations, keyLength, hashFunction) { console.warn("Using pure JS PBKDF2 implementation (less efficient)"); // Real PBKDF2 implementation const hmacFunc = (key, message) => { // HMAC implementation const blockSize = 64; // Block size for SHA-256 const ipad = new Uint8Array(blockSize); const opad = new Uint8Array(blockSize); // Prepare key let keyToUse; if (key.length > blockSize) { // If key is too long, hash it const hashAlgo = hashFunction === "sha512" ? "sha512" : "sha256"; // Use Node.js crypto if available if (typeof crypto__namespace !== "undefined" && crypto__namespace.createHash) { const hash = crypto__namespace.createHash(hashAlgo); hash.update(key); keyToUse = new Uint8Array(hash.digest()); } else { // Fallback to our implementation const hashResult = hashCore.Hash.create(key, { algorithm: hashAlgo, iterations: 1, outputFormat: "buffer", }); keyToUse = hashResult; } } else { keyToUse = key; } // Create ipad and opad for (let i = 0; i < blockSize; i++) { ipad[i] = 0x36; opad[i] = 0x5c; } // XOR key with ipad and opad for (let i = 0; i < keyToUse.length; i++) { ipad[i] ^= keyToUse[i]; opad[i] ^= keyToUse[i]; } // Concatenate ipad with message const innerInput = new Uint8Array(ipad.length + message.length); innerInput.set(ipad, 0); innerInput.set(message, ipad.length); // Hash inner input let innerHash; if (typeof crypto__namespace !== "undefined" && crypto__namespace.createHash) { const hash = crypto__namespace.createHash(hashFunction === "sha512" ? "sha512" : "sha256"); hash.update(innerInput); innerHash = new Uint8Array(hash.digest()); } else { // Fallback to our implementation const hashAlgo = hashFunction === "sha512" ? "sha512" : "sha256"; const hashResult = hashCore.Hash.create(innerInput, { algorithm: hashAlgo, iterations: 1, outputFormat: "buffer", }); innerHash = hashResult; } // Concatenate opad with inner hash const outerInput = new Uint8Array(opad.length + innerHash.length); outerInput.set(opad, 0); outerInput.set(innerHash, opad.length); // Hash outer input let outerHash; if (typeof crypto__namespace !== "undefined" && crypto__namespace.createHash) { const hash = crypto__namespace.createHash(hashFunction === "sha512" ? "sha512" : "sha256"); hash.update(outerInput); outerHash = new Uint8Array(hash.digest()); } else { // Fallback to our implementation const hashAlgo = hashFunction === "sha512" ? "sha512" : "sha256"; const hashResult = hashCore.Hash.create(outerInput, { algorithm: hashAlgo, iterations: 1, outputFormat: "buffer", }); outerHash = hashResult; } return outerHash; }; // PBKDF2 implementation const hLen = hashFunction === "sha512" ? 64 : 32; // Length of hash output const result = new Uint8Array(keyLength); const blocks = Math.ceil(keyLength / hLen); for (let i = 1; i <= blocks; i++) { // Compute U_1 = HMAC(password, salt || INT_32_BE(i)) const blockInput = new Uint8Array(salt.length + 4); blockInput.set(salt, 0); // INT_32_BE(i) blockInput[salt.length] = (i >> 24) & 0xff; blockInput[salt.length + 1] = (i >> 16) & 0xff; blockInput[salt.length + 2] = (i >> 8) & 0xff; blockInput[salt.length + 3] = i & 0xff; let u = hmacFunc(password, blockInput); let f = new Uint8Array(u); for (let j = 1; j < iterations; j++) { u = hmacFunc(password, u); for (let k = 0; k < hLen; k++) { f[k] ^= u[k]; } } // Copy the block to the result const offset = (i - 1) * hLen; const bytesToCopy = Math.min(hLen, keyLength - offset); result.set(f.subarray(0, bytesToCopy), offset); } return result; } /** * Scrypt key derivation * @param password - Password input * @param salt - Salt value * @param cost - CPU/memory cost parameter * @param keyLength - Desired key length in bytes * @returns Derived key */ static scrypt(password, salt, cost, keyLength) { // Try to use scrypt-js if available try { if (typeof require === "function") { try { const scryptJs = require("scrypt-js"); // Calculate parameters const N = Math.pow(2, cost); // Cost parameter (must be power of 2) const r = 8; // Block size parameter const p = 1; // Parallelization parameter // Create a synchronous wrapper around the async scrypt function const scryptSync = (pwd, slt, len) => { let result = null; let error = null; // Call the async function scryptJs .scrypt(pwd, slt, N, r, p, len) .then((hash) => { result = hash; }) .catch((err) => { error = err; }); // Wait for the result (blocking) const maxWaitTime = Date.now() + 10000; // 10 second timeout while (result === null && error === null) { // Check for timeout if (Date.now() > maxWaitTime) { throw new Error("Scrypt operation timed out"); } // Small delay to prevent CPU hogging for (let i = 0; i < 1000000; i++) { // Busy wait } } // Check for errors if (error) { throw error; } // Return the result if (result) { return result; } throw new Error("Scrypt operation failed with no result"); }; // Call our synchronous wrapper return scryptSync(password, salt, keyLength); } catch (e) { console.warn("scrypt-js not available:", e); // Fall back to Node.js crypto scrypt } } } catch (e) { console.warn("Error using scrypt-js:", e); // Fall back to Node.js crypto scrypt } // Try to use Node.js crypto if available if (typeof crypto__namespace !== "undefined" && typeof crypto__namespace.scryptSync === "function") { try { // Node.js crypto.scryptSync is synchronous const result = crypto__namespace.scryptSync(Buffer.from(password), Buffer.from(salt), keyLength, { N: Math.pow(2, cost), // Cost parameter (must be power of 2) r: 8, // Block size parameter p: 1, // Parallelization parameter }); return new Uint8Array(result); } catch (e) { console.warn("Node.js scrypt failed, using fallback implementation:", e); // Fall back to PBKDF2 } } // If all else fails, use PBKDF2 with high iteration count console.warn("All Scrypt implementations failed, using PBKDF2 as fallback"); // Use PBKDF2 with increased iterations to compensate for the weaker algorithm // Scrypt with cost N is roughly equivalent to PBKDF2 with N*r*p iterations const equivalentIterations = Math.pow(2, cost) * 8 * 1; const iterations = Math.min(1000000, equivalentIterations); // Cap at a reasonable maximum return Keys.pbkdf2(password, salt, iterations, keyLength, "sha512" // Use stronger hash ); } /** * Argon2 key derivation * @param password - Password input * @param salt - Salt value * @param iterations - Time cost parameter * @param keyLength - Desired key length in bytes * @returns Derived key */ static argon2(password, salt, iterations, keyLength) { // Try to use the argon2 package if available try { // Check if we're in a browser environment first if (typeof window !== "undefined") { try { // Try to use argon2-browser in browser environments const argon2Browser = require("argon2-browser"); // Create a synchronous wrapper for browser environments const argon2BrowserSync = (pwd, slt, iter, len) => { try { // Use the synchronous version if available if (typeof argon2Browser.hashSync === "function") { const result = argon2Browser.hashSync({ pass: pwd, salt: slt, time: iter, mem: 4096, parallelism: 1, hashLen: len, type: argon2Browser.ArgonType.Argon2id, }); return new Uint8Array(result.hash); } // If no sync version, use a synchronous XMLHttpRequest to wait for the result let result = null; let error = null; // Create a worker to run argon2 in a separate thread const worker = new Worker(URL.createObjectURL(new Blob([ ` importScripts('https://cdn.jsdelivr.net/npm/argon2-browser@1.18.0/dist/argon2.min.js'); onmessage = function(e) { const { pwd, salt, time, mem, hashLen } = e.data; argon2.hash({ pass: pwd, salt: salt, time: time, mem: mem, parallelism: 1, hashLen: hashLen, type: argon2.ArgonType.Argon2id }) .then(result => { postMessage({ success: true, hash: result.hash }); }) .catch(err => { postMessage({ success: false, error: err.message }); }); } `, ], { type: "application/javascript" }))); // Send the data to the worker worker.postMessage({ pwd: Array.from(pwd), salt: Array.from(slt), time: iter, mem: 4096, hashLen: len, }); // Set up the message handler worker.onmessage = (e) => { if (e.data.success) { result = new Uint8Array(e.data.hash); } else { error = new Error(e.data.error); } }; // Use a synchronous XMLHttpRequest to block until we have a result const xhr = new XMLHttpRequest(); xhr.open("GET", "data:text/plain;charset=utf-8,", false); const startTime = Date.now(); const maxWaitTime = 30000; // 30 seconds timeout while (result === null && error === null) { // Check for timeout if (Date.now() - startTime > maxWaitTime) { worker.terminate(); throw new Error("Argon2 operation timed out"); } // Poll every 100ms try { xhr.send(null); } catch (e) { // Ignore errors from the XHR } } // Clean up worker.terminate(); // Check for errors if (error) { throw error; } // Return the result if (result) { return result; } throw new Error("Argon2 operation failed with no result"); } catch (err) { const errorMessage = err instanceof Error ? err.message : "Unknown error"; throw new Error(`Browser Argon2 operation failed: ${errorMessage}`); } }; // Call our browser synchronous wrapper return argon2BrowserSync(password, salt, iterations, keyLength); } catch (e) { console.warn("argon2-browser not available:", e); // Fall back to Node.js implementation or other fallbacks } } // Check if we're in a Node.js environment if (typeof require === "function") { try { // Import the argon2 package // Since argon2 is async and our API is sync, we need to use a workaround // This is not ideal but allows us to maintain compatibility // Create a proper synchronous wrapper around the async argon2 function const argon2Sync = (pwd, slt, iter, len) => { try { // Since argon2 is async, we need to use child_process for sync operation const childProcess = require("child_process"); // Create a small script to run argon2 in a separate process const script = ` const argon2 = require('argon2'); const pwd = Buffer.from(${JSON.stringify(Array.from(pwd))}); const salt = Buffer.from(${JSON.stringify(Array.from(slt))}); argon2.hash(pwd, { type: argon2.argon2id, timeCost: ${iter}, memoryCost: 4096, parallelism: 1, salt: salt, hashLength: ${len}, raw: true, }) .then(hash => { process.stdout.write(Buffer.from(hash).toString('hex')); process.exit(0); }) .catch(err => { process.stderr.write(err.message); process.exit(1); }); `; // Execute the script synchronously const result = childProcess.execSync(`node -e "${script.replace(/\n/g, " ")}"`, { timeout: 30000 } // 30 second timeout ); // Convert the hex output back to Uint8Array const hexOutput = result.toString().trim(); const bytes = new Uint8Array(len); for (let i = 0; i < len; i++) { bytes[i] = parseInt(hexOutput.substring(i * 2, i * 2 + 2), 16); } return bytes; } catch (err) { // If child_process approach fails, throw a clear error const errorMessage = err instanceof Error ? err.message : "Unknown error"; throw new Error(`Argon2 operation failed: ${errorMessage}`); } }; // Call our synchronous wrapper return argon2Sync(password, salt, iterations, keyLength); } catch (e) { console.warn("Argon2 package error:", e); // Fall back to scrypt-js } } } catch (e) { console.warn("Error using Argon2 package:", e); // Fall back to scrypt-js } // If argon2 is not available, use scrypt-js as a fallback try { if (typeof require === "function") { try { const scryptJs = require("scrypt-js"); // Use scrypt-js as a fallback console.warn("Argon2 not available, using scrypt-js as fallback"); // Convert parameters to scrypt parameters // Argon2 iterations roughly map to scrypt N as 2^cost const N = Math.pow(2, Math.min(18, Math.max(14, Math.floor(iterations / 2)))); // Between 2^14 and 2^18 const r = 8; // Block size const p = 1; // Parallelization // Create a synchronous wrapper around the async scrypt function const scryptSync = (pwd, slt, len) => { let result = null; let error = null; // Call the async function scryptJs .scrypt(pwd, slt, N, r, p, len) .then((hash) => { result = hash; }) .catch((err) => { error = err; }); // Wait for the result (blocking) const maxWaitTime = Date.now() + 10000; // 10 second timeout while (result === null && error === null) { // Check for timeout if (Date.now() > maxWaitTime) { throw new Error("Scrypt operation timed out"); } // Small delay to prevent CPU hogging for (let i = 0; i < 1000000; i++) { // Busy wait } } // Check for errors if (error) { throw error; } // Return the result if (result) { return result; } throw new Error("Scrypt operation failed with no result"); }; // Call our synchronous wrapper return scryptSync(password, salt, keyLength); } catch (e) { console.warn("scrypt-js not available:", e); // Fall back to Node.js crypto scrypt } } } catch (e) { console.warn("Error using scrypt-js:", e); // Fall back to Node.js crypto scrypt } // If both argon2 and scrypt-js are not available, use Node.js crypto scrypt try { if (typeof crypto__namespace !== "undefined" && typeof crypto__namespace.scryptSync === "function") { console.warn("Using Node.js crypto scrypt as fallback"); // Convert parameters to scrypt parameters const N = Math.pow(2, Math.min(18, Math.max(14, Math.floor(iterations / 2)))); // Between 2^14 and 2^18 const r = 8; // Block size const p = 1; // Parallelization const result = crypto__namespace.scryptSync(Buffer.from(password), Buffer.from(salt), keyLength, { N: N, r: r, p: p, }); return new Uint8Array(result); } } catch (e) { console.warn("Node.js crypto scrypt not available:", e); // Fall back to PBKDF2 } // If all else fails, use PBKDF2 with high iteration count console.warn("All Argon2 alternatives failed, using PBKDF2 as final fallback"); return Keys.pbkdf2(password, salt, iterations * 10, // Increase iterations to compensate for weaker algorithm keyLength, "sha512" // Use stronger hash ); } } exports.Keys = Keys; //# sourceMappingURL=keys.js.map