Skip to content

Instantly share code, notes, and snippets.

@Jaykul
Created April 19, 2018 07:00
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Jaykul/5cb0410abd40672707faf67549404ea8 to your computer and use it in GitHub Desktop.
Save Jaykul/5cb0410abd40672707faf67549404ea8 to your computer and use it in GitHub Desktop.
Yet another password generator

Everyone seems to write their own password generators. In fact, this is my third. What sets it apart are couple of things:

First, it uses patterns which are basically the same as the ones in the pattern-based generator in KeePass. You can generate a 100 character alpha numeric password by saying: New-Password A100 and you can even specify your own custom character sets.

For comparison with the System.Web.Security.Membership.GeneratePassword that many people use, there are a few major points:

It works on PowerShell Core.

It only generates a single character at a time and adds them to a SecureString. Although the protection afforded by SecureString is lower on .NET Core than in full .NET Framework, we still avoid having the whole password in memory together in the simple string class.

Additionally, all the characters are generated using the cryptographically secure random number generator (the GeneratePassword method uses System.Random to add special characters to the string if they're missing).

Finally, this generator is still not as good as the one in KeePass. It's missing a few features, including the ability to randomiz the order of the characters in pattern generated passwords (and of course, other ways of generating passwords). I never use those features because I'd rather just define the valid characters and how many I want. Additionally, I'm using the old built-in RNGCryptoServiceProvider, whereas KeePass uses a fancy ChaCha20 crypto random stream. I'm not sure I could put a number on how much difference that makes in the real world, but there you go.

function New-Password {
<#
.SYNOPSIS
Generate pseudo-random passwords based on templates
.PARAMETER Template
The template for the password you want to generate. (Defaults to a totally random 16-20 character password)
This defines which types of characters are generated for each character in the password.
IMPORTANT: the US English alphabet is hardcoded ... (we make no apologies, but thought you should know that)
NOTE: The template has changed somewhat from v1 (to more closely resemble the pattern used by KeePass)
Char | Type | Actual character set
-----|-----------------------------|---------------------
a | Lower-Case Alphanumeric | abcdefghijklmnopqrstuvwxyz 0123456789
A | Mixed-Case Alphanumeric | ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789
U | Upper-Case Alphanumeric | ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789
d | Digit | 0123456789
h | Lower-Case Hex Character | 0123456789 abcdef
H | Upper-Case Hex Character | 0123456789 ABCDEF
l | Lower-Case Letter | abcdefghijklmnopqrstuvwxyz
L | Mixed-Case Letter | ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz
u | Upper-Case Letter | ABCDEFGHIJKLMNOPQRSTUVWXYZ
v | Lower-Case Vowel | aeiou
V | Mixed-Case Vowel | AEIOU aeiou
Z | Upper-Case Vowel | AEIOU
c | Lower-Case Consonant | bcdfghjklmnpqrstvwxyz
C | Mixed-Case Consonant | BCDFGHJKLMNPQRSTVWXYZ bcdfghjklmnpqrstvwxyz
z | Upper-Case Consonant | BCDFGHJKLMNPQRSTVWXYZ
p | Punctuation | ,.;:
b | Bracket | ()[]{}<>
s | Printable 7-Bit Punctuation | !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
S | Printable 7-Bit ASCII | A-Z, a-z, 0-9, !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
\ | Escape (Fixed Char) | Use following character as is.
0-9 | Repeat | Repeat the previous character n times.
Using a number, you can define how many times the previous placeholder should occur:
* d4 is equivalent to dddd
* dH4a is equivalent to dHHHHa
* Hda1dH is equivalent to HdadH
* S16 is equivalent to SSSSSSSSSSSSSSSS (this is the default password pattern)
To define custom character sets, you pass a hashtable to -CustomCharacterSets which maps one character to an array of characters, then you can use that character in your template.
Note you cannot overwrite characters that are already in the character map (as listed above).
.PARAMETER CustomCharacterSet
A hashtable mapping single characters to an array of characters for a custom character set.
For example, to use numbers without zero or 1 (avoiding confusion with the letters O and L), you can define:
-CustomCharacterSet @{ n = "23456789" }
.EXAMPLE
New-Password "zvcvcdd"
Description
-----------
Generates a "pronounceable" 7 character password consisting of alternating consonants and vowels followed by a 2-digit number
.EXAMPLE
New-Password A16
Description
-----------
Generates a 16 character alpha-numeric password
.EXAMPLE
-split "Cvcvcdd " * 8 | New-Password
Description
-----------
Demonstrates that the function can take pipeline input. Passing multiple templates via the pipeline will generate multiple passwords.
In this case, we generate EIGHT "pronounceable" 7 character password consisting of alternating consonants and vowels followed by a 2-digit number
.EXAMPLE
New-Password "zvvcpzvvcdd"
Description
-----------
Generates a password which starts with an upper-case consonant, followed by two lower-case vowels, followed by a punctuation mark, followed by an upper-case consonant, followed by two lower-case vowels, followed by two numbers.
.EXAMPLE
New-Password "Get-zvcvvc"
Description
-----------
Generates a password which looks like a strange PowerShell command, starting with "Get-" and ending with an uppercase consonant, a vowel, a consonant, two vowels, and a final consonant.
.INPUTS
[String]
A string template for a password
.OUTPUTS
[SecureString]
A password, as secure as we can make it
.NOTES
HISTORY
2.0 Change random number generator
Return a SecureString
1.1 Bugfix for the \ escape character
+ added a hex option (H for upper) and (h for lower)
+ changed the '#' to 'd' for digits so you can write the patterns without quotes.
1.0 First release
#>
[OutputType([SecureString])]
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline=$true, Position=0)]
[string]$Template = "S16",
[hashtable]$CustomCharacterSet = @{}
)
begin {
$CharacterSets = [System.Collections.Generic.Dictionary[char,char[]]]::new()
@{
[char]'a' = [char[]]"abcdefghijklmnopqrstuvwxyz0123456789"
[char]'A' = [char[]]"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
[char]'U' = [char[]]"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
[char]'d' = [char[]]"0123456789"
[char]'h' = [char[]]"0123456789abcdef"
[char]'H' = [char[]]"0123456789ABCDEF"
[char]'l' = [char[]]"abcdefghijklmnopqrstuvwxyz"
[char]'L' = [char[]]"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
[char]'u' = [char[]]"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
[char]'v' = [char[]]"aeiou"
[char]'V' = [char[]]"AEIOUaeiou"
[char]'Z' = [char[]]"AEIOU"
[char]'c' = [char[]]"bcdfghjklmnpqrstvwxyz"
[char]'C' = [char[]]"BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz"
[char]'z' = [char[]]"BCDFGHJKLMNPQRSTVWXYZ"
[char]'p' = [char[]]",.;:"
[char]'b' = [char[]]"()[]{}<>"
[char]'s' = [char[]]"!`"#$%&'()*+,-./:;<=>?@[\]^_``{|}~"
[char]'S' = [char[]]"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!`"#$%&'()*+,-./:;<=>?@[\]^_``{|}~"
}.GetEnumerator().ForEach{ $CharacterSets.Add($_.Key, $_.Value) }
$CustomCharacterSet.GetEnumerator().ForEach{ $CharacterSets.Add($_.Key, $_.Value) }
# This returns a RNGCryptoServiceProvider
$cryptoRNG = [System.Security.Cryptography.RandomNumberGenerator]::Create()
}
process {
# Create the return object
$securePassword = [System.Security.SecureString]::new()
# Expand the template
$Template = [regex]::replace($Template, "(.)(\d+)", {param($match) $match.Groups[1].Value * [int]($match.Groups[2].Value)})
Write-Verbose "Template: $Template"
$b = [byte[]]0
for($c = 0; $c -lt $Template.Length; $c++) {
$securePassword.AppendChar($(
if($Template[$c] -eq '\') {
$Template[(++$c)]
} else {
$cryptoRNG.GetBytes($b)
$char = $Template[$c]
if ($Set = $CharacterSets[$char]) {
$Index = [int]$b[0] % $Set.Length
$Set[$Index]
} else {
$char
}
}
))
}
return $securePassword
}
}
@mattzaharias
Copy link

Hey Jaykul, great script notes and usage examples. Is there a way to run this script where we can see the actual output? I'd like to use it to generate several passwords and then make my ultimate selection among those generated passwords. My output is just "System.Security.SecureString". I simply copy/pasted the code here and saved and run it as a .ps1 powershell script.

@craiglandis
Copy link

ConvertFrom-SecureString -SecureString (new-password) didn't return the plain text string, but this does (worked on both PS 6.2 and 5.1):

$securePassword = new-password
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePassword)
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)

@Jaykul
Copy link
Author

Jaykul commented Mar 26, 2020

You can also abuse [System.Net.NetworkCredential] like:

$SecurePassword = New-Password
[Net.NetworkCredential]::new("user", $SecurePassword).Password

Or if you're making a bunch:

$cred = [Net.NetworkCredential]::new()
foreach ($i in 1..10) {
    $cred.SecurePassword = New-Password "zvcvcddzvcvc"
    $cred.Password
}

@lxsocon
Copy link

lxsocon commented Oct 9, 2020

If you are using PowerShell (i.e. not Windows PowerShell), then you can actually get at the password using this:

New-Password 'A24' | ConvertFrom-SecureString -AsPlainText

(i.e. a 24 character alphanumeric password).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment