Generate a password in powershell

Filed under: PowerShell

Security is hard. One of the common mistakes I’ve seen people make in creating a password generation function is using Get-Random or Math.Random to generate the password instead of using System.Security.Cryptography.RandomNumberGenerator.

Get-Random uses the Math.Random under the covers which is not cryptographically secure.

I’ve put together a function for generating passwords in Powershell, New-Password, using RandomNumberGenerator. It allows you to specify:

  • characters sets that you want to use to seed the password
  • specify adhoc characters in case the default sets are too broad.
  • the exact length of the password
  • verify the password, which allows you to check to ensure that the password has certain characters.
  • return the password as a secure string.

You can copy the code below and save it to a file e.g. New-Password.ps1 and then you can dot source the file. e.g. . "C:\User\${Env:UserName}\OneDrive\New-Password.ps1"

#TODO: Add Entropy 
$passwordCharSets = @{
    LatinAlphaUpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    LatinAlphaLowerCase = "abcdefghijklmnopqrstuvwxyz";
    Digits = "0123456789";
    Hyphen = "-";
    Underscore = "_";
    Brackets = "[]{}()<>";
    Special = "~`&%$#@*+=|\/,:;^";
    Space = " ";
}

 function Merge-PasswordCharSets() {
    [cmdletbinding()]
    Param(
        [ValidateSet('LatinAlphaUpperCase', 'LatinAlphaLowerCase', 'Digits', 'Hyphen', 'Underscore', 'Brackets', 'Special', 'Space')]
        [Parameter()]
        [string[]] $CharSets
    )

    if($CharSets -eq $null -or $CharSets.Length -eq 0) { return $null }

    $result = $null;

    if($CharSets -ne $null -and $CharSets -gt 0) {
        $sb1 = New-Object System.Text.StringBuilder

        foreach($setName in $CharSets) {
            if($passwordCharSets.ContainsKey($setName)) {

                $characters = $passwordCharSets[$setName];

                $sb1.Append($characters) | Out-Null
            }
        }

        $result = $sb1.ToString();
    }
    return $result;
}

function Test-Password() {
    Param(
        [Char[]] $Characters,
        [ScriptBlock] $Validate
    )

    if($characters -eq $null -or $characters.Length -eq 0) {
        return $false;
    }

    if($Validate -ne $null) {
        & $Validate -Characters $Characters;
    }

    $lower = $false;
    $upper = $false;
    $digit = $false;

    for($i = 0; $i -lt $characters.Length; $i++) {
        if($lower -and $upper -and $digit) {
            return $true;
        }

        $char = [char]$characters[$i];


        if([Char]::IsDigit($char)) {
            $digit = $true;
            continue;
        }

        if([Char]::IsLetter($char)) {
            if([Char]::IsUpper($char)) {
                $upper = $true;
                continue;
            }

            if([Char]::IsLower($char)) {
                $lower = $true;
            }
        }
    }

    return $false;
}

function New-Password() {
    Param(
        [int] $Length,
        [ValidateSet('LatinAlphaUpperCase', 'LatinAlphaLowerCase', 'Digits', 'Hyphen', 'Underscore', 'Brackets', 'Special', 'Space')]
        [string[]] $CharSets = $null,
        [string] $Chars = $null,
        [ScriptBlock] $Validate = $null,
        [switch] $AsSecureString
    )

    if($Length -eq 0) {
        $Length = 16;
    }

    $sb = New-Object System.Text.StringBuilder
    if($CharSets -ne $Null -and $CharSets.Length -gt 0) {
        $set = (Merge-PasswordCharSets $CharSets)
        if($set -and $set.Length -gt 0) {
            $sb.Append($set) | Out-Null  
        }
    }

    if(![string]::IsNullOrWhiteSpace($Chars)) {
        $sb.Append($Chars)  | Out-Null  
    } 

    if($sb.Length -eq 0) {
        $sets = Merge-PasswordCharSets (@('LatinAlphaUpperCase', 'LatinAlphaLowerCase', 'Digits', 'Hyphen', 'Underscore'))
        $sb.Append($sets)  | Out-Null  
    }

    $permittedChars = $sb.ToString();
    $password = [char[]]@(0) * $Length;
    $bytes = [byte[]]@(0) * $Length;


    while( (Test-Password $password -Validate $Validate) -eq $false) {
        $rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
        $rng.GetBytes($bytes);
        $rng.Dispose();

        for($i = 0; $i -lt $Length; $i++) {
            $index = [int] ($bytes[$i] % $permittedChars.Length);
            $password[$i] = [char] $permittedChars[$index];
        }
    }

    $result = -join $password;
    if($AsSecureString.ToBool()) {
        return $result | ConvertTo-SecureString -AsPlainText
    }



    return $result;
}

Once you save the script to OneDrive or another location of your choosing, you can execute it like so.

. "C:\User\${Env:UserName}\OneDrive\New-Password.ps1"
New-Password
Nerdy Mishka