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.

313 lines (310 loc) 10.8 kB
'use strict'; /** * Hash validator - Input validation and security checks */ class HashValidator { /** * Validate hash input for security * @param input - Input to validate * @param options - Validation options */ static validateHashInput(input, options) { // Check input length (allow empty strings for edge cases) if (typeof input === "string" && input.length === 0) { console.warn("Warning: Hashing empty string"); } if (input instanceof Uint8Array && input.length === 0) { console.warn("Warning: Hashing empty array"); } // Check for suspicious patterns if (typeof input === "string") { // Check for null bytes if (input.includes("\0")) { throw new Error("Input contains null bytes"); } // Check for extremely long inputs (potential DoS) if (input.length > 1000000) { // 1MB limit throw new Error("Input too large (max 1MB)"); } // Check for common weak patterns if (HashValidator.hasWeakPatterns(input)) { console.warn("Input contains weak patterns that may reduce security"); } } // Validate Uint8Array input if (input instanceof Uint8Array) { if (input.length > 10000000) { // 10MB limit for binary data throw new Error("Binary input too large (max 10MB)"); } } // Validate options HashValidator.validateOptions(options); } /** * Check for weak patterns in input * @param input - String input to check * @returns True if weak patterns found */ static hasWeakPatterns(input) { // Check for repeated characters if (/(.)\1{10,}/.test(input)) { return true; } // Check for simple sequences if (/012345|123456|abcdef|qwerty/i.test(input)) { return true; } // Check for common weak passwords const weakPatterns = [ /^password/i, /^123456/, /^admin/i, /^test/i, /^guest/i, ]; return weakPatterns.some((pattern) => pattern.test(input)); } /** * Validate hash options * @param options - Options to validate */ static validateOptions(options) { // Validate iterations if (options.iterations !== undefined) { if (options.iterations < 1) { throw new Error("Iterations must be at least 1"); } if (options.iterations > 10000000) { throw new Error("Iterations too high (max 10,000,000)"); } } // Validate algorithm if (options.algorithm) { const validAlgorithms = [ "sha256", "sha512", "sha3-256", "sha3-512", "blake3", "blake2b", "blake2s", "pbkdf2", ]; if (!validAlgorithms.includes(options.algorithm)) { throw new Error(`Unsupported algorithm: ${options.algorithm}`); } } // Validate output format if (options.outputFormat) { const validFormats = [ "hex", "base64", "base58", "binary", "base64url", "buffer", ]; if (!validFormats.includes(options.outputFormat)) { throw new Error(`Unsupported output format: ${options.outputFormat}`); } } // Validate salt if (options.salt) { if (typeof options.salt === "string" && options.salt.length === 0) { throw new Error("Salt cannot be empty string"); } if (options.salt instanceof Uint8Array && options.salt.length === 0) { throw new Error("Salt cannot be empty array"); } } // Validate pepper if (options.pepper) { if (typeof options.pepper === "string" && options.pepper.length === 0) { throw new Error("Pepper cannot be empty string"); } if (options.pepper instanceof Uint8Array && options.pepper.length === 0) { throw new Error("Pepper cannot be empty array"); } } } /** * Validate password strength * @param password - Password to validate * @returns Validation result */ static validatePasswordStrength(password) { const issues = []; const recommendations = []; let score = 0; // Length check if (password.length < 8) { issues.push("Password too short (minimum 8 characters)"); } else if (password.length < 12) { issues.push("Password should be at least 12 characters"); score += 10; } else { score += 25; } // Character variety const hasLower = /[a-z]/.test(password); const hasUpper = /[A-Z]/.test(password); const hasDigit = /\d/.test(password); const hasSpecial = /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password); if (hasLower) score += 15; else issues.push("Missing lowercase letters"); if (hasUpper) score += 15; else issues.push("Missing uppercase letters"); if (hasDigit) score += 15; else issues.push("Missing numbers"); if (hasSpecial) score += 15; else issues.push("Missing special characters"); // Common patterns if (/(.)\1{2,}/.test(password)) { issues.push("Contains repeated characters"); score -= 10; } // Only flag sequential patterns if they're significant (4+ chars) if (/1234|abcd|qwer/i.test(password)) { issues.push("Contains sequential patterns"); score -= 15; } // Dictionary words (basic check) const commonWords = [ "password", "admin", "user", "test", "guest", "login", ]; if (commonWords.some((word) => password.toLowerCase().includes(word))) { issues.push("Contains common dictionary words"); score -= 20; } // Generate recommendations if (score < 60) { recommendations.push("Use a longer password with mixed character types"); } if (!hasSpecial) { recommendations.push("Add special characters"); } if (password.length < 12) { recommendations.push("Use at least 12 characters"); } if (issues.some((issue) => issue.includes("repeated") || issue.includes("sequential"))) { recommendations.push("Avoid repeated characters and sequential patterns"); } return { isSecure: score >= 70 && issues.filter((issue) => !issue.includes("should be")) .length === 0, score: Math.max(0, Math.min(100, score)), issues, recommendations, }; } /** * Enhanced timing-safe string comparison * @param a - First string/buffer * @param b - Second string/buffer * @returns True if equal */ static timingSafeEqual(a, b) { // Convert inputs to buffers const bufferA = Buffer.isBuffer(a) ? a : a instanceof Uint8Array ? Buffer.from(a) : Buffer.from(a, "utf8"); const bufferB = Buffer.isBuffer(b) ? b : b instanceof Uint8Array ? Buffer.from(b) : Buffer.from(b, "utf8"); // Use Node.js timing-safe comparison if lengths match if (bufferA.length === bufferB.length) { try { return require("crypto").timingSafeEqual(bufferA, bufferB); } catch (error) { // Fallback to manual timing-safe comparison return HashValidator.manualTimingSafeEqual(bufferA, bufferB); } } // For different lengths, still do timing-safe comparison return HashValidator.manualTimingSafeEqual(bufferA, bufferB); } /** * Manual timing-safe comparison implementation */ static manualTimingSafeEqual(a, b) { let result = 0; const maxLength = Math.max(a.length, b.length); // Always compare the same number of bytes for (let i = 0; i < maxLength; i++) { const byteA = i < a.length ? a[i] : 0; const byteB = i < b.length ? b[i] : 0; result |= byteA ^ byteB; } // Also compare lengths in a timing-safe way result |= a.length ^ b.length; return result === 0; } /** * Validate salt quality * @param salt - Salt to validate * @returns Validation result */ static validateSalt(salt) { const issues = []; const recommendations = []; // Convert to buffer for analysis const saltBuffer = Buffer.isBuffer(salt) ? salt : salt instanceof Uint8Array ? Buffer.from(salt) : Buffer.from(salt, "base64"); // Check length if (saltBuffer.length < 16) { issues.push("Salt too short (minimum 16 bytes recommended)"); recommendations.push("Use at least 16 bytes for salt"); } if (saltBuffer.length < 32) { recommendations.push("Consider using 32+ bytes for stronger security"); } // Check for patterns (basic entropy check) const uniqueBytes = new Set(saltBuffer); const entropyRatio = uniqueBytes.size / saltBuffer.length; if (entropyRatio < 0.5) { issues.push("Salt has low entropy (too many repeated bytes)"); recommendations.push("Use cryptographically secure random salt generation"); } // Check for all zeros or all same value if (uniqueBytes.size === 1) { issues.push("Salt contains only one unique value"); recommendations.push("Generate random salt using secure random number generator"); } return { isValid: issues.length === 0, issues, recommendations, }; } } exports.HashValidator = HashValidator; //# sourceMappingURL=hash-validator.js.map