let generatedResults = []; let myChart = null; // --- CHARACTER SETS --- const CHARS = { LOWERCASE: 'abcdefghijklmnopqrstuvwxyz', UPPERCASE: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', NUMBERS: '0123456789', SPECIAL: '!@#$%^&*()_+-=[]{}|;:,.<>?/~`' }; const CRACK_SPEED_PER_SECOND = 10000000000; // 10 billion guesses/sec (modern GPU array) // --- UTILITIES --- function getElement(id) { return document.getElementById(id); } function smoothScrollTo(element) { element.scrollIntoView({ behavior: 'smooth', block: 'start' }); } function calculateEntropy(length, keyspace) { if (keyspace <= 1 || length <= 0) return 0; // Entropy formula: E = L * log2(R) return length * (Math.log(keyspace) / Math.log(2)); } function formatCrackTime(seconds) { if (seconds === 0 || seconds === Infinity) return 'Instant'; const units = [ { limit: 60, name: 'second' }, { limit: 60, name: 'minute' }, { limit: 24, name: 'hour' }, { limit: 365.25 / 12, name: 'month' }, // Average month { limit: 100, name: 'year' }, { limit: Infinity, name: 'century' } ]; let time = seconds; let unitIndex = 0; while (unitIndex < units.length - 1 && time >= units[unitIndex].limit) { time /= units[unitIndex].limit; unitIndex++; } const unitName = units[unitIndex].name; const value = time < 1000 ? time.toFixed(1) : time.toExponential(2); return `${value} ${unitName}${time > 1 && time < 1000 && unitName !== 'century' ? 's' : ''}`; } // --- CORE GENERATION LOGIC --- function generatePassword(length, characterSet, minReqs, excludeChars) { let password = ''; let availableChars = characterSet; // 1. Apply Exclusions to the overall available character set if (excludeChars) { for (const char of excludeChars) { availableChars = availableChars.split(char).join(''); } } if (availableChars.length === 0) { throw new Error("Character set is empty after applying exclusions."); } const requiredChars = []; const requiredCount = Object.keys(minReqs).length; // 2. Enforce Minimum Requirements if (minReqs.UPPERCASE) requiredChars.push(CHARS.UPPERCASE); if (minReqs.LOWERCASE) requiredChars.push(CHARS.LOWERCASE); if (minReqs.NUMBERS) requiredChars.push(CHARS.NUMBERS); if (minReqs.SPECIAL) requiredChars.push(CHARS.SPECIAL); // Ensure the total required length is not greater than the password length if (requiredCount > length) { throw new Error("Password length is too short to meet all inclusion requirements."); } // Add the mandatory characters first for (let i = 0; i < requiredChars.length; i++) { let reqSet = requiredChars[i]; // Apply exclusion to the requirement set before picking a character let cleanReqSet = reqSet; if (excludeChars) { for (const char of excludeChars) { cleanReqSet = cleanReqSet.split(char).join(''); } } if (cleanReqSet.length === 0) { throw new Error(`Required character set (e.g., uppercase) is empty after exclusion.`); } const randomIndex = Math.floor(Math.random() * cleanReqSet.length); password += cleanReqSet[randomIndex]; } // 3. Fill the rest of the password length const remainingLength = length - password.length; for (let i = 0; i < remainingLength; i++) { const randomIndex = Math.floor(Math.random() * availableChars.length); password += availableChars[randomIndex]; } // 4. Shuffle the password to ensure mandatory characters are not always at the start const passwordArray = password.split(''); for (let i = passwordArray.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [passwordArray[i], passwordArray[j]] = [passwordArray[j], passwordArray[i]]; } return passwordArray.join(''); } // --- RENDERING --- function drawChart(results) { if (!results || results.length === 0) return; // Determine Strength Categories based on Entropy (industry standard metrics) const categories = { 'Very Weak (0-35 bits)': 0, 'Weak (36-59 bits)': 0, 'Moderate (60-79 bits)': 0, 'Strong (80-119 bits)': 0, 'Very Strong (120+ bits)': 0 }; let totalEntropy = 0; results.forEach(res => { const e = res.entropy; totalEntropy += e; if (e < 36) categories['Very Weak (0-35 bits)']++; else if (e < 60) categories['Weak (36-59 bits)']++; else if (e < 80) categories['Moderate (60-79 bits)']++; else if (e < 120) categories['Strong (80-119 bits)']++; else categories['Very Strong (120+ bits)']++; }); // Update Summary Metrics const avgEntropy = totalEntropy / results.length; getElement('wp-passgen-avg-entropy').textContent = `${avgEntropy.toFixed(2)} bits`; // Calculate average crack time const avgKeyspace = Math.pow(2, avgEntropy); const avgCrackSeconds = avgKeyspace / CRACK_SPEED_PER_SECOND; getElement('wp-passgen-avg-cracktime').textContent = formatCrackTime(avgCrackSeconds); const ctx = getElement('wp-passgen-strength-chart').getContext('2d'); if (myChart) myChart.destroy(); myChart = new Chart(ctx, { type: 'bar', // Bar chart for clear strength distribution data: { labels: Object.keys(categories), datasets: [{ label: 'Number of Passwords', data: Object.values(categories), backgroundColor: [ 'rgba(220, 53, 69, 0.8)', // Very Weak (Red) 'rgba(255, 193, 7, 0.8)', // Weak (Yellow) 'rgba(23, 162, 184, 0.8)', // Moderate (Cyan) 'rgba(40, 167, 69, 0.8)', // Strong (Green) 'rgba(0, 123, 255, 0.8)' // Very Strong (Blue) ], borderColor: [ 'rgba(220, 53, 69, 1)', 'rgba(255, 193, 7, 1)', 'rgba(23, 162, 184, 1)', 'rgba(40, 167, 69, 1)', 'rgba(0, 123, 255, 1)' ], borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, title: { display: true, text: 'Generated Password Strength Distribution' } }, scales: { y: { beginAtZero: true, title: { display: true, text: 'Count' } }, x: { display: true } } } }); } function renderTable(results) { const tbody = getElement('wp-passgen-table').querySelector('tbody'); tbody.innerHTML = ''; results.forEach((res, index) => { const row = tbody.insertRow(); row.innerHTML = ` ${index + 1} ${res.password} ${res.entropy.toFixed(2)} ${res.crackTime} Copied! `; }); } // --- MAIN CONTROLLER --- function handleCalculation() { const lengthInput = getElement('wp-passgen-length'); const countInput = getElement('wp-passgen-count'); const excludeInput = getElement('wp-passgen-exclude'); const msgArea = getElement('wp-passgen-message-area'); const resultsArea = getElement('wp-passgen-results-area'); msgArea.style.display = 'none'; try { const length = parseInt(lengthInput.value) || 0; const count = parseInt(countInput.value) || 0; const exclude = excludeInput.value.trim(); if (length < 8 || length > 64) throw new Error("Length must be between 8 and 64 characters."); if (count < 1 || count > 20) throw new Error("Number of passwords must be between 1 and 20."); const minReqs = { UPPERCASE: getElement('wp-passgen-req-uppercase').checked, LOWERCASE: getElement('wp-passgen-req-lowercase').checked, NUMBERS: getElement('wp-passgen-req-numbers').checked, SPECIAL: getElement('wp-passgen-req-special').checked, }; const requiredCount = Object.values(minReqs).filter(v => v).length; if (requiredCount > length) { throw new Error(`Length (${length}) is too short to meet the ${requiredCount} selected inclusion requirements.`); } // 1. Determine the overall available character set (Keyspace R) let combinedChars = ''; if (minReqs.LOWERCASE) combinedChars += CHARS.LOWERCASE; if (minReqs.UPPERCASE) combinedChars += CHARS.UPPERCASE; if (minReqs.NUMBERS) combinedChars += CHARS.NUMBERS; if (minReqs.SPECIAL) combinedChars += CHARS.SPECIAL; // Remove exclusion characters from the combined set let effectiveKeyspaceChars = combinedChars; if (exclude) { for (const char of exclude) { effectiveKeyspaceChars = effectiveKeyspaceChars.split(char).join(''); } } if (effectiveKeyspaceChars.length === 0) { throw new Error("No characters left to generate passwords. Check your requirements and exclusions."); } const effectiveKeyspace = effectiveKeyspaceChars.length; getElement('wp-passgen-keyspace').textContent = effectiveKeyspace; // 2. Generation Loop generatedResults = []; for (let i = 0; i < count; i++) { const password = generatePassword(length, effectiveKeyspaceChars, minReqs, exclude); const entropy = calculateEntropy(length, effectiveKeyspace); const crackSeconds = Math.pow(2, entropy) / CRACK_SPEED_PER_SECOND; generatedResults.push({ id: i + 1, password: password, entropy: entropy, crackTime: formatCrackTime(crackSeconds) }); } // 3. Render Results & UX renderTable(generatedResults); drawChart(generatedResults); resultsArea.style.display = 'flex'; getElement('wp-passgen-download').disabled = false; getElement('wp-passgen-share').disabled = false; // Smooth scroll to results smoothScrollTo(resultsArea); } catch (error) { msgArea.textContent = `Error: ${error.message}`; msgArea.style.display = 'block'; resultsArea.style.display = 'none'; getElement('wp-passgen-download').disabled = true; getElement('wp-passgen-share').disabled = true; console.error("Calculation Error:", error); } } // --- EXPORT/SHARE LOGIC / CLIPBOARD --- function showCopyFeedback(element, text) { // Handles feedback for the main Download/Share buttons and the small table copy buttons. const originalText = element.textContent; element.textContent = text; // If it's a small table copy button, also show the sibling feedback span if (element.classList.contains('wp-passgen-action-btn')) { const feedbackElement = element.nextElementSibling; // The feedback span is the next sibling feedbackElement.textContent = 'Copied!'; feedbackElement.style.display = 'inline'; setTimeout(() => { feedbackElement.style.display = 'none'; }, 1500); } // Revert the button text for all buttons setTimeout(() => { element.textContent = originalText; }, 1500); } function copyToClipboard(text, element) { // Use document.execCommand('copy') as a robust fallback for clipboard in iFrames try { const textarea = document.createElement('textarea'); textarea.value = text; textarea.style.position = 'fixed'; // Prevents scrolling to bottom document.body.appendChild(textarea); textarea.focus(); textarea.select(); document.execCommand('copy'); document.body.removeChild(textarea); showCopyFeedback(element, 'Copied!'); } catch (err) { console.error('Fallback copy failed:', err); getElement('wp-passgen-message-area').textContent = 'Failed to copy password. Clipboard access denied.'; getElement('wp-passgen-message-area').style.display = 'block'; } } function downloadCSV() { if (generatedResults.length === 0) return; const headers = ["#", "Password", "Entropy (bits)", "Time to Crack"]; const rows = generatedResults.map(r => [ r.id, `"${r.password}"`, // Enclose password in quotes to handle special chars r.entropy.toFixed(2), r.crackTime.replace(/,/g, '') // Remove commas for clean CSV ]); let csvContent = headers.join(",") + "\n" + rows.map(e => e.join(",")).join("\n"); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.setAttribute("href", url); link.setAttribute("download", "passwords_breakdown.csv"); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); showCopyFeedback(getElement('wp-passgen-download'), 'Downloaded!'); } function shareResults() { const msgArea = getElement('wp-passgen-message-area'); if (navigator.share) { let shareText = "Generated Password Summary:\n"; shareText += `Average Entropy: ${getElement('wp-passgen-avg-entropy').textContent}\n`; shareText += `Keyspace: ${getElement('wp-passgen-keyspace').textContent}\n`; shareText += `\nPasswords Generated:\n`; shareText += generatedResults.slice(0, 5).map(r => `[${r.id}] ${r.password} (${r.entropy.toFixed(1)} bits)`).join('\n'); shareText += generatedResults.length > 5 ? `\n...and ${generatedResults.length - 5} more.` : ''; navigator.share({ title: 'Advanced Password Generation Results', text: shareText }).then(() => { showCopyFeedback(getElement('wp-passgen-share'), 'Shared!'); }).catch((error) => { msgArea.textContent = 'Share failed. Try copying manually.'; msgArea.style.display = 'block'; }); } else { msgArea.textContent = 'Share feature not supported on this browser. Use the Download button instead.'; msgArea.style.display = 'block'; showCopyFeedback(getElement('wp-passgen-share'), 'Not Supported!'); } } // --- INITIALIZATION (Show Sample Results on First Load) --- window.onload = function() { console.log("Tool Initialized. Generating sample results..."); handleCalculation(); };