Password Generator: Three Random Words with Custom Separators


Passwords should really be eliminated with secure methods like FIDO2, however, if you have to use them remember while complex random strings are secure, they're nearly impossible to remember. That's why I decided to build a password generator that creates memorable yet secure passwords using three random words (stored locally in the code) separated by customizable numbers and special characters.

The Problem with Traditional Password Generators

Most password generators create strings like Kx9$mL2#vR8 - highly secure but completely unmemorable. This leads people to write passwords down or reuse simple ones, defeating the security purpose entirely.

Three-Word Password Generation

I developed a password generator that creates passwords like Mountain47&Butterfly92*Sunshine - still highly secure due to length and complexity, but much more memorable because they use real words.

Visual Results

This is the visual look of the website:


Then if we look at a more secure password with the copy option in the same format as the "Generate" button:


How the Algorithm Works

Let’s look at how the algorithm works to generate your password.

1. Word Selection Strategy

The generator uses a local dictionary of over 300 words organized by length (3-10 characters). When you select a target word length, the algorithm:

function getWordsByLength(targetLength) {
    // First try to get words of exact length
    let filtered = wordList.filter(word => word.length === targetLength);
    
    // If no exact matches, get words within 1 character of target
    if (filtered.length === 0) {
        filtered = wordList.filter(word => 
            Math.abs(word.length - targetLength) <= 1
        );
    }
    
    // If still no matches, get words within 2 characters of target
    if (filtered.length === 0) {
        filtered = wordList.filter(word => 
            Math.abs(word.length - targetLength) <= 2
        );
    }
    
    // Fallback to all words if nothing found
    return filtered.length > 0 ? filtered : wordList;
}

This ensures you get words as close as possible to your desired length rather than just random short words.

2. Separator Generation

The separators between words are dynamically generated based on your preferences:

function generateSeparator(numCount, specCount) {
    let separator = '';
    
    // Add numbers
    for (let i = 0; i < numCount; i++) {
        separator += getRandomInt(0, 9);
    }
    
    // Add special characters
    for (let i = 0; i < specCount; i++) {
        separator += getRandomElement(specialChars);
    }
    
    // Shuffle the separator for randomness
    return separator.split('').sort(() => Math.random - 0.5).join('');
}

This creates unique separators like 47&923*, or 1$+ depending on your settings.

3. Password Assembly

The final password is assembled by:

  1. Selecting three unique random words
  2. Capitalizing the first letter of each word
  3. Generating two unique separators
  4. Combining everything together
function generatePassword() {
    const maxWordLength = parseInt(wordLengthSlider.value);
    const numCount = parseInt(numberCountSlider.value);
    const specCount = parseInt(specialCountSlider.value);
    
    // Get filtered words
    const availableWords = getWordsByLength(maxWordLength);
    
    // Select three unique random words
    const words = [];
    for (let i = 0; i < 3; i++) {
        let word;
        do {
            word = getRandomElement(availableWords);
        } while (words.includes(word) && availableWords.length > 3);
        words.push(word);
    }
    
    // Capitalize first letter of each word
    const capitalizedWords = words.map(word => 
        word.charAt(0).toUpperCase() + word.slice(1)
    );
    
    // Generate unique separators
    const separator1 = generateSeparator(numCount, specCount);
    const separator2 = generateSeparator(numCount, specCount);
    
    // Combine everything
    return `${capitalizedWords[0]}${separator1}${capitalizedWords[1]}${separator2}
    ${capitalizedWords[2]}`;
}

Password Strength

  • Length: Typically 20-40+ characters
  • Character variety: Uppercase, lowercase, numbers, special characters
  • Entropy: High due to word selection randomness and separator complexity
  • Memorability: Significantly higher than random strings

Example Password

Password: Mountain47&Butterfly92*Sunshine

  • Length: 35 characters
  • Contains: 3 words + 6 numbers + 2 special chars

UX Features

The interface provides three sliders for instant customization:

  • Target Word Length (3-10 characters)
  • Numbers per separator (1-4 digits)
  • Special characters per separator (1-4 symbols)

Performance Consideration's 

The generator is lightning-fast because:

  • Uses vanilla JavaScript (no frameworks)
  • Lightweight word list stored in memory
  • Efficient filtering algorithms
  • No external API calls required

Word Database Structure

