PowerShell for Security: PassWord Gen Part 1

My history with Password Generators

Password generators can be very simple and fun to build, and I thought that publishing my own history with creating them can be a good source of knowledge for other people, hence this post :)

My first version of Get-GeneratedPassword was created in Powershell 3.0, and at that point I didn’t have that many requirements, the usecase for the function was basically to stash it in my $profile to quickly set new passwords for various AD accounts.

However the first version was based on a dotnet class method called: [System.Web.Security.Membership]::GeneratePassword

Adam Bertram does a great job covering how to wrap this in a module, click the class name to read his post about it.

The class does bring a dependency on the the specific dotnet class, and for me, this approach started to bring errors in Powershell cores early versions.

New attempt without dependencies

This is my attempt at creating my own password generator

function Get-GeneratedPassword {
<#
.SYNOPSIS
    Cross-platform password generator
.DESCRIPTION
    Get-GeneratedPassword is using a Get-Random, a string and regex 
    validation to ensure that the password meets the complexity level 
    enforced by default in ActiveDirectory
.EXAMPLE
    PS C:\> Get-GeneratedPassword -PwLength 10 -Amount 10
    Generates 10 passwords with the length set to 10
.EXAMPLE
    PS C:\> Get-GeneratedPassword -PwLength 12 | clip
    Only supported in Windows. Will generate a password with 12 as length 
    and clip the result to clipboard
.EXAMPLE
    PS C:\> $user = "emil"; $pw = ConvertTo-SecureString -String (Get-GeneratedPassword 12) -AsPlainText
    PS C:\> $creds = $user,$pw
    Creates a CredentialObject that can be passed in to user generating cmdlets
.EXAMPLE
    PS C:\> Get-GeneratedPassword -PwLength 8 -Amount 100 | Out-File C:\Temp\PW.txt
    Generates 100 passwords to a textfile stored in C:\Temp\PW.txt
.INPUTS
    PwLengt as int32
.OUTPUTS
    Outputs randomized password as string(s)
.NOTES
    Purpose :   Designed to meet AD Complexity rules & be crossplatform (Windows, Linux)
    Author  :   Emil.t.Larsson@gmail.com
    Date    :   2021-05-11
    OS      :   Win10, Ubuntu 20
    Version :   1.0.0
#>
    [CmdletBinding()]
    Param
    (
        
        [Parameter(Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            Position = 0)]
        [ValidateRange(6, 30)]
        [int32]$PwLength,
        [Parameter(Mandatory = $false)]
        [int32]$Amount = 1
    )

    Begin {
        $Password = @()
    }
    Process {

        $PwdValues = "-!@#$%^&*_{}()?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

        do {

            $PasswordGenerated = ($PwdValues.ToCharArray() | Sort-Object { Get-Random })[1..$PwLength] -join ''

            # Regex rules, contains any of the special AND 0-9 AND upper/lower
            if (
                $PasswordGenerated -match "[-!@#$%^&*_{}()?]" -and 
                $PasswordGenerated -match "(?-i)[A-Z]" -and 
                $PasswordGenerated -match "(?-i)[a-z]" -and 
                $PasswordGenerated -match "[0-9]"
            ) {
                # Add to pw array
                $Password += $PasswordGenerated
            }
            else {
                Continue
            }
        }
        until ($Password.count -eq $Amount)
    }
    End {
        $Password
    }
}

The script can be found on my GitHub PS repo

Displaying the cmdlet

Read the comment based help, or load the function and run:

Get-Help Get-GeneratedPassword

My Requirements was the following

  • Cover AD complexity rules (in 99,9%)
  • String output, for simplicity
  • X-platform
  • No dependencies outside of Powershell 7

Begin

The function starts of by enforcing some of the requirements using ValidateRange, and a default value for the -Amount parameter
[ValidateRange(6, 30)] [int32]$PwLength

Since AD’s complexity rule is enforcing at least 6 chars, this range checks that requirement box.

[int32]$Amount = 1

The default value solves the issue of just running the cmdlet without the -Amount parameter

Next up is the whole idea behind the script, instead of using a dotnet class, I’ll just generate my own string of chars to pick from:

$pwdvalues = "-!@#$%^&*_{}()?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

By using a do-until loop, I can simply abuse Get-Random:
$PasswordGenerated = ($pwdvalues.tochararray() | Sort-Object { Get-Random })[1..$PwLength] -join ''

until my desired count of complex passwords are achieved by validating them through some regex validations:

$PasswordGenerated -match "[-!@#$%^&*_{}()?]" -and

$PasswordGenerated -match "(?-i)[A-Z]" -and

$PasswordGenerated -match "(?-i)[a-z]" -and

$PasswordGenerated -match "[0-9]"

This validation is critical for only getting the complex passwords for output

The “(?-i)” part is needed since PowerShell by default is case-insensitive, this definition solves that part, and we need this since we really do care about the match being case-sensitive. This blog post by Jake Bolton covers the problem in detail.

Since all we do here is randomly grabbing strings and joining them, we’re only working with a string object. Making the script fast and the output very simple, and since the output is just a simple string, it can be easily turned into a .txt file or used within ConvertTo-SecureString

Lastly

This is a quite simple and short function, and I’m sure it wont cover all my password generating needs for the future, but hopefully for some time at least.

I hope this post got you thinking & curious about:

  • regex validation
  • do-while loops
  • string manipulation
  • case sensitivity
  • self-made functions

in Powershell!

Happy coding

/Emil