Reputation:
I have the following PowerShell script that creates a random string of 15 digits, for use as an Active Directory password.
The trouble is, this works great most of the time, but on some occasions it doesn't use a number or symbol. I just get 15 letters. This is then not usable as an Active Directory password, as it must have at least one number or symbol in it.
$punc = 46..46
$digits = 48..57
$letters = 65..90 + 97..122
$YouShallNotPass = get-random -count 15 `
-input ($punc + $digits + $letters) |
% -begin { $aa = $null } `
-process {$aa += [char]$_} `
-end {$aa}
Write-Host "Password is $YouShallNotPass"
How would I amend the script to always have at least one random number or symbol in it?
Thank you.
Upvotes: 8
Views: 33912
Reputation: 872
Here is my attempt at a cryptographically secure password generator. The core functionality is based on this C# version which seems well regarded. The finer details of it you can read in the DESCRIPTION
section of the help comment.
EDIT: Added SecureString
as the default output type to make it easier to create functions that PSScriptAnalyzer won't complain about even though it's mostly security theater. You can override by supplying -OutputAs String
.
<#
.SYNOPSIS
Creates a cryptographically secure password
.DESCRIPTION
Creates a cryptographically secure password
The dotnet class [RandomNumberGenerator] is used
to create cryptographically random values which
are converted to numbers and used to index each
character set.
Minimum required character types is implemented
by generating those values up front with a specific
character set, generating remaining characters with
the full character set and finally shuffling
everything together.
This cmdlet is compatible with both Powershell Desktop
and Core. When using Powershell Core, a safer shuffler
cmdlet is used.
.NOTES
Thanks to
* CodesInChaos - Core functionality from his CSharp version (https://stackoverflow.com/a/19068116/5339918)
* Jamesdlin - Minimum char shuffle idea (https://stackoverflow.com/a/74323305/5339918)
* Shane - Json safe flag idea (https://stackoverflow.com/a/73316960/5339918)
.EXAMPLE
Create password as plaintext string.
New-Password -OutputAs String
.EXAMPLE
Specify password length and exclude Numbers/Symbols from password
New-Password -Length 64 -NumberCharset @() -SymbolCharset @()
.EXAMPLE
Require 2 of each character set in final password
New-Password -MinimumUpper 2 -MinimumLower 2 -MinimumNumber 2 -MinimumSymbol 2
#>
function New-Password {
[CmdletBinding()]
[OutputType([securestring], [string])]
param(
[ValidateRange(1, [uint32]::MaxValue)]
[uint32] $Length = 32,
[uint32] $MinimumUpper,
[uint32] $MinimumLower,
[uint32] $MinimumNumber,
[uint32] $MinimumSymbol,
[char[]] $UpperCharSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
[char[]] $LowerCharSet = 'abcedefghijklmnopqrstuvwxyz',
[char[]] $NumberCharSet = '0123456789',
[char[]] $SymbolCharSet = '!@#$%^&*()[]{},.:`~_-=+', # Excludes problematic characters like ;'"/\,
[ValidateSet('SecureString', 'String')]
[string] $OutputAs = 'SecureString',
[switch] $JsonSafe,
[switch] $SqlServerSafe
)
#============
# PRE CREATE
#============
if ($JsonSafe) {
$ProblemCharacters = @(';', "'", '"', '/', '\', ',', '`', '&', '+')
[char[]] $SymbolCharSet = $SymbolCharSet | Where-Object { $_ -notin $ProblemCharacters }
}
if ($SqlServerSafe) {
$ProblemCharacters = @(';', "'", '"', '/', '\', ',', '`', '$', '&', '+', '{', '}')
[char[]] $SymbolCharSet = $SymbolCharSet | Where-Object { $_ -notin $ProblemCharacters }
}
# Parameter validation
switch ($True) {
{ $MinimumUpper -and -not $UpperCharSet } { throw 'Cannot require uppercase without a uppercase charset' }
{ $MinimumLower -and -not $UpperCharSet } { throw 'Cannot require lowercase without a lowercase charset' }
{ $MinimumNumber -and -not $UpperCharSet } { throw 'Cannot require numbers without a numbers charset' }
{ $MinimumSymbol -and -not $SymbolCharSet } { throw 'Cannot require symbols without a symbol charset' }
}
$TotalMinimum = $MinimumUpper + $MinimumLower + $MinimumNumber + $MinimumSymbol
if ($TotalMinimum -gt $Length) {
throw "Total required characters ($TotalMinimum) exceeds password length ($Length)"
}
$FullCharacterSet = $UpperCharSet + $LowerCharSet + $NumberCharSet + $SymbolCharSet
#=========
# CREATE
#=========
$CharArray = [char[]]::new($Length)
$Bytes = [Byte[]]::new($Length * 8) # 8 bytes = 1 uint64
$RNG = [System.Security.Cryptography.RandomNumberGenerator]::Create()
$RNG.GetBytes($Bytes) # Populate bytes with random numbers
for ($i = 0; $i -lt $Length; $i++) {
# Convert the next 8 bytes to a uint64 value
[uint64] $Value = [BitConverter]::ToUInt64($Bytes, $i * 8)
if ($MinimumUpper - $UpperSatisfied) {
$CharArray[$i] = $UpperCharSet[$Value % [uint64] $UpperCharSet.Length]
$UpperSatisfied++
continue
}
if ($MinimumLower - $LowerSatisfied) {
$CharArray[$i] = $LowerCharSet[$Value % [uint64] $LowerCharSet.Length]
$LowerSatisfied++
continue
}
if ($MinimumNumber - $NumberSatisfied) {
$CharArray[$i] = $NumberCharSet[$Value % [uint64] $NumberCharSet.Length]
$NumberSatisfied++
continue
}
if ($MinimumSymbol - $SymbolSatisfied) {
$CharArray[$i] = $SymbolCharSet[$Value % [uint64] $SymbolCharSet.Length]
$SymbolSatisfied++
continue
}
$CharArray[$i] = $FullCharacterSet[$Value % [uint64] $FullCharacterSet.Length]
}
if ($TotalMinimum -gt 0) {
if ($PSVersionTable.PSEdition -eq 'Core') {
$CharArray = $CharArray | Get-SecureRandom -Shuffle
} else {
# If `-SetSeed` is used, this would always produce the same result
$CharArray = $CharArray | Get-Random -Count $Length
}
}
if ($OutputAs -eq 'SecureString') {
# Apparently this is CLS-Compliant compared to [SecureString]::New(Char*, Int32)
# https://learn.microsoft.com/en-us/dotnet/api/system.security.securestring.-ctor?view=net-9.0
$SecureString = [SecureString]::new()
foreach($Char in $CharArray) {
$SecureString.AppendChar($Char)
}
return $SecureString
}
return [String]::new($CharArray)
}
Upvotes: 0
Reputation: 23763
As suggested by jisaak, there is no 100% guaranty that the Membership.GeneratePassword Method generates a password that meets the AD complexity requirements.
That's why I reinvented the wheel:
Function Create-String([Int]$Size = 8, [Char[]]$CharSets = "ULNS", [Char[]]$Exclude) {
$Chars = @(); $TokenSet = @()
If (!$TokenSets) {$Global:TokenSets = @{
U = [Char[]]'ABCDEFGHIJKLMNOPQRSTUVWXYZ' #Upper case
L = [Char[]]'abcdefghijklmnopqrstuvwxyz' #Lower case
N = [Char[]]'0123456789' #Numerals
S = [Char[]]'!"#$%&''()*+,-./:;<=>?@[\]^_`{|}~' #Symbols
}}
$CharSets | ForEach {
$Tokens = $TokenSets."$_" | ForEach {If ($Exclude -cNotContains $_) {$_}}
If ($Tokens) {
$TokensSet += $Tokens
If ($_ -cle [Char]"Z") {$Chars += $Tokens | Get-Random} #Character sets defined in upper case are mandatory
}
}
While ($Chars.Count -lt $Size) {$Chars += $TokensSet | Get-Random}
($Chars | Sort-Object {Get-Random}) -Join "" #Mix the (mandatory) characters and output string
}; Set-Alias Create-Password Create-String -Description "Generate a random string (password)"
Usage:
Size
parameter defines the length of the password.CharSets
parameter defines the complexity where the character U
,
L
, N
and S
stands for Uppercase, Lowercase, Numerals and Symbols.
If supplied in lowercase (u
, l
, n
or s
) the returned string
might contain any of character in the concerned character set, If
supplied in uppercase (U
, L
, N
or S
) the returned string will
contain at least one of the characters in the concerned character
set.Exclude
parameter lets you exclude specific characters that might e.g.
lead to confusion like an alphanumeric O
and a numeric 0
(zero).Examples:
To create a password with a length of 8 characters that might contain any uppercase characters, lowercase characters and numbers:
Create-Password 8 uln
To create a password with a length of 12 characters that that contains at least one uppercase character, one lowercase character, one number and one symbol and does not contain the characters OLIoli01:
Create-Password 12 ULNS "OLIoli01"
For the latest New-Password
version: use:
Install-Script -Name PowerSnippets.New-Password
Upvotes: 14
Reputation: 11
I've created this. You can choose how many Pwd to create
$howoften = Read-Host "How many would you like to create: "
$i = 0
do{
(-join(1..42 | ForEach {((65..90)+(97..122)+(".") | % {[char]$_})+(0..9)+(".") | Get-Random}))
$i++
} until ($i -match $howoften)
To change the length of the pwd simply edit the "42" in line 4
(-join(1..**42** | ForEach ...
Upvotes: 1
Reputation: 1
I had the same issue here is the snippet I used to create my alphanumerical password its simple all I have done is used ASCII regex replace to make it nice.
Function Password-Generator ([int]$Length)
{
# Generate passwords just call password-generator(lenght of password)
$Assembly = Add-Type -AssemblyName System.Web
$RandomComplexPassword = [System.Web.Security.Membership]::GeneratePassword($Length,2)
$AlphaNumericalPassword = $RandomComplexPassword -replace '[^\x30-\x39\x41-\x5A\x61-\x7A]+'
Write-Output $AlphaNumericalPassword
}
Upvotes: 0
Reputation: 870
My take on generating passwords in PowerShell, based on what I've found here and in the Internets:
#Requires -Version 4.0
[CmdletBinding(PositionalBinding=$false)]
param (
[Parameter(
Mandatory = $false,
HelpMessage = "Minimum password length"
)]
[ValidateRange(1,[int]::MaxValue)]
[int]$MinimumLength = 24,
[Parameter(
Mandatory = $false,
HelpMessage = "Maximum password length"
)]
[ValidateRange(1,[int]::MaxValue)]
[int]$MaximumLength = 42,
[Parameter(
Mandatory = $false,
HelpMessage = "Characters which can be used in the password"
)]
[ValidateNotNullOrEmpty()]
[string]$Characters = '1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM@#%*-_+:,.'
)
(1..(Get-Random -Minimum $MinimumLength -Maximum $MaximumLength) `
| %{ `
$Characters.GetEnumerator() | Get-Random `
}) -join ''
I preferred this over using System.Web, not to introduce dependencies, which could change with .Net / .Net Core versions.
My variation also allows random password length (in specified range), is fairly concise (apart from the parameters section, which is quite verbose, to enforce some validations and provide defaults) and allows character repetitions (as opposite to the code in the question, which never repeats the same character).
I understand, that this does not guarantee a digit in the password. This however can be addressed in different ways. E.g. as was suggested, to repeat the generation until the password matches the requirements (contains a digit). My take would be:
Assuming, that the above script would be named "Get-RandomPassword.ps1", it could look like this:
$pass = .\Get-RandomPassword.ps1
$pass += (0..9 | Get-Random)
$pass = (($pass.GetEnumerator() | Get-Random -Count $pass.Length) -join '')
Write-Output $pass
This can be generalized, to enforce using any character category:
$sets = @('abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', '0123456789', '()-_=+[{]};:''",<.>/?`~')
$pass = .\Get-RandomPassword.ps1 -Characters ($sets -join '')
foreach ($set in $sets) {
$pass += ($set.GetEnumerator() | Get-Random)
}
$pass = (($pass.GetEnumerator() | Get-Random -Count $pass.Length) -join '')
Write-Output $pass
Upvotes: 0
Reputation: 47
Command to Generate Random passwords by using existing funciton:
[system.web.security.membership]::GeneratePassword(x,y)
x = Length of the password
y = Complexity
General Error:
Unable to find type [system.web.security.membership]. Make sure that the assembly that contains this type is loaded.
Solution:
Run the below command:
Add-Type -AssemblyName System.web;
Upvotes: 3
Reputation: 14959
Another solution:
function New-Password() {
param(
[int] $Length = 10,
[bool] $Upper = $true,
[bool] $Lower = $true,
[bool] $Numeric = $true,
[string] $Special
)
$upperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
$lowerChars = "abcdefghijklmnopqrstuvwxyz"
$numericChars = "0123456789"
$all = ""
if ($Upper) { $all = "$all$upperChars" }
if ($Lower) { $all = "$all$lowerChars" }
if ($Numeric) { $all = "$all$numericChars" }
if ($Special -and ($special.Length -gt 0)) { $all = "$all$Special" }
$password = ""
for ($i = 0; $i -lt $Length; $i++) {
Write-Host "password: [$password]"
$password = $password + $all[$(Get-Random -Minimum 0 -Maximum $all.Length)]
}
$valid = $true
if ($Upper -and ($password.IndexOfAny($upperChars.ToCharArray()) -eq -1)) { $valid = $false }
if ($Lower -and ($password.IndexOfAny($lowerChars.ToCharArray()) -eq -1)) { $valid = $false }
if ($Numeric -and ($password.IndexOfAny($numericChars.ToCharArray()) -eq -1)) { $valid = $false }
if ($Special -and $Special.Length -gt 1 -and ($password.IndexOfAny($Special.ToCharArray()) -eq -1)) { $valid = $false }
if (-not $valid) {
$password = New-Password `
-Length $Length `
-Upper $Upper `
-Lower $Lower `
-Numeric $Numeric `
-Special $Special
}
return $password
}
Flexible enough to set length, turn on/of upper, lower, and numeric, and set the list of specials.
Upvotes: 0
Reputation: 58981
You could invoke the Get-Random cmdlet three times, each time with a different input
parameter (punc, digit and letters), concat the result strings and shuffle them using another Get-Random
invoke:
(Get-Random -Count 15 -InputObject ([char[]]$yourPassword)) -join ''
However, why do you want to reinvent the wheel? Consider using the following GeneratePassword function:
[Reflection.Assembly]::LoadWithPartialName("System.Web")
[System.Web.Security.Membership]::GeneratePassword(15,2)
And to ensure, it contains at least one random number (you already specify the number of symbols):
do {
$pwd = [System.Web.Security.Membership]::GeneratePassword(15,2)
} until ($pwd -match '\d')
Upvotes: 21