Most password generators will give you very strong passwords like k6P;**_Fs/W7S)y-%5>6 that will be impossible to brute force and almost as impossible to remember. Storing your password in a password manager, secret keeper or some other product is a good idea. I store my passwords in one of those as well. The benefit for having a complex password that you can remember, is you only need to fire up the password manager if you can’t remember. Most of the time you just type away and you’re in. It may be a false sense of control but I’m happy with it.

The biggest limitation with the method we’re using today is a word file. Not MS Word the application, word as in dictionary file or list. A compilation of words. There are files of word lists all over the internet. If someone has access to your list of words, it could allow them to brute force your password from that. So go find yourself a list. You can always get a different one if it doesn’t meet your needs. The file should be one word per line. If not you’ll need to figure out an alternate delimiter when importing and exporting the file.

Requirements

For this script, the password requirements are:

  • 18 - 26 characters long.
  • At least 1 capital letter.
  • At least 1 number.
  • At least 1 symbol.

These requirements are based on my experience. Our password is going to have some random and some fixed structure. We’re going to split out word file into individual files for each length of word. A file of 3 letter words, a file of 4 letter words etc. Once we have word files we can start building our password. Our end password will be word symbol word number. Evening@Fireplace3 for example.

Setup

First we need to breakdown our large, single word file into individual files by word length.

    
        
# By default Get-Content uses \n as the delimiter. 
# If you ended up with a file that isn't one word per line,
# you'll need to use -Delimiter to set it appropriately.
$words = Get-Content .\your_big_file_of_words.txt 
# Pipe the words to a foreach loop, make sure they're all lowercase
# and then append them to the file for it's length.
$words | ForEach { $($_.ToLower()) | out-file .\$($_.length)-letterwords.txt -Append }
    
You should have something like this now.
 
     
        
Get-ChildItem
    Directory:  C:\Users\JeffH\PasswordTool

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a----        5/26/2021  12:04 PM     3.87KB 10-letterwords.txt
-a----        5/26/2021  12:04 PM     2.36KB 11-letterwords.txt
-a----        5/26/2021  12:04 PM     1.07KB 12-letterwords.txt
-a----        5/26/2021  12:04 PM      752   13-letterwords.txt
-a----        5/26/2021  12:04 PM      386   14-letterwords.txt
-a----        5/26/2021  12:04 PM       38   16-letterwords.txt
-a----        5/26/2021  12:04 PM      274   2-letterwords.txt
-a----        5/26/2021  12:04 PM     1.70KB 3-letterwords.txt
-a----        5/26/2021  12:04 PM     5.88KB 4-letterwords.txt
-a----        5/26/2021  12:04 PM     6.61KB 5-letterwords.txt
-a----        5/26/2021  12:04 PM     7.67KB 6-letterwords.txt
-a----        5/26/2021  12:04 PM     7.82KB 7-letterwords.txt
-a----        5/26/2021  12:04 PM     6.02KB 8-letterwords.txt
-a----        5/26/2021  12:04 PM     4.86KB 9-letterwords.txt
 
    

Set some parameters

To make this useful, we add a param block to pass in options for use as a script. If you want longer or shorter passwords, you’ll need to change the ValidateRange, the $words hash and the $wordlength hash to reflect your choices.

    
        
param(
    [ValidateRange(18,26)]
    [int]$PasswordLength = 18, # allow 18 to 26 with a default of 18.
    [int]$PasswordCount = 10,  # number of passwords to create.
    [int]$Cap = 1,             # Which letter to capitalize.
    [switch]$leet              # Switch to use "leet speak"
)
    

Importing word files.

For different length passwords, we use these as the building blocks. I opted to load them all at once instead of trying to only load the ones needed for a particular length password. I’ve also hard coded the name and location of the files. Better practice would be to test for each file and exit gracefully if the file isn’t there.

    
        
#########
# word files
$four = get-content .\4-common.txt;
$five = get-content .\5-common.txt;
$six = get-content .\6-common.txt;
$seven = get-content .\7-common.txt;
$eight = get-content .\8-common.txt;
$nine = get-content .\9-common.txt
$ten = get-content .\10-common.txt
$eleven = get-content .\11-common.txt
$twelve = get-content .\12-common.txt
#########
#
    

Create our TitleCase tool

Creating a System.Globalization.TextInfo object will give us access to the ToTitleCase method. We’ll use this later to capitalize a single letter in each word.

    
        
$t = (Get-Culture).TextInfo;
    

Create symbol and number lists.

Here we hard code our list of symbols and number. Edit this to suit your needs. Some environments have different symbols that are allowed. The second list of symbols is used when the leet switch is used. I’ve noted the index of the last element for use with Get-Random later.

    
        
$symbols = ('!', '@', '#', '$', '%', '+', '~'); # index of last element 6
$symbols2 = ('!',  '#', '%', '+', '~'); # index of last element 4
$numbers = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9); # index of last element 9
    

Set Composition based on password length.

Here is where we choose how our password is composed. For example, instead of two 8 character words for an 18 length password, I could use an 8 letter word and two 4 letter words. A zero in the last place is ignored.

    
        
$words = @{
    18 = (8, 8, 0); 
    19 = (4, 9, 4);
    20 = (9, 9, 0);
    21 = (9, 5, 5);
    22 = (10, 10, 0);
    23 = (5, 11, 5);
    24 = (11, 11, 0); 
    25 = (11, 12, 0);
    26 = (12, 12, 0);
};
    

Determine which collection to pull from.

This pairs the values from the combonation we chose in the previous step with the array holding each length word list.

    
        
