Switching it up: Improving our Random Password Generator

Switching it up: Improving our Random Password Generator

In my last article, Scrambling an Egg: A How-To Guide on Random Password Generation in PowerShell, I covered an algorithm I devised for generating a random password in PowerShell. If you haven't read that one, you should before you start this one. In short, the function I wrote allows the user to pick from any and all ASCII character sets. It will spit out a randomized string containing at least one, if not more of each character set chosen. It will then scramble the order of the string it came up with, so that nothing is known about a password generated by this function if a would-be attacker looked at the code.

A handful of people commented on that last article offering recommendations to improve my code. So of course, I dug in and started working. I am absolutely a fan of making ever-more-elegant solutions. After all, this is a labor of love, and I spare no effort when it comes to love! So today, I write about how I switched up my code and made it even better. Let's get into it!

function New-RandomizedPassword
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$false)]
        [ValidateRange(6, 10000)]
        [Int]$PasswordLength = 8,

        [Parameter(Mandatory = $false)]
        [bool]$RequiresUppercase = $false,

        [Parameter(Mandatory = $false)]
        [bool]$RequiresNumerical = $false,

        [Parameter(Mandatory = $false)]
        [bool]$RequiresSpecial = $false
    )

    $PasswordCharacterArray = New-Object -TypeName System.Collections.ArrayList
    $CharacterSpaceArray = New-Object -TypeName System.Collections.ArrayList

    switch ( $true )
    {
        $RequiresUppercase
        {
            $null = $PasswordCharacterArray.Add(((65..90) | Get-Random | ForEach-Object {[char]$_}))
            $PasswordLength = $PasswordLength - 1
            $null = $CharacterSpaceArray.Add((65..90))
        }
        $RequiresNumerical
        {
            $null = $PasswordCharacterArray.Add(((48..57) | Get-Random | ForEach-Object {[char]$_}))
            $PasswordLength = $PasswordLength - 1
            $null = $CharacterSpaceArray.Add((48..57))
        }
        $RequiresSpecial
        {
            $null = $PasswordCharacterArray.Add(((33..38) + (40..47) + (58..64) | Get-Random | ForEach-Object {[char]$_}))
            $PasswordLength = $PasswordLength - 1
            $null = $CharacterSpaceArray.Add((33..38) + (40..47) + (58..64))
        }
    }
    $null = $PasswordCharacterArray.Add(((97..122) | Get-Random | ForEach-Object {[char]$_}))
    $PasswordLength = $PasswordLength - 1
    $null = $CharacterSpaceArray.Add((97..122))

    for($i = 1; $i -le $PasswordLength; $i++)
    {
        $null = $PasswordCharacterArray.Add(($CharacterSpaceArray | Get-Random | ForEach-Object {[char]$_}))
    }

    return -join ($PasswordCharacterArray | Get-Random -Count $PasswordCharacterArray.Count)
}        

Gone are the permutations of if statements and repeating for loops. Instead, they have been replaced with a switch statement with 3 cases, and a single for loop at the end. You might also notice how much shorter this function is. We were originally at 110 lines of code, and now we're down to 58.

In essence, this function will always generate at least one lowercase character, and at least one character from each selected character set. As it checks for each character set, it will generate a random character from that set, and will take note of which character sets were enabled (by adding them to $CharacterSpaceArray). It will also decrement by 1 the $PasswordLength variable for each character set selected. Once it has completed checking all of the character sets, the remainder of $PasswordLength will be used to generate the corresponding number of new random characters from our $CharacterSpaceArray.

First, let's deal with the new data format. Instead of beginning with a blank string, we're going to start right out of the gate with an array. These 2 lines create new objects of the ArrayList type.

$PasswordCharacterArray = New-Object -TypeName System.Collections.ArrayList
$CharacterSpaceArray = New-Object -TypeName System.Collections.ArrayList        

The first object will contain our randomly generated characters, while the second one will contain our dynamically generated character sets.

Now let's cover the switch statement. Effectively, when this function is run, the switch statement will check for whether or not each character set parameter was set to $true. If any given character set parameter was set to $true, the block of code underneath will run. Let's look at the first switch case.

$RequiresUppercase
{
	$null = $PasswordCharacterArray.Add(((65..90) | Get-Random | ForEach-Object {[char]$_}))
	$PasswordLength = $PasswordLength - 1
	$null = $CharacterSpaceArray.Add((65..90))
}        

So, if $RequiresUppercase was set to $true, then this block of code will run. That first line under the opening brackets will generate a random number between 65 and 90, convert it to a character, and then add that character to the $PasswordCharacterArray.

As a side note here, the ".Add()" method of the ArrayList type returns the index of where we started adding to the ArrayList. So if you are adding, for example, your second character to the ArrayList, you'll get a 1 back. That is why I am putting "$null =" in front of these lines. That way, my function doesn't also spit out numbers in the console.

The second line of code in that block decrements the $PasswordLength value by 1. This is so that we can keep track of how many characters we have left to generate. In this switch statement, only cases which are set to $true will be run, so we only ever decrement the password length when we already know we're adding a corresponding character.

Finally, the third line of code adds this character space to the ArrayList object which contains all of the character space we're going to use to generate the remainder of the password. Remember, only character sets where the parameter was set to $true are ever added.

The next piece of this is the for loop that will generate the remaining required characters. Since we've been keeping track of how many characters we've already generated, we know exactly how many times we need to run it. Here's the code.

for($i = 1; $i -le $PasswordLength; $i++)
{
  $null = $PasswordCharacterArray.Add(($CharacterSpaceArray | Get-Random | ForEach-Object {[char]$_}))
}        

Let me explain this in plan English. For each remaining character required to meet the password length specified, we're going to generate a random number somewhere within any of the character sets which were enabled. We're then going to take that randomly generated number, convert it to a character, and add it to our $PasswordCharacterArray.

Now we have an array ($PasswordCharacterArray). We just need to scramble the order of its contents, and return it to the function caller.

return -join ($PasswordCharacterArray | Get-Random -Count $PasswordCharacterArray.Count)        

The keen-eyed among you may have noticed that this logic used to consume 2 lines. Since we're working natively with arrays anyway now, we don't need to convert anything to an array. This line of code will take our $PasswordCharacterArray, randomize the order of its indexes, join each character from each index into a single string, and return that to the function caller.

That's it! I must say, this is a huge improvement over the previous code in quite a few ways. I hope this has been interesting to you all. So now I challenge you, dear audience... Keep your ideas coming, and keep that creativity flowing!

To view or add a comment, sign in

More articles by William Ogle

Insights from the community

Others also viewed

Explore topics