There is an ocean of password generators out there, and even quite a few written in PowerShell, but there were some features I wanted in a password generator that made me create my own.
UPDATE 23.09.2015: Refactored the code. Better pronounceable passwords generated.
UPDATE 21.09.2015: Function will now also output SecureString by using the AsSecureString parameter.
This function uses the System.Security.Cryptography.RNGCryptoServiceProvider class in .NET to create cryptographically secure random numbers. These numbers are used to generate a strong and complex random password.
You can control the length of the generated password, as well as specify the complexity rules by including or excluding characters from the four main character groups; symbols, numbers, uppercase letters and lowercase letters. You can also use it to generate several passwords in one go by using the Count parameter.
I also decided to support the creation of what is called pronounceable passwords. This mode uses only uppercase and lowercase letters, but tries to mix vowels and consonants in a “smart” manner so that (parts of) the password is somewhat pronounceable. The mix of letters are still randomized so they will still be secure-ish. I wouldn’t rely on using these passwords for something critical, but if you want these kinds of passwords, this function can generate them. NOTE! After the last update, the pronounceable passwords also supports numbers, so they will be somewhat more secure!
I hope you will find this useful, and if you have ideas for further improvements, or find any bugs, don’t hesitate to contact me!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Invoke-VerifyAndUpdatePasswordComplexity { | |
param ( | |
[System.Text.StringBuilder] $Password, | |
[switch] $IncludeSymbols = $true, | |
[switch] $IncludeNumbers = $true, | |
[switch] $IncludeUppercaseCharacters = $true, | |
[switch] $IncludeLowercaseCharacters = $true, | |
[string] $Symbols, | |
[string] $Numbers, | |
[string] $UpperCaseCharacters, | |
[string] $LowerCaseCharacters, | |
[switch] $UpdatePassword | |
) | |
$complexityOk = $true | |
if ($IncludeSymbols) { | |
if (-not(Compare-Object –ReferenceObject $symbols.ToCharArray() –DifferenceObject $password.ToString().ToCharArray() –IncludeEqual –ExcludeDifferent)) { | |
if ($UpdatePassword) { | |
$password[(Get-Random –Minimum 1 –Maximum ($password.Length))] = $symbols[(Get-Random –Minimum 0 –Maximum ($symbols.Length))] | |
} | |
else { | |
$complexityOk = $false | |
} | |
} | |
} | |
if ($IncludeNumbers) { | |
if (-not(Compare-Object –ReferenceObject $numbers.ToCharArray() –DifferenceObject $password.ToString().ToCharArray() –IncludeEqual –ExcludeDifferent)) { | |
if ($UpdatePassword) { | |
$password[(Get-Random –Minimum 1 –Maximum ($password.Length))] = $numbers[(Get-Random –Minimum 0 –Maximum ($numbers.Length))] | |
} | |
else { | |
$complexityOk = $false | |
} | |
} | |
} | |
if ($IncludeUppercaseCharacters) { | |
if (-not(Compare-Object –ReferenceObject $UpperCaseCharacters.ToCharArray() –DifferenceObject $password.ToString().ToCharArray() –IncludeEqual –ExcludeDifferent)) { | |
if ($UpdatePassword) { | |
$password[(Get-Random –Minimum 1 –Maximum ($password.Length))] = $UpperCaseCharacters[(Get-Random –Minimum 0 –Maximum ($UpperCaseCharacters.Length))] | |
} | |
else { | |
$complexityOk = $false | |
} | |
} | |
} | |
if ($IncludeLowercaseCharacters) { | |
if (-not(Compare-Object –ReferenceObject $LowerCaseCharacters.ToCharArray() –DifferenceObject $password.ToString().ToCharArray() –IncludeEqual –ExcludeDifferent)) { | |
if ($UpdatePassword) { | |
$password[(Get-Random –Minimum 1 –Maximum ($password.Length))] = $LowerCaseCharacters[(Get-Random –Minimum 0 –Maximum ($LowerCaseCharacters.Length))] | |
} | |
else { | |
$complexityOk = $false | |
} | |
} | |
} | |
if ($UpdatePassword) { | |
Write-Output $Password | |
} | |
else { | |
Write-Output $complexityOk | |
} | |
} | |
function IsConsonant { | |
param([char]$Character) | |
if ('bcdfghjklmnpqrstvwxyz'.ToCharArray() -contains $Character) {Write-Output $true} | |
else {Write-Output $false} | |
} | |
function IsVowel { | |
param([char]$Character) | |
if ('aeiou'.ToCharArray() -contains $Character) {Write-Output $true} | |
else {Write-Output $false} | |
} | |
function New-PronounceablePassword { | |
[CmdletBinding()] | |
param ( | |
[Parameter()] | |
[ValidateRange(4,[int]::MaxValue)] | |
[int] $Length = 10, | |
[Parameter()] | |
[Alias('Numbers')] | |
[switch] $IncludeNumbers = $true, | |
[Parameter()] | |
[Alias('Uppercase')] | |
[switch] $IncludeUppercaseCharacters = $true | |
) | |
$consonantDigraphs = 'bl', 'br', 'ch', 'cl', 'cr', 'dr', 'fl', 'fr', 'gl', 'gr', 'pl', 'pr', 'sc', 'sh', 'sk', 'sl', 'sm', 'sn', 'sp', 'st', 'sw', 'th', 'tr', 'tw', 'wh', 'wr' | |
$consonantTrigraphs = 'sch', 'scr', 'shr', 'sph', 'spl', 'spr', 'squ', 'str', 'thr' | |
$consonants = 'bcdfghjklmnpqrstvwxyz' | |
$vowels = 'aeiou' | |
$startSet = $consonantDigraphs + $consonantTrigraphs + $consonants.ToCharArray() + $vowels.ToCharArray() | |
$allSet = $consonants.ToCharArray() + $vowels.ToCharArray() | |
$currentConsecutiveConsonants = 0 | |
$currentConsecutiveVowels = 0 | |
$password = New-Object –TypeName System.Text.StringBuilder –ArgumentList $Length | |
$byteArray = New-Object –TypeName System.Byte[] –ArgumentList $Length | |
$rng = New-Object –TypeName System.Security.Cryptography.RNGCryptoServiceProvider | |
$rng.GetBytes($byteArray) | |
for ($i = 0; $i -lt $length; $i++) { | |
# as the start of the password, we choose from our special start set | |
if ($i -eq 0) { | |
[void]$password.Append(($startSet[$byteArray[$i] % $startSet.Length])) | |
if ($password.Length -eq 2) { $i++ } | |
elseif ($password.Length -eq 3) { $i = $i + 2 } | |
$afterStart = $true | |
continue | |
} | |
# handle the character after the start | |
if ($afterStart) { | |
# if a consonant, next must be a vowel | |
if (IsConsonant –Character $password[-1]) { | |
[void]$password.Append(($vowels[$byteArray[$i] % $vowels.Length])) | |
$currentConsecutiveVowels++ | |
$currentConsecutiveConsonants = 0 | |
} | |
# if a vowel, next must be a consonant | |
else { | |
[void]$password.Append(($consonants[$byteArray[$i] % $consonants.Length])) | |
$currentConsecutiveConsonants++ | |
$currentConsecutiveVowels = 0 | |
} | |
$afterStart = $false | |
} | |
else { | |
# if 3 consecutive vowels, next must be a consonant | |
if ($currentConsecutiveVowels -eq 3) { | |
[void]$password.Append(($consonants[$byteArray[$i] % $consonants.Length])) | |
$currentConsecutiveConsonants++ | |
$currentConsecutiveVowels = 0 | |
continue | |
} | |
# if 2 consecutive consonants, next must be a vowel | |
if ($currentConsecutiveConsonants -eq 2) { | |
[void]$password.Append(($vowels[$byteArray[$i] % $vowels.Length])) | |
$currentConsecutiveVowels++ | |
$currentConsecutiveConsonants = 0 | |
continue | |
} | |
# randomly pick next character | |
[void]$password.Append(($allSet[$byteArray[$i] % $allSet.Length])) | |
# if last character added was a consonant | |
if (IsConsonant –Character $password[-1]) { | |
$currentConsecutiveConsonants++ | |
$currentConsecutiveVowels = 0 | |
} | |
# else last character added was a vowel | |
else { | |
$currentConsecutiveVowels++ | |
$currentConsecutiveConsonants = 0 | |
} | |
} | |
} | |
$currentConsecutiveConsonants = 0 | |
$currentConsecutiveVowels = 0 | |
$rng.GetBytes($byteArray) | |
# perform a second pass of the generated password to try | |
# to make it more pronounceable and secure | |
for ($i = 0; $i -lt $length; $i++) { | |
if (IsConsonant –Character $password[$i]) { | |
$currentConsecutiveConsonants++ | |
$currentConsecutiveVowels = 0 | |
} | |
else { | |
$currentConsecutiveVowels++ | |
$currentConsecutiveConsonants = 0 | |
} | |
# handle difficult to pronounce consonant pairs | |
$difficultConsonantPairs = 'gb', 'jb', 'tp', 'tf', 'qx', 'bt', 'js', 'wv', 'jp', 'mz', 'qj', 'fc', 'jq', 'mv', 'kc', 'jr', 'mk', 'bw', 'cg', 'fj', 'kd', 'hc', 'hg', 'cj', 'wc', 'bp', 'bc', 'bv', 'vq', 'mw', 'pk', 'bx', 'fr', 'hv', 'cz', 'vg', 'bd', 'wj', 'dn', 'gj', 'jf', 'qb', 'tg', 'pv', 'vl', 'jy', 'wn', 'pw', 'dw', 'kq', 'vd', 'vw', 'tj', 'qw', 'vm', 'zs', 'sz', 'dx', 'vf', 'yd', 'tx' | |
if ($currentConsecutiveConsonants -eq 2) { | |
$consonantPair = $password[$i – 1] + $password[$i] | |
if ($difficultConsonantPairs -contains $consonantPair) { | |
Write-Verbose 'Replacing difficult consonant pairs' | |
$password[$i – 1] = (($consonants[$byteArray[$i] % $consonants.Length])) | |
$password[$i] = (($consonants[$byteArray[$i] % $consonants.Length])) | |
} | |
} | |
if ($IncludeUppercaseCharacters) { | |
# randomly change characters to upper case | |
if ($true, $false | Get-Random) { | |
$password[$i] = $password[$i].ToString().ToUpper() | |
} | |
if ($password[$i] -eq 'L') {$password[$i] = 'l'; Write-Verbose 'Changed "l" to "L"'} | |
} | |
switch ($password[$i]) { | |
'O' {$password[$i] = 'o'; Write-Verbose 'Changed "O" to "o"'} | |
'I' {$password[$i] = 'i'; Write-Verbose 'Changed "I" to "i"'} | |
} | |
} | |
# replace last character(s) with random numbers | |
if ($IncludeNumbers) { | |
if ($Length -lt 8) { | |
Write-Verbose 'Replacing the last character with a random number' | |
$password[-1] = (Get-Random –Minimum 0 –Maximum 9).ToString() | |
} | |
elseif ($Length -lt 20) { | |
Write-Verbose 'Replacing the last two characters with random numbers' | |
$password[-2] = (Get-Random –Minimum 0 –Maximum 9).ToString() | |
$password[-1] = (Get-Random –Minimum 0 –Maximum 9).ToString() | |
} | |
else { | |
Write-Verbose 'Replacing the last three characters with random numbers' | |
$password[-3] = (Get-Random –Minimum 0 –Maximum 9).ToString() | |
$password[-2] = (Get-Random –Minimum 0 –Maximum 9).ToString() | |
$password[-1] = (Get-Random –Minimum 0 –Maximum 9).ToString() | |
} | |
} | |
Write-Output $password.ToString() | |
} | |
function New-ComplexPassword { | |
[CmdletBinding()] | |
param ( | |
[Parameter()] | |
[ValidateRange(4,[int]::MaxValue)] | |
[int] $Length = 12, | |
[Parameter()] | |
[Alias('Symbols')] | |
[switch] $IncludeSymbols = $true, | |
[Parameter()] | |
[Alias('Numbers')] | |
[switch] $IncludeNumbers = $true, | |
[Parameter()] | |
[Alias('Uppercase')] | |
[switch] $IncludeUppercaseCharacters = $true, | |
[Parameter()] | |
[Alias('Lowercase')] | |
[switch] $IncludeLowercaseCharacters = $true, | |
[Parameter()] | |
[Alias('StartWith')] | |
[ValidateSet('Any','Letter','Number')] | |
[string] $AlwaysStartWith = 'Any' | |
) | |
# if for some strange reason the user have disabled using any character groups, give them a funny warning and break | |
if (!$IncludeSymbols -and !$IncludeNumbers -and !$IncludeUppercaseCharacters -and !$IncludeLowercaseCharacters) { | |
Write-Warning 'Since you have indicated that you want to generate a password consisting of just thin air, here you go:' | |
break | |
} | |
# the different character sets used in generating passwords | |
$symbols = '!#%+:=?@' | |
$numbers = '23456789' | |
$uppercase = 'ABCDEFGHJKLMNPRSTUVWXYZ' | |
$lowercase = 'abcdefghijkmnopqrstuvwxyz' | |
# based on the parameters used create the character arrays we need | |
if ($IncludeSymbols) { $charSetAll += $symbols } | |
if ($IncludeNumbers) { $charSetAll += $numbers } | |
if ($IncludeUppercaseCharacters) { $charSetAll += $uppercase; $charSetLetters += $uppercase } | |
if ($IncludeLowercaseCharacters) { $charSetAll += $lowercase; $charSetLetters += $lowercase } | |
# generate a byte array to hold our random numbers | |
$byteArray = New-Object –TypeName System.Byte[] –ArgumentList $Length | |
# create the RNG and fill the byte array with random numbers | |
$rng = New-Object –TypeName System.Security.Cryptography.RNGCryptoServiceProvider | |
$rng.GetBytes($byteArray) | |
# create our password string object | |
$password = New-Object System.Text.StringBuilder –ArgumentList $Length | |
for ($i = 0; $i -lt $Length; $i++) { | |
# handle first character | |
if ($i -eq 0) { | |
if ($AlwaysStartWith -eq 'Number') { | |
Write-Verbose 'Making sure that the first character in the password is a number' | |
if ($IncludeNumbers) { [void]$password.Append(($numbers[$byteArray[$i] % $numbers.Length])) } | |
else { Write-Warning 'You can''t have a password that starts with a number without including numbers in the generated password!' ;break } | |
} | |
elseif ($AlwaysStartWith -eq 'Letter') { | |
Write-Verbose 'Making sure that the first character in the password is a letter' | |
if ($IncludeUppercaseCharacters -or $IncludeLowercaseCharacters) { [void]$password.Append(($charSetLetters[$byteArray[$i] % $charSetLetters.Length])) } | |
else { Write-Warning 'You can''t have a password that starts with a characters without including any characters in the generated password!' ;break } | |
} | |
else { [void]$password.Append(($charSetAll[$byteArray[$i] % $charSetAll.Length])) } | |
} | |
# the rest of the characters | |
else { [void]$password.Append(($charSetAll[$byteArray[$i] % $charSetAll.Length])) } | |
} | |
if ($password.Length -eq $Length) { | |
$verifyUpdateParameters = @{ | |
Password = $password | |
IncludeSymbols = $IncludeSymbols | |
IncludeNumbers = $IncludeNumbers | |
IncludeUppercaseCharacters = $IncludeUppercaseCharacters | |
IncludeLowercaseCharacters = $IncludeLowercaseCharacters | |
Symbols = $symbols | |
Numbers = $numbers | |
UppercaseCharacters = $uppercase | |
LowercaseCharacters = $lowercase | |
} | |
# before we return the generated password we need to make sure that it includes | |
# at least one character from each choosen character group | |
# this loop and helper function will randomly replace characters until they | |
# meet the complexity rules specified by the user | |
if (-not (Invoke-VerifyAndUpdatePasswordComplexity @verifyUpdateParameters)){ | |
do { | |
Write-Verbose 'Password don''t meet complexity rules – updating password' | |
$password = Invoke-VerifyAndUpdatePasswordComplexity @verifyUpdateParameters –UpdatePassword | |
} until (Invoke-VerifyAndUpdatePasswordComplexity @verifyUpdateParameters) | |
} | |
} | |
Write-Output $password.ToString() | |
} | |
function New-Password { | |
<# | |
.SYNOPSIS | |
Generate complex password(s). | |
.DESCRIPTION | |
This functions lets you generate either random passwords of the | |
choosen complexity and length, or pronounceable passwords. | |
.EXAMPLE | |
New-Password | |
Will generate a random password. | |
.EXAMPLE | |
New-Password -Length 20 | |
Will generate a random password 20 characters in length. | |
.EXAMPLE | |
New-Password -Min 12 -Max 20 -IncludeSymbols:$false -AlwaysStartWith Letter | |
Will generate a password between 12 and 20 characters in length, that don't include symbols | |
but that always starts with a letter. | |
.EXAMPLE | |
New-Password -Type Pronounceable -Amount 10 | |
Will generate 10 pronounceable passwords. | |
.NOTES | |
Author: Øyvind Kallstad | |
Date: 19.09.2015 | |
Version: 1.2 | |
.OUTPUTS | |
System.String | |
System.Security.SecureString | |
.LINK | |
https://communary.wordpress.com/ | |
#> | |
[CmdletBinding(DefaultParameterSetName = 'FixedLength')] | |
param ( | |
# Specifies the length of the generated password. | |
[Parameter(ParameterSetName = 'FixedLength')] | |
[ValidateRange(4,[int]::MaxValue)] | |
[int] $Length = 12, | |
# Specifies a minimum length of the generated password. | |
[Parameter(ParameterSetName = 'VariableLength')] | |
[ValidateRange(4,[int]::MaxValue)] | |
[int] $MinimumPasswordLength = 12, | |
# Specifies a maximum length of the generated password. | |
[Parameter(ParameterSetName = 'VariableLength')] | |
[ValidateScript({if ($_ -lt $MinimumPasswordLength) {Throw "Maximum password length must larger than or equal to $MinimumPasswordLength"} else {$true}})] | |
[int] $MaximumPasswordLength = 14, | |
# The type of passwords being generated. Valid values are 'Complex' and 'Pronounceable'. | |
[Parameter()] | |
[ValidateSet('Complex','Pronounceable')] | |
[string] $Type = 'Complex', | |
# Specifies how many generated passwords are returned. | |
[Parameter()] | |
[Alias('Count')] | |
[ValidateRange(1,[int]::MaxValue)] | |
[int] $Amount = 1, | |
# Include symbols in the generated password. Note that symbols are not | |
# implemented for pronounceable passwords yet. | |
[Parameter()] | |
[Alias('Symbols')] | |
[switch] $IncludeSymbols = $true, | |
# Include numbers in the generated password. | |
[Parameter()] | |
[Alias('Numbers')] | |
[switch] $IncludeNumbers = $true, | |
# Include uppercase characters in the generated password. | |
[Parameter()] | |
[Alias('Uppercase')] | |
[switch] $IncludeUppercaseCharacters = $true, | |
# Include lowercase characters in the generated password. | |
[Parameter()] | |
[Alias('Lowercase')] | |
[switch] $IncludeLowercaseCharacters = $true, | |
# Force the generated password to start with a letter or a number. | |
[Parameter()] | |
[Alias('StartWith')] | |
[ValidateSet('Any','Letter','Number')] | |
[string] $AlwaysStartWith = 'Any', | |
# Output will be a SecureString object instead of a string. | |
[Parameter()] | |
[switch] $AsSecureString | |
) | |
# display a warning if trying to generate weak passwords | |
$warningThreshold = 12 | |
if (($Length -lt $warningThreshold) -or ($MinimumPasswordLength -lt $warningThreshold)) { | |
Write-Warning "Passwords with a length of less than $warningThreshold characters are not very secure! Please consider generating longer passwords." | |
} | |
# display warning regarding pronounceable passwords and symbols | |
if (($Type -eq 'Pronounceable') -and ($IncludeSymbols)) { | |
Write-Warning 'Pronounceable passwords will be generated without symbols.' | |
} | |
for ($i = 1; $i -le $Amount; $i++) { | |
if ($PSCmdlet.ParameterSetName -eq 'VariableLength') { | |
# if min and max password length are equal just use one of them | |
if ($MinimumPasswordLength -eq $MaximumPasswordLength) { | |
$Length = $MinimumPasswordLength | |
} | |
# else randomize the password length based on the min and max values | |
# we use Get-Random here for simplicity, and the length of the password hardly needs | |
# to be calculated using a cryptographically secure method 🙂 | |
else { | |
$Length = Get-Random –Minimum $MinimumPasswordLength –Maximum $MaximumPasswordLength | |
} | |
} | |
switch ($Type) { | |
'Complex' {$passwordString = New-ComplexPassword –Length $Length –IncludeSymbols:$IncludeSymbols –IncludeNumbers:$IncludeNumbers –IncludeUppercaseCharacters:$IncludeUppercaseCharacters –IncludeLowercaseCharacters:$IncludeLowercaseCharacters –AlwaysStartWith $AlwaysStartWith} | |
'Pronounceable' {$passwordString = New-PronounceablePassword –Length $Length –IncludeNumbers:$IncludeNumbers –IncludeUppercaseCharacters:$IncludeUppercaseCharacters} | |
} | |
if ($AsSecureString) { | |
$secureString = ConvertTo-SecureString –String $passwordString –AsPlainText –Force | |
Write-Output $secureString | |
} | |
else { | |
Write-Output $passwordString | |
} | |
} | |
} |