$wordlength = @{
    4 = $four;
    5 = $five;
    6 = $six;
    7 = $seven;
    8 = $eight;
    9 = $nine;
    10 = $ten;
    11 = $eleven;
    12 = $twelve;
}
    

Create function to retrieve a random word.

We create a function called Get-Word and give it an alias of gw.

    
        

function Get-Word {
    [alias("gw")]
    param(
        $length
    )
    if($length -gt 0){
        # get the array for words $length long
        $array = $wordlength[$length]
        # get the end of the array
        $arrayend = $($array.length) - 1;
        # get our word.
        $w = $array[(get-random $arrayend)]
    

Capitalize one letter in the word.

Here’s where we make use of the ToTitleCase method from earlier and the $Cap switch from our parameter block. Picking a letter other than the first letter will add an exponential layer of protection against brute force attacks.

    
        
        # if 1 just titlecase else split the word, titlecase and put it 
        # back together
        if($cap -eq 1){
            $w = $t.ToTitleCase($w)
        }else{
            $w = $($w.Substring(0,($cap -1))) + $($t.ToTitleCase($w.Substring(($cap - 1))))
        }
    }else{
        # since we are using 2 or 3 word
        # combinations the length in the 
        # third spot could be zero in which 
        # case we return an empty value
        $w = '';
    }

    return $w;
} 
    

Put it together

Loop for each password to generate.

The $PasswordCount parameter is used to tell our script how many passwords to generate.

    
        
for ($i = 0; $i -lt $PasswordCount; $i++) {
    # Grab the array from the $words hash
    # to tell us what length wordfiles to
    # pull from
    $wordarray = $words[$passwordlength];
    # gw is the alias we set for Get-Word
    $w1 = gw $wordarray[0];
    $w2 = gw $wordarray[1];
    $w3 = gw $wordarray[2];
    $symbol = $symbols[(get-random 6)]
    $symbol2 = $symbols2[(get-random 4)]
    $symbol3 = $symbols2[(get-random 4)]
    $num = $numbers[(get-random 9)];
    

Return ‘1337’ style if needed.

Change, add or remove these to suit your needs. Some systems complain if they recognize a word in your password and won’t let you proceed. This makes things a little harder to remember but definitely more secure. I use the second symbol list here to ensure I don’t overlap my random symbol with my replaced symbol.

    
        
    if ($leet) {
        # .replace() is case sensitive so it will not change the letter we capitilized.
        # anoother common switch would be to replace 's' with '$'
        $w1 = $w1.replace('o','0').replace('e','3').replace('i','1').replace('a','@')
        $w2 = $w2.replace('o','0').replace('e','3').replace('i','1').replace('a','@')
        $w3 = $w3.replace('o','0').replace('e','3').replace('i','1').replace('a','@')
        Write-host "$w1" -NoNewline 
        Write-Host "$symbol2" -NoNewline
        Write-Host "$w2" -NoNewline 
        Write-Host "$symbol3" -NoNewLine
        Write-Host "$w3" 
    }
    
Regular output without the leet switch.
    
        
    else {
        Write-host "$w1" -NoNewline
        Write-Host "$symbol" -NoNewline
        Write-Host "$w2" -NoNewline 
        Write-Host "$num" -NoNewLine
        Write-Host "$w3" 
    }
}
    

The Result

The default

Here is a sample output of 10 passwords. We use the default length and default capitalization.

 
     
        
Gen-Pass -PasswordCount 10 
Indicate$Pressure3
Accident%Downtown0
Detailed%Overlook3
Division@Teaching4
Painting#Portrait1
Previous@Schedule8
Strongly%Detailed0
Analysis#Detailed2
Colonial#Educator1
Football#Reporter7
 
    
After I’ve generated a list of passwords I pick and choose based on what goes together in my head as an easily remembered pair. For example I could take ‘Football’ from the last line and pair it with ‘Schedule’ from further up to get Football#Schedule7. Some of the pairs already seem to go together. ‘Painting’ and ‘Portrait’. ‘Accident’ and ‘Downtown’. If the symbol in between doesn’t fit in your mind, change it. That is what I like about the list. I can look over the choices and figure out what is a good password and also something my brain will let stick. The older I get the less it lets stick 😃. For our examples, I chose word files that were fairly small. The whole list before splitting was only 3000 words. I recommend a bigger list.

Move the Caps

Here’s a sample where we move the capital letter to the third place. If you choose to compose your passwords with shorter words, make sure you don’t try to capitalize the fifth letter of a four letter word.

 
     
        
Gen-Pass -PasswordCount 5 -Cap 3
ciVilian#ovErlook1
spEnding%teRrible3
maTerial$poLitics1
inCident#inTerest6
enTrance$ovErcome7
 
    
Here is output with one of our combinations that uses three words.
 
     
        
Gen-Pass -PasswordLength 21
Christmas@Adopt2Peace
Technical$Swing3Sleep
Guarantee@First8Issue
Colleague@Awful4Focus
Authority+Steal8Story
 
    
Finally here’s a sample where we’ve substituted some of the vowels and created some crazy looking passwords.
 
     
        
Gen-Pass -PasswordCount 5 -Cap 3 -leet
c0Nsum3r%d1Al0gu3+
@pPr0v@l%c0Nv1nc3+
stRuggl3+spEnd1ng!
1nD1c@t3+d3M0cr@t#
3mPl0y33%clIn1c@l%
 
    
Thanks for reading. I hope this is of some use to you. Let me know what you think in the comments down below.