xypriss-security
Version:
XyPriss Security is an advanced JavaScript security library designed for enterprise applications. It provides military-grade encryption, secure data structures, quantum-resistant cryptography, and comprehensive security utilities for modern web applicatio
535 lines (531 loc) • 20.6 kB
JavaScript
'use strict';
var patterns = require('../../utils/patterns.js');
var passwordTypes = require('./password-types.js');
/**
* 🔐 Password Security Analysis Module
*
* Production-ready password strength analysis and security validation
*/
/**
* Password security analysis and validation
*/
class PasswordSecurity {
constructor(config) {
this.config = config;
this.commonPasswords = this.getCommonPasswords();
this.keyboardPatterns = this.getKeyboardPatterns();
}
/**
* Update configuration
*/
updateConfig(config) {
this.config = config;
}
/**
* Analyze password strength with detailed metrics
*/
analyzeStrength(password) {
if (!password) {
return this.createEmptyAnalysis();
}
const details = this.analyzePasswordDetails(password);
const entropy = this.calculateEntropy(password, details);
const score = this.calculateStrengthScore(password, details, entropy);
const vulnerabilities = this.findVulnerabilities(password, details);
const feedback = this.generateFeedback(details, vulnerabilities);
const estimatedCrackTime = this.estimateCrackTime(score, entropy);
return {
score: Math.round(score),
feedback,
entropy: Math.round(entropy * 100) / 100,
estimatedCrackTime,
vulnerabilities,
details,
};
}
/**
* Validate password against policy
*/
validatePolicy(password) {
if (!this.config.policy) {
return {
isValid: true,
violations: [],
score: this.analyzeStrength(password).score,
suggestions: [],
};
}
const policy = this.config.policy;
const violations = [];
const suggestions = [];
// Length validation
if (password.length < policy.minLength) {
violations.push(`Password must be at least ${policy.minLength} characters long`);
suggestions.push(`Add ${policy.minLength - password.length} more characters`);
}
if (policy.maxLength && password.length > policy.maxLength) {
violations.push(`Password must be no more than ${policy.maxLength} characters long`);
}
// Character requirements
if (policy.requireUppercase && !/[A-Z]/.test(password)) {
violations.push("Password must contain uppercase letters");
suggestions.push("Add uppercase letters (A-Z)");
}
if (policy.requireLowercase && !/[a-z]/.test(password)) {
violations.push("Password must contain lowercase letters");
suggestions.push("Add lowercase letters (a-z)");
}
if (policy.requireNumbers && !/[0-9]/.test(password)) {
violations.push("Password must contain numbers");
suggestions.push("Add numbers (0-9)");
}
if (policy.requireSymbols && !/[^A-Za-z0-9]/.test(password)) {
violations.push("Password must contain special characters");
suggestions.push("Add special characters (!@#$%^&*)");
}
// Strength requirement
const strength = this.analyzeStrength(password);
if (policy.minStrengthScore &&
strength.score < policy.minStrengthScore) {
violations.push(`Password strength score (${strength.score}) is below required minimum (${policy.minStrengthScore})`);
suggestions.push(...strength.feedback);
}
// Forbidden patterns
if (policy.forbiddenPatterns) {
for (const pattern of policy.forbiddenPatterns) {
if (pattern.test(password)) {
violations.push("Password contains forbidden patterns");
suggestions.push("Avoid common patterns and sequences");
break;
}
}
}
// Forbidden words
if (policy.forbiddenWords) {
const lowerPassword = password.toLowerCase();
for (const word of policy.forbiddenWords) {
if (lowerPassword.includes(word.toLowerCase())) {
violations.push("Password contains forbidden words");
suggestions.push("Avoid common words and personal information");
break;
}
}
}
return {
isValid: violations.length === 0,
violations,
score: strength.score,
suggestions: [...new Set(suggestions)],
};
}
/**
* Audit multiple passwords for security issues
*/
async auditPasswords(hashes) {
const algorithmDistribution = {
[passwordTypes.PasswordAlgorithm.ARGON2ID]: 0,
[passwordTypes.PasswordAlgorithm.ARGON2I]: 0,
[passwordTypes.PasswordAlgorithm.ARGON2D]: 0,
[passwordTypes.PasswordAlgorithm.SCRYPT]: 0,
[passwordTypes.PasswordAlgorithm.PBKDF2_SHA512]: 0,
[passwordTypes.PasswordAlgorithm.BCRYPT_PLUS]: 0,
[passwordTypes.PasswordAlgorithm.MILITARY]: 0,
};
const securityLevelDistribution = {
[passwordTypes.PasswordSecurityLevel.STANDARD]: 0,
[passwordTypes.PasswordSecurityLevel.HIGH]: 0,
[passwordTypes.PasswordSecurityLevel.MAXIMUM]: 0,
[passwordTypes.PasswordSecurityLevel.MILITARY]: 0,
[passwordTypes.PasswordSecurityLevel.QUANTUM_RESISTANT]: 0,
};
let weakPasswords = 0;
let outdatedHashes = 0;
let needsRehash = 0;
let oldestHash = Date.now();
let totalStrength = 0;
for (const hash of hashes) {
try {
const hashInfo = this.parseHashMetadata(hash);
// Update algorithm distribution
if (hashInfo.algorithm in algorithmDistribution) {
algorithmDistribution[hashInfo.algorithm]++;
}
// Update security level distribution
if (hashInfo.securityLevel in securityLevelDistribution) {
securityLevelDistribution[hashInfo.securityLevel]++;
}
// Check if hash is outdated
if (this.isOutdatedAlgorithm(hashInfo.algorithm)) {
outdatedHashes++;
needsRehash++;
}
// Check if hash is weak based on iterations/parameters
if (this.isWeakHash(hashInfo)) {
weakPasswords++;
needsRehash++;
}
// Track oldest hash
if (hashInfo.timestamp && hashInfo.timestamp < oldestHash) {
oldestHash = hashInfo.timestamp;
}
// Add to total strength calculation
totalStrength += this.getSecurityLevelScore(hashInfo.securityLevel);
}
catch (error) {
// Log parsing errors for debugging
if (process.env.NODE_ENV === "development") {
console.warn(`[PasswordSecurity] Failed to parse hash: ${error.message}`);
}
outdatedHashes++;
}
}
const securityScore = Math.max(0, 100 - weakPasswords * 10 - outdatedHashes * 5);
const averageStrength = hashes.length > 0 ? totalStrength / hashes.length : 0;
const recommendations = [];
if (weakPasswords > 0) {
recommendations.push(`${weakPasswords} weak passwords should be strengthened`);
}
if (outdatedHashes > 0) {
recommendations.push(`${outdatedHashes} passwords use outdated hashing algorithms`);
}
if (needsRehash > 0) {
recommendations.push(`${needsRehash} passwords should be rehashed with stronger algorithms`);
}
return {
totalPasswords: hashes.length,
weakPasswords,
outdatedHashes,
needsRehash,
securityScore,
recommendations,
details: {
algorithmDistribution,
securityLevelDistribution,
averageStrength,
oldestHash,
},
};
}
// ===== PRIVATE HELPER METHODS =====
getCommonPasswords() {
return new Set(patterns.commonPassword);
}
getKeyboardPatterns() {
return patterns.keyboardPatterns;
}
createEmptyAnalysis() {
return {
score: 0,
feedback: ["Password is empty"],
entropy: 0,
estimatedCrackTime: "Instant",
vulnerabilities: ["Empty password"],
details: {
length: 0,
hasUppercase: false,
hasLowercase: false,
hasNumbers: false,
hasSymbols: false,
hasRepeated: false,
hasSequential: false,
hasCommonPatterns: false,
},
};
}
analyzePasswordDetails(password) {
return {
length: password.length,
hasUppercase: /[A-Z]/.test(password),
hasLowercase: /[a-z]/.test(password),
hasNumbers: /[0-9]/.test(password),
hasSymbols: /[^A-Za-z0-9]/.test(password),
hasRepeated: /(.)\1{2,}/.test(password),
hasSequential: this.hasSequentialChars(password),
hasCommonPatterns: this.hasCommonPatterns(password),
};
}
calculateEntropy(password, details) {
let charsetSize = 0;
if (details.hasUppercase)
charsetSize += 26;
if (details.hasLowercase)
charsetSize += 26;
if (details.hasNumbers)
charsetSize += 10;
if (details.hasSymbols)
charsetSize += 33;
if (charsetSize === 0)
return 0;
return Math.log2(Math.pow(charsetSize, details.length));
}
calculateStrengthScore(password, details, entropy) {
let score = 0;
// Length scoring (0-40 points)
score += Math.min(40, details.length * 2.5);
// Character variety (0-25 points)
if (details.hasUppercase)
score += 6.25;
if (details.hasLowercase)
score += 6.25;
if (details.hasNumbers)
score += 6.25;
if (details.hasSymbols)
score += 6.25;
// Entropy bonus (0-25 points)
score += Math.min(25, entropy / 4);
// Penalties
if (details.hasRepeated)
score -= 10;
if (details.hasSequential)
score -= 15;
if (details.hasCommonPatterns)
score -= 20;
if (details.length < 8)
score -= 20;
if (this.commonPasswords.has(password.toLowerCase()))
score -= 50;
return Math.max(0, Math.min(100, score));
}
findVulnerabilities(password, details) {
const vulnerabilities = [];
if (details.length < 8)
vulnerabilities.push("Password too short");
if (details.hasRepeated)
vulnerabilities.push("Contains repeated characters");
if (details.hasSequential)
vulnerabilities.push("Contains sequential patterns");
if (details.hasCommonPatterns)
vulnerabilities.push("Contains common patterns");
if (this.commonPasswords.has(password.toLowerCase())) {
vulnerabilities.push("Password is commonly used");
}
return vulnerabilities;
}
generateFeedback(details, vulnerabilities) {
const feedback = [];
if (details.length < 12)
feedback.push("Consider using a longer password");
if (!details.hasUppercase)
feedback.push("Add uppercase letters");
if (!details.hasLowercase)
feedback.push("Add lowercase letters");
if (!details.hasNumbers)
feedback.push("Add numbers");
if (!details.hasSymbols)
feedback.push("Add special characters");
if (vulnerabilities.length > 0) {
feedback.push("Address security vulnerabilities");
}
return feedback.length > 0 ? feedback : ["Password is strong"];
}
estimateCrackTime(score, entropy) {
// More accurate calculation based on entropy and modern hardware capabilities
if (entropy >= 80)
return "Centuries";
if (entropy >= 60)
return "Decades";
if (entropy >= 50)
return "Years";
if (entropy >= 40)
return "Months";
if (entropy >= 30)
return "Weeks";
if (entropy >= 25)
return "Days";
if (entropy >= 20)
return "Hours";
if (entropy >= 15)
return "Minutes";
if (entropy >= 10)
return "Seconds";
return "Instant";
}
hasSequentialChars(password) {
for (let i = 0; i < password.length - 2; i++) {
const char1 = password.charCodeAt(i);
const char2 = password.charCodeAt(i + 1);
const char3 = password.charCodeAt(i + 2);
if (char2 === char1 + 1 && char3 === char2 + 1) {
return true;
}
}
return false;
}
hasCommonPatterns(password) {
return this.keyboardPatterns.some((pattern) => pattern.test(password));
}
parseHashMetadata(hash) {
// Parse XyPrissSecurity format (if available)
if (hash.startsWith("$xypriss$")) {
return this.parseXyPrissHash(hash);
}
// Parse bcrypt format
if (hash.startsWith("$2a$") ||
hash.startsWith("$2b$") ||
hash.startsWith("$2y$")) {
return this.parseBcryptHash(hash);
}
// Parse Argon2 format
if (hash.startsWith("$argon2")) {
return this.parseArgon2Hash(hash);
}
// Parse scrypt format
if (hash.includes("scrypt") ||
hash.startsWith("$s0$") ||
hash.startsWith("$s1$")) {
return this.parseScryptHash(hash);
}
// Parse PBKDF2 format
if (hash.includes("pbkdf2") || hash.includes("$pbkdf2")) {
return this.parsePBKDF2Hash(hash);
}
// Fallback for unknown formats
return this.parseGenericHash(hash);
}
parseXyPrissHash(hash) {
try {
// Parse XyPrissSecurity specific format
const parts = hash.split("$");
if (parts.length < 4)
throw new Error("Invalid XyPrissSecurity hash format");
const version = parts[2];
const metadata = JSON.parse(Buffer.from(parts[3], "base64").toString());
return {
algorithm: metadata.algorithm || passwordTypes.PasswordAlgorithm.ARGON2ID,
securityLevel: metadata.securityLevel || passwordTypes.PasswordSecurityLevel.HIGH,
iterations: metadata.iterations,
timestamp: metadata.timestamp || Date.now(),
version,
};
}
catch (error) {
return this.parseGenericHash(hash);
}
}
parseBcryptHash(hash) {
const rounds = parseInt(hash.split("$")[2] || "10");
return {
algorithm: passwordTypes.PasswordAlgorithm.BCRYPT_PLUS,
securityLevel: rounds >= 12
? passwordTypes.PasswordSecurityLevel.HIGH
: passwordTypes.PasswordSecurityLevel.STANDARD,
iterations: Math.pow(2, rounds),
timestamp: Date.now() - 365 * 24 * 60 * 60 * 1000, // Estimate 1 year old
};
}
parseArgon2Hash(hash) {
const parts = hash.split("$");
const variant = parts[1];
const params = parts[3]?.split(",") || [];
const iterations = parseInt(params.find((p) => p.startsWith("t="))?.substring(2) || "3");
const memory = parseInt(params.find((p) => p.startsWith("m="))?.substring(2) || "65536");
let algorithm = passwordTypes.PasswordAlgorithm.ARGON2ID;
if (variant === "argon2i")
algorithm = passwordTypes.PasswordAlgorithm.ARGON2I;
if (variant === "argon2d")
algorithm = passwordTypes.PasswordAlgorithm.ARGON2D;
return {
algorithm,
securityLevel: memory >= 65536
? passwordTypes.PasswordSecurityLevel.HIGH
: passwordTypes.PasswordSecurityLevel.STANDARD,
iterations,
timestamp: Date.now() - 180 * 24 * 60 * 60 * 1000, // Estimate 6 months old
};
}
parseScryptHash(hash) {
return {
algorithm: passwordTypes.PasswordAlgorithm.SCRYPT,
securityLevel: passwordTypes.PasswordSecurityLevel.HIGH,
iterations: 32768,
timestamp: Date.now() - 90 * 24 * 60 * 60 * 1000, // Estimate 3 months old
};
}
parsePBKDF2Hash(hash) {
const iterations = this.extractIterationsFromPBKDF2(hash);
return {
algorithm: passwordTypes.PasswordAlgorithm.PBKDF2_SHA512,
securityLevel: iterations >= 100000
? passwordTypes.PasswordSecurityLevel.STANDARD
: passwordTypes.PasswordSecurityLevel.STANDARD,
iterations,
timestamp: Date.now() - 730 * 24 * 60 * 60 * 1000, // Estimate 2 years old
};
}
parseGenericHash(hash) {
// Infer from hash characteristics
if (hash.length >= 128) {
return {
algorithm: passwordTypes.PasswordAlgorithm.MILITARY,
securityLevel: passwordTypes.PasswordSecurityLevel.MILITARY,
timestamp: Date.now(),
};
}
else if (hash.length >= 64) {
return {
algorithm: passwordTypes.PasswordAlgorithm.ARGON2ID,
securityLevel: passwordTypes.PasswordSecurityLevel.HIGH,
timestamp: Date.now() - 30 * 24 * 60 * 60 * 1000,
};
}
else {
return {
algorithm: passwordTypes.PasswordAlgorithm.PBKDF2_SHA512,
securityLevel: passwordTypes.PasswordSecurityLevel.STANDARD,
iterations: 10000,
timestamp: Date.now() - 365 * 24 * 60 * 60 * 1000,
};
}
}
extractIterationsFromPBKDF2(hash) {
const iterationMatch = hash.match(/\$(\d+)\$/);
if (iterationMatch) {
return parseInt(iterationMatch[1]);
}
const iterMatch = hash.match(/iter[=:](\d+)/i);
if (iterMatch) {
return parseInt(iterMatch[1]);
}
return 10000; // Default PBKDF2 iterations
}
isOutdatedAlgorithm(algorithm) {
const outdatedAlgorithms = [
passwordTypes.PasswordAlgorithm.PBKDF2_SHA512, // If iterations are too low
];
return outdatedAlgorithms.includes(algorithm);
}
isWeakHash(hashInfo) {
switch (hashInfo.algorithm) {
case passwordTypes.PasswordAlgorithm.PBKDF2_SHA512:
return (hashInfo.iterations || 0) < 100000;
case passwordTypes.PasswordAlgorithm.BCRYPT_PLUS:
return (hashInfo.iterations || 0) < 4096; // 2^12 rounds
case passwordTypes.PasswordAlgorithm.SCRYPT:
return (hashInfo.iterations || 0) < 16384;
case passwordTypes.PasswordAlgorithm.ARGON2ID:
case passwordTypes.PasswordAlgorithm.ARGON2I:
case passwordTypes.PasswordAlgorithm.ARGON2D:
return (hashInfo.securityLevel === passwordTypes.PasswordSecurityLevel.STANDARD);
default:
return false;
}
}
getSecurityLevelScore(level) {
switch (level) {
case passwordTypes.PasswordSecurityLevel.QUANTUM_RESISTANT:
return 100;
case passwordTypes.PasswordSecurityLevel.MILITARY:
return 90;
case passwordTypes.PasswordSecurityLevel.MAXIMUM:
return 80;
case passwordTypes.PasswordSecurityLevel.HIGH:
return 70;
case passwordTypes.PasswordSecurityLevel.STANDARD:
return 50;
default:
return 30;
}
}
}
exports.PasswordSecurity = PasswordSecurity;
//# sourceMappingURL=password-security.js.map