I organized the word list by length categories for optimal performance:

const wordList = [
    // 3-4 letter words
    'cat', 'dog', 'sun', 'moon', 'star', 'fire',
    
    // 5 letter words  
    'apple', 'beach', 'cloud', 'dance', 'eagle',
    
    // ... continuing through 10-letter words
    'grasshopper', 'cheesecake', 'refrigerator'
];

The local code includes over 300 words spanning, however, as this dictionary is local, you can customize it of your organizational requirements:

  • Nature: Mountain, Waterfall, Sunshine
  • Animals: Elephant, Butterfly, Dolphin
  • Food: Chocolate, Pineapple, Sandwich
  • Objects: Umbrella, Bicycle, Telescope
  • Emotions: Happiness, Peaceful, Wonderful

Copy-to-Clipboard Implementation

I implemented The ability to copy the password to your clipboard straight from the tool:

async function copyToClipboard(text) {
    try {
        await navigator.clipboard.writeText(text);
        showCopiedFeedback();
    } catch (err) {
        // Fallback for older browsers
        const textArea = document.createElement('textarea');
        textArea.value = text;
        document.body.appendChild(textArea);
        textArea.select();
        document.execCommand('copy');
        document.body.removeChild(textArea);
        showCopiedFeedback();
    }
}

HTML Code

Here's the complete, production-ready code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Smart Password Generator</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
            background: white;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 20px;
        }

        .container {
            background: rgba(255, 255, 255, 0.95);
            backdrop-filter: blur(10px);
            border-radius: 24px;
            padding: 40px;
            max-width: 500px;
            width: 100%;
            box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
            border: 1px solid rgba(255, 255, 255, 0.2);
        }

        h1 {
            text-align: center;
            color: #2d3748;
            margin-bottom: 8px;
            font-size: 2rem;
            font-weight: 600;
        }

        .subtitle {
            text-align: center;
            color: #718096;
            margin-bottom: 32px;
            font-size: 0.95rem;
        }

        .password-display {
            background: #f7fafc;
            border: 2px solid #e2e8f0;
            border-radius: 16px;
            padding: 20px;
            margin-bottom: 24px;
            position: relative;
            transition: all 0.3s ease;
        }

        .password-display:hover {
            border-color: #667eea;
        }

        .password-text {
            font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
            font-size: 1.1rem;
            word-break: break-all;
            color: #2d3748;
            min-height: 28px;
            display: flex;
            align-items: center;
        }

        .copy-btn {
            position: absolute;
            top: 12px;
            right: 12px;
            background: #667eea;
            color: white;
            border: none;
            border-radius: 8px;
            padding: 8px 12px;
            font-size: 0.8rem;
            cursor: pointer;
            transition: all 0.2s ease;
            opacity: 0;
        }

        .password-display:hover .copy-btn {
            opacity: 1;
        }

        .copy-btn:hover {
            background: #5a67d8;
            transform: translateY(-1px);
        }

        .copy-btn.copied {
            background: #48bb78;
            opacity: 1;
        }

        .controls {
            display: grid;
            gap: 20px;
            margin-bottom: 24px;
        }

        .control-group {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 16px 20px;
            background: #f7fafc;
            border-radius: 12px;
            border: 1px solid #e2e8f0;
        }

        .control-label {
            font-weight: 500;
            color: #4a5568;
            font-size: 0.9rem;
        }

        .control-input {
            display: flex;
            align-items: center;
            gap: 12px;
        }

        input[type="range"] {
            width: 80px;
            height: 6px;
            border-radius: 3px;
            background: #e2e8f0;
            outline: none;
            -webkit-appearance: none;
        }

        input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 18px;
            height: 18px;
            border-radius: 50%;
            background: #667eea;
            cursor: pointer;
            transition: all 0.2s ease;
        }

        input[type="range"]::-webkit-slider-thumb:hover {
            transform: scale(1.1);
            background: #5a67d8;
        }

        .value-display {
            min-width: 24px;
            text-align: center;
            font-weight: 600;
            color: #667eea;
        }

        .generate-btn {
            width: 100%;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border: none;
            border-radius: 16px;
            padding: 16px 24px;
            font-size: 1.1rem;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
            position: relative;
            overflow: hidden;
        }

        .generate-btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 12px 24px rgba(102, 126, 234, 0.3);
        }

        .generate-btn:active {
            transform: translateY(0);
        }

        .generate-btn::before {
            content: '';
            position: absolute;
            top: 0;
            left: -100%;
            width: 100%;
            height: 100%;
            background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
            transition: left 0.5s;
        }

        .generate-btn:hover::before {
            left: 100%;
        }

        @media (max-width: 480px) {
            .container {
                padding: 24px;
                margin: 10px;
            }
            
            h1 {
                font-size: 1.6rem;
            }
            
            .control-group {
                flex-direction: column;
                gap: 12px;
                text-align: center;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Smart Password Generator</h1>
        <p class="subtitle">Generate secure passwords using three random words</p>
        
        <div class="password-display">
            <div class="password-text" id="passwordText">Click generate to create your password</div>
            <button class="copy-btn" id="copyBtn">Copy</button>
        </div>
        
        <div class="controls">
            <div class="control-group">
                <span class="control-label">Target Word Length</span>
                <div class="control-input">
                    <input type="range" id="wordLength" min="3" max="10" value="6">
                    <span class="value-display" id="wordLengthValue">6</span>
                </div>
            </div>
            
            <div class="control-group">
                <span class="control-label">Numbers</span>
                <div class="control-input">
                    <input type="range" id="numberCount" min="1" max="4" value="2">
                    <span class="value-display" id="numberCountValue">2</span>
                </div>
            </div>
            
            <div class="control-group">
                <span class="control-label">Special Characters</span>
                <div class="control-input">
                    <input type="range" id="specialCount" min="1" max="4" value="2">
                    <span class="value-display" id="specialCountValue">2</span>
                </div>
            </div>
        </div>
        
        <button class="generate-btn" id="generateBtn">Generate Password</button>
    </div>

    <script>
        // Expanded diverse word list covering nature, animals, food, emotions, activities, and more
        const wordList = [
            // 3-4 letter words
            'cat', 'dog', 'sun', 'moon', 'star', 'fire', 'wind', 'rain', 'snow', 'rock', 'tree', 'fish', 
            'bird', 'lake', 'hill', 'road', 'book', 'cake', 'game', 'home', 'lion', 'bear', 'frog', 'duck',
            'rose', 'leaf', 'sand', 'wave', 'gold', 'blue', 'warm', 'cold', 'soft', 'loud', 'fast', 'slow',
            'love', 'hope', 'fear', 'joy', 'pain', 'calm', 'wild', 'free', 'door', 'window', 'chair', 'desk',
            
            // 5 letter words  
            'apple', 'beach', 'cloud', 'dance', 'eagle', 'flame', 'grace', 'happy', 'magic', 'night',
            'ocean', 'peace', 'river', 'storm', 'tiger', 'water', 'brave', 'dream', 'earth', 'frost',
            'heart', 'light', 'piano', 'queen', 'smile', 'truth', 'whale', 'world', 'youth', 'amber',
            'coral', 'grape', 'honey', 'ivory', 'jasper', 'lemon', 'mango', 'olive', 'pearl', 'quilt',
            'robin', 'sugar', 'tulip', 'velvet', 'waltz', 'zebra', 'chess', 'flute', 'horse', 'knife',
            'mouse', 'peach', 'bread', 'sheep', 'house', 'pizza', 'grape', 'plant', 'shirt', 'boots',
            
            // 6 letter words
            'castle', 'dragon', 'forest', 'guitar', 'hammer', 'island', 'jungle', 'kettle', 'legend', 'marble',
            'nature', 'orange', 'palace', 'quartz', 'rabbit', 'silver', 'temple', 'unique', 'violet', 'wisdom',
            'yellow', 'branch', 'butter', 'cherry', 'flower', 'garden', 'lovely', 'mother', 'nephew', 'pickle',
            'stream', 'turkey', 'vegetable', 'window', 'banana', 'candle', 'doctor', 'feather', 'giraffe', 'kitten',
            'monkey', 'nephew', 'pickle', 'squirrel', 'turtle', 'valley', 'winter', 'zipper', 'bottle', 'cookie',
            'father', 'golden', 'insect', 'jacket', 'ladder', 'magnet', 'nephew', 'pocket', 'powder', 'sister',
            
            // 7 letter words
            'balance', 'captain', 'diamond', 'evening', 'freedom', 'gateway', 'harmony', 'journey', 'kitchen',
            'library', 'mystery', 'morning', 'rainbow', 'science', 'thunder', 'victory', 'whisper', 'crystal',
            'perfect', 'station', 'village', 'healthy', 'bedroom', 'chicken', 'dolphin', 'elephant', 'feather',
            'giraffe', 'hamster', 'jewelry', 'kitchen', 'ladybug', 'musical', 'nothing', 'orchard', 'penguin',
            'quickly', 'raccoon', 'sparrow', 'tornado', 'umbrella', 'vanilla', 'walrus', 'bicycle', 'blanket',
            'cabinet', 'dancing', 'embrace', 'farming', 'growing', 'holiday', 'imagine', 'jogging', 'laundry',
            
            // 8 letter words
            'mountain', 'treasure', 'elephant', 'sandwich', 'birthday', 'peaceful', 'starlight', 'universe',
            'adventure', 'beautiful', 'chocolate', 'discovery', 'forgotten', 'guardian', 'hospital', 'infinite',
            'kindness', 'learning', 'midnight', 'official', 'paradise', 'question', 'romantic', 'sunshine',
            'vacation', 'wildlife', 'xylophone', 'yearning', 'butterfly', 'dinosaur', 'flamingo', 'honeybee',
            'jellyfish', 'kangaroo', 'laughter', 'medicine', 'necklace', 'purchase', 'sandwich', 'umbrella',
            'vitamins', 'windmill', 'breakfast', 'cinnamon', 'doorbell', 'envelope', 'firework', 'greeting',
            'hedgehog', 'icecream', 'jukebox', 'keyboard', 'lobster', 'magazine', 'notebook', 'sandwich',
            
            // 9 letter words
            'adventure', 'beautiful', 'butterfly', 'challenge', 'education', 'fantastic', 'gardening',
            'happiness', 'important', 'knowledge', 'landscape', 'marketing', 'nightfall', 'operation',
            'pineapple', 'questions', 'raspberry', 'something', 'tradition', 'valentine', 'wonderful',
            'breakfast', 'community', 'delicious', 'everybody', 'furniture', 'gathering', 'handshake',
            'invention', 'jellybean', 'knowledge', 'lemonade', 'moonlight', 'newspaper', 'orchestra',
            'parachute', 'quicksand', 'rhinoceros', 'snowflake', 'telephone', 'underwear', 'vegetable',
            'waterfall', 'xylophone', 'yesterday', 'zucchini', 'adventure', 'beautiful', 'crocodile',
            'dragonfly', 'evergreen', 'firetruck', 'gentleman', 'hamburger', 'icicle', 'jellyfish',
            
            // 10 letter words
            'basketball', 'everything', 'friendship', 'generation', 'helicopter', 'incredible', 'lighthouse',
            'playground', 'restaurant', 'strawberry', 'understand', 'volleyball', 'watermelon', 'background',
            'candlelight', 'dictionary', 'everything', 'fingernail', 'grasshopper', 'hummingbird', 'impossible',
            'jabberwocky', 'kaleidoscope', 'lawnmower', 'magnificent', 'nutcracker', 'overwhelmed', 'peppermint',
            'quarantine', 'rhinoceros', 'skateboard', 'tablespoon', 'underwater', 'vegetables', 'windshield',
            'xylophone', 'yellowbird', 'zookeeper', 'automobile', 'broomstick', 'cheesecake', 'dragonfly',
            'earthquake', 'fingertips', 'grandmother', 'helicopter', 'icebreaker', 'jackhammer', 'keyboard',
            'linebacker', 'motorcycle', 'newsletter', 'observatory', 'porcupine', 'quicksilver', 'refrigerator'
        ];

        const specialChars = ['!', '@', '#', '$', '%', '&', '*', '+', '=', '?'];
        
        // DOM elements
        const passwordText = document.getElementById('passwordText');
        const copyBtn = document.getElementById('copyBtn');
        const generateBtn = document.getElementById('generateBtn');
        const wordLengthSlider = document.getElementById('wordLength');
        const wordLengthValue = document.getElementById('wordLengthValue');
        const numberCountSlider = document.getElementById('numberCount');
        const numberCountValue = document.getElementById('numberCountValue');
        const specialCountSlider = document.getElementById('specialCount');
        const specialCountValue = document.getElementById('specialCountValue');

        // Update value displays
        wordLengthSlider.addEventListener('input', (e) => {
            wordLengthValue.textContent = e.target.value;
        });

        numberCountSlider.addEventListener('input', (e) => {
            numberCountValue.textContent = e.target.value;
        });

        specialCountSlider.addEventListener('input', (e) => {
            specialCountValue.textContent = e.target.value;
        });

        // Generate random number
        function getRandomInt(min, max) {
            return Math.floor(Math.random() * (max - min + 1)) + min;
        }

        // Get random element from array
        function getRandomElement(arr) {
            return arr[Math.floor(Math.random() * arr.length)];
        }

        // Filter words by length - get words close to target length
        function getWordsByLength(targetLength) {
            // First try to get words of exact length
            let filtered = wordList.filter(word => word.length === targetLength);
            
            // If no exact matches, get words within 1 character of target
            if (filtered.length === 0) {
                filtered = wordList.filter(word => 
                    Math.abs(word.length - targetLength) <= 1
                );
            }
            
            // If still no matches, get words within 2 characters of target
            if (filtered.length === 0) {
                filtered = wordList.filter(word => 
                    Math.abs(word.length - targetLength) <= 2
                );
            }
            
            // Fallback to all words if nothing found
            return filtered.length > 0 ? filtered : wordList;
        }

        // Generate separator with numbers and special characters
        function generateSeparator(numCount, specCount) {
            let separator = '';
            
            // Add numbers
            for (let i = 0; i < numCount; i++) {
                separator += getRandomInt(0, 9);
            }
            
            // Add special characters
            for (let i = 0; i < specCount; i++) {
                separator += getRandomElement(specialChars);
            }
            
            // Shuffle the separator
            return separator.split('').sort(() => Math.random() - 0.5).join('');
        }

        // Generate password
        function generatePassword() {
            const maxWordLength = parseInt(wordLengthSlider.value);
            const numCount = parseInt(numberCountSlider.value);
            const specCount = parseInt(specialCountSlider.value);
            
            // Get filtered words
            const availableWords = getWordsByLength(maxWordLength);
            
            // Select three random words
            const words = [];
            for (let i = 0; i < 3; i++) {
                let word;
                do {
                    word = getRandomElement(availableWords);
                } while (words.includes(word) && availableWords.length > 3);
                words.push(word);
            }
            
            // Capitalize first letter of each word
            const capitalizedWords = words.map(word => 
                word.charAt(0).toUpperCase() + word.slice(1)
            );
            
            // Generate separators
            const separator1 = generateSeparator(numCount, specCount);
            const separator2 = generateSeparator(numCount, specCount);
            
            // Combine everything
            const password = `${capitalizedWords[0]}${separator1}${capitalizedWords[1]}${separator2}${capitalizedWords[2]}`;
            
            return password;
        }

        // Copy to clipboard
        async function copyToClipboard(text) {
            try {
                await navigator.clipboard.writeText(text);
                copyBtn.textContent = 'Copied!';
                copyBtn.classList.add('copied');
                setTimeout(() => {
                    copyBtn.textContent = 'Copy';
                    copyBtn.classList.remove('copied');
                }, 2000);
            } catch (err) {
                // Fallback for older browsers
                const textArea = document.createElement('textarea');
                textArea.value = text;
                document.body.appendChild(textArea);
                textArea.select();
                document.execCommand('copy');
                document.body.removeChild(textArea);
                
                copyBtn.textContent = 'Copied!';
                copyBtn.classList.add('copied');
                setTimeout(() => {
                    copyBtn.textContent = 'Copy';
                    copyBtn.classList.remove('copied');
                }, 2000);
            }
        }

        // Event listeners
        generateBtn.addEventListener('click', () => {
            const password = generatePassword();
            passwordText.textContent = password;
        });

        copyBtn.addEventListener('click', () => {
            const password = passwordText.textContent;
            if (password && password !== 'Click generate to create your password') {
                copyToClipboard(password);
            }
        });

        // Generate initial password
        window.addEventListener('load', () => {
            const password = generatePassword();
            passwordText.textContent = password;
        });
    </script>
</body>
</html>
Previous Post Next Post

نموذج الاتصال