Generate a Random Password

A quick google didn’t return too many interesting PowerShell scripts so I decided to revisit one of my own.

The jury is still out on if we should be changing passwords regularly ( or maybe not after reading the links below).  Forcing regular password changes just makes people use weak password and password systems which allow an attacker to decode all passwords having gained access to one.

Best practice is now not to change your password but use a strong ( complex strong not just long ) one instead.

Take a look at these links:

Even the National Cyber Security Centre don’t recommend you force people to change the password.

So having firmly established that we shouldn’t be changing passwords my next two blogs will be on how to generate a secure password ( well if we are going to change it then we might as well change it to a strong one ) and to automate the change 🙂

Often Privileged service accounts will have a non expiring password – Crazy!

All of these articles assume we are discussing end users not service accounts.  Systems don’t care if the password is horrendous to remember or type.  Service accounts are often privileged accounts and yet almost all service accounts have a password that to set to never expire.

Microsoft tacked on fine grained passwords ( and it feels like its tacked on too ) none of the existing management tools are even aware of it.  You need to be a domain admin to use the PowerShell commandlets to determine what the resultant password policy actually is for each user.   Most tools still just look at the default domain policy.

Microsoft have been trying to help with this issue with recent OS updates.  We can now set more than one password policy in a domain using Password Settings Objects and we can also use ‘managed service accounts’ for many of the Microsoft products.   Managed service accounts are great as no one knows the password ever and the system will change it for you on a regular basis.

Use Managed Service Accounts if you can but if not then extra strong passwords for service accounts should be used.  Use ARS to enforce then when an account is created by adding the account to a dynamic group linked to a fine grained password policy.

You should be using stronger password policies for all service accounts.  Although you could do this manually of course, using a Password Settings Object with a strong password policy is going to ensure someone doesn’t use a very easy to guess password on their service account once you hand it over to them.  There are other restrictions you can place on service accounts but that’s straying too far off piste for this blog article.

If you have ARS then you can use a dynamic group to ensure all new service accounts get the appropriate password policy.

Where possible use managed service accounts but what about the rest?  Applications that can’t use Managed Service accounts and the app support teams want a non expiring password.

This is where you need your Information Security Team to go to bat for you but if the app support team are allowed to have a static password on their service account then they should be required to change it regularly.  For windows based applications generally that’s not too difficult and there are some tools out there that can help. Many password vaults will change the password on a schedule and also update the service password on systems where the service account is used.  This is again a little off piste so lets get back to the main topic of the next two blog articles.  How to generate a secure password and then how to use this password to reset the password on an ARS managed domain.

My script function is self documented so I won’t really need to write too much about it.  Just use Get-Help to list out the script documentation.   I use  an arrays to store the various character sets and then use Get-Random to choose one of the characters.  There are 4 arrays in all, one for each character type, i.e. Upper Case letters , Lower case letters, numbers and Special characters.  I then combine them into a single array.  The script parameters control which arrays are combined, e.g. if I use -special 0 then the special characters are left out of the combined array of allowed characters.

I then needed a way of ensuring that a password contained the minimum number of each character set.    The way I did this was to create a while loop that keeps going until I have my password.  Inside the ‘While’ loop I select a random character from the combined set of characters  and compare it with the individual character sets and increment a counter so that the password would always have the minimum number of required character types in the password before padding the password to meet the length requirement.  If the charLimit is passed to the function then it will prevent repeat characters.

There is a slight problem here in that I just removed some of the randomness ( is that a word? ) as the first set of characters have a known formula, i.e. using the function defaults, by not adding any command line switches, means the first 4 characters will always be made up of 1 upper alpha, 1 lower alpha 1 numeric and 1 special.   This is hardly insecure but for good measure I wrote another bit of code to randomise the initial password generated by my code.  I take the initial password and copy it to another variable, converting it to a list which will allow me to access the Remove method.

Sadly the ‘Remove’ method of an array object does not work so you need to convert the array to a list if you want to remove characters from the array

I can now use a For loop and randomly select one of the initial password characters and start building a new password removing the character from the array list as I go.  This keeps my password complexity intact but also improves the randomness of the returned password.  If the  concurrentLimit parameter is used then this section of code also prevents repeating characters.

I also added a way of making the generated password more user friendly using a -friendly switch.  This reduces the character set by removing the ambiguous characters, e.g. l and I.  This is really designed for a single use password that will be used to reset a users password and you want to ensure when you communicate the password to the user that they don’t mistake some of the characters.  The password is definitely not as strong when you reduce the allowed character pool but as you will be setting the user must change password at next logon flag this isn’t a problem.

here is the full function:

Function Get-RandomPassword {
 <#   .SYNOPSIS   Generates a Random Password.   .DESCRIPTION   Generates a Random Password which has controllable complexity using command line switches.  The friendly mode is typically used   when generating a password for one time use only and avoids possible confusion over the characters by removing ambiguous    characters from the available pool, e.g. I and l look the same so are removed from the characters set used to build the    password.      The default password is complex with at least 8 characters including at least 1 upper, 1 lower, 1 number and 1 special character.      You can also limit the number of repeated and consecutive characters that are allowed although these actually make a password weaker    if it is known that this rule is used when generating passwords.  It is included in case this limitation is imposed by the system    you are generating the password for.   .OUTPUTS   A Password String   .PARAMETER MaxLength   [int] The Maximum length of the password to be returned.     .PARAMETER MinLength   [int] The minimum length of the password to be returned.   .PARAMETER Upper   [int] The MINIMUM number of UPPER case characters to be included in the password.       .PARAMETER Lower   [int] The MINIMUM number of LOWER case characters to be included in the password.       .PARAMETER Number   [int]   The MINIMUM number of NUMERIC characters to be included in the password.    .PARAMETER Special   [int]   The MINIMUM number of SPECIAL characters to be included in the password.       .PARAMETER CharLimit   [int]   Limits the number of times a character can appear in the password, e.g. aardvark is not allowed because 'a' and 'r' are repeated in the password.      .PARAMETER ConcurrentLimit   [int]   Limits the number of times a character can be used concurrently, e.g. 'P4ssword$' is not allowed becasue 'ss' is present in the password.       .PARAMETER Padright   [int] Used for debug only and determines the message padding when write-host is called      .PARAMETER alphaFirst   [switch] Forces first character to be an Alpha.       .PARAMETER numericFirst   [switch] Forces first character to be NUMERIC.       .PARAMETER showDebug   [switch] Used to display debug messages to screen.       .PARAMETER Friendly   [switch] Used to limit the characters set by removing the ambiguous characters, e.g. I and l .    .EXAMPLE    $password = Get-RandomPassword -MaxLength 20 -MinLength 10 -Upper 5 -Lower 5 -number 1 -Special 1     Returns a 12 to 20 Character password - because the MinLEngth must be at least equal to the character requirements    e.g. 5 + 5 + 1 + 1 = 12      .EXAMPLE    $password = Get-RandomPassword -Friendly      Returns a strong 8 character password - excluding any ambiguous characters       .EXAMPLE    $password = Get-RandomPassword -MaxLength 10 -MinLength 8  -Special 0    Returns a password 8 to 10 characters in length that does not include any special characters   .NOTES   FunctionName : Get-RandomPassword   Created by   : Lee Andrews   Date Coded   : 03/06/2017   #>
 param (
  [parameter(HelpMessage = 'The Maximum Length of the password to generate')]
  [int]$MaxLength  = 8,
  [parameter(HelpMessage = 'The Minimum Length of the password to generate')]
  [int]$MinLength  = 8,
  [parameter(HelpMessage = 'The Minimum number of the UPPER case characters to be added to the password')]
  [int]$Upper      = 1,
  [parameter(HelpMessage = 'The Minimum number of the LOWER case characters to be added to the password')]
  [int]$Lower      = 1,
  [parameter(HelpMessage = 'The Minimum number of the NUMBERS to be added to the password')]
  [int]$number     = 1,
  [parameter(HelpMessage = 'The Minimum number of the SPECIAL characters to be added to the password')]
  [int]$Special    = 1,
  [parameter(HelpMessage = 'The Maximum Times a character can be used in a password, 0 = no limit')]
  [int]$CharLimit   = 0,
  [parameter(HelpMessage = 'The Maximum number of concurrent characters that can be the same, 0 = no limit')]
  [int]$concurrentLimit  = 0,
  [parameter(HelpMessage = 'Used to pad debug messages')]
  [int]$padright   = 100,
  [parameter(HelpMessage = 'Used to force an ALPHA to the begining of the password')]
  [switch]$alphaFirst,
  [parameter(HelpMessage = 'Used to force an NUMERIC to the begining of the password')]
  [switch]$numericFirst,
  [parameter(HelpMessage = 'Used to display messages to screen')]
  [switch]$showDebug,
  [parameter(HelpMessage = 'Used to remove ambiguous characters from the available pool')]
  [switch]$friendly
 )

  Function Update-Password {
  param (
   [ref]$pwd,
   [ref]$foundChrType,
   $requiredCharCount,
   $requiredComplexityMet,
   $pwdChr
  )
  Function Check-ConcurrentLimtNotExceeded {
   param ($nextPWDChar)
   if ( $CharLimit -ne 0 ) {
    if ( $charCount.contains($nextPWDChar) ) {
     if ( $charCount.item($nextPWDChar) -eq $CharLimit ) {
      $combination.Remove($nextPWDChar)
      Return $true
     }
     else {
      $charCount.item($nextPWDChar) = $charCount.item($nextPWDChar) + 1
     }
    }
    else {
     $charCount.add($nextPWDChar,1)
    }
   }
   Return $false
  }
  if ( $requiredComplexityMet ) {
   if ( $foundChrType.value -gt 0 ) {
    if ( Check-ConcurrentLimtNotExceeded $pwdChr ) {
     return $true
    }
    $pwd.value += $pwdChr
    $foundChrType.value  ++
   }
   Return $true
  }
  if ( $foundChrType.value -lt $requiredCharCount  ) {
   if ( Check-ConcurrentLimtNotExceeded $pwdChr ) {
    Return $false
   }
   $foundChrType.value  ++
   $pwd.value += $pwdChr
   Return $false
  }
  Return $true
 } # Function Update-Password {
 #region Initialise Variables
 if ( $friendly ) {
  # use this set when setting a password with the password must change flag
  $uppers = 'A','C','E','F','H','K','M','N','T','W','X','Y'
  $lowers = 'a','b','c','d','e','f','g','h','k','m','n','r','s','t','w','x','y','z'
  $numerals = '3','4','7','9'
  $specials = '£','%','&','*','@','#','='
 }
 else {
  $uppers = 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'
  $lowers = 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'
  $numerals = '0','1','2','3','4','5','6','7','8','9'
  $specials = '£','%','&','*','@','#','='
 }
 $combinationList     = @()
 $totalRequiredLength = 0
 $foundUppers         = 0
 $foundLowers         = 0
 $foundNumerals       = 0
 $foundSpecials       = 0
 $pwd                 = ''
 $charCount           = @{}
 $concurrent          = @{}
 #endregion Initialise Variables
 #region Dynamically adjust variables based on password complexity requirements
 if ( $Upper   -ge 1 ) { $combinationList += $uppers   ; $totalRequiredLength += $Upper   ; $foundUpper   = $false } else { $foundUpper   = $true }
 if ( $Lower   -ge 1 ) { $combinationList += $lowers   ; $totalRequiredLength += $lower   ; $foundLower   = $false } else { $foundLower   = $true }
 if ( $number  -ge 1 ) { $combinationList += $numerals ; $totalRequiredLength += $number  ; $foundNumeral = $false } else { $foundNumeral = $true }
 if ( $special -ge 1 ) { $combinationList += $specials ; $totalRequiredLength += $special ; $foundSpecial = $false } else { $foundSpecial = $true }
 $combination = New-Object System.Collections.ArrayList($null)
 $combination.AddRange($combinationList)
 if ( $showDebug ) {
  Write-Host "".padright($padright,"-") -ForegroundColor Green -background Black
  Write-Host "Maximum Password Length = $MaxLength".padright($padright) -ForegroundColor Green -background Black
  Write-Host "Minimum Password Length = $MinLength".padright($padright) -ForegroundColor Green -background Black
  Write-Host "Total   Required Length = $totalRequiredLength".padright($padright) -ForegroundColor Green -background Black
 }
 if ( $totalRequiredLength -le 0          ) { Throw "ERROR password complexity rules does not include any character sets" }
 if ( $MaxLength           -lt $MinLength ) { Throw "Minimum password length cannot be less than maximum password length" }
 if ( $totalRequiredLength -gt $MaxLength ) { Throw "Password complexity requirement greater than the requested maximum password length" }
 if ( $MinLength -lt $totalRequiredLength ) {
  $MinLength = $totalRequiredLength
  if ( $showDebug ) {
   Write-Host "Min Password Length increased to match the larger minimum REQUIRED PWD length".padright($padright) -ForegroundColor Green -background Black
  }
 }
 $PwdLen = $rand.Next($MinLength,$MaxLength+1) # generate a random password length between min and max length parameters
 if ( $showDebug ) { Write-Host "Target  Password Length = $PwdLen".padright($padright) -ForegroundColor Green -background Black }
 #endregion Dynamically adjust variables based on password complexity requirements
 #region Build Initial password based on complexity requirements
 $loopRetries = 10000
 $loopTry = 0
 $lastPWDChar = ""
 while ( ( $PWD.length -lt $PwdLen ) -or ( ! ( $foundUpper -and $foundLower -and $foundNumeral -and $foundSpecial ) ) ) {
  if ( $loopRetry -gt $loopRetries ) { Throw "Unable to create password - compexity rules may be too restrictive" } else { $loopTry ++ }
  try { $nextPWDChar = Get-Random -InputObject $combination }
  catch {
   $bp = 0
   Throw "FATAL ERROR: Complexity Rules have exhausted the character pool"
  }
  #Write-Host "Next Chr = $nextPWDChar"

  switch ($nextPWDChar) {
  	{$uppers -ccontains $_} {
    $foundUpper = Update-Password -foundChrType ([ref]$foundUppers) -pwdChr $_ -pwd ([ref]$pwd) -requiredCharCount $Upper  -requiredComplexityMet $( $foundUpper -and $foundLower -and $foundNumeral -and $foundSpecial )
    break
  	}
  	{$lowers -ccontains $_} {
    $foundLower = Update-Password -foundChrType ([ref]$foundLowers) -pwdChr $_ -pwd ([ref]$pwd) -requiredCharCount $Lower  -requiredComplexityMet $( $foundUpper -and $foundLower -and $foundNumeral -and $foundSpecial )
  		break
  	}
  	{$numerals -ccontains $_} {
    $foundNumeral = Update-Password -foundChrType ([ref]$foundNumerals) -pwdChr $_ -pwd ([ref]$pwd) -requiredCharCount $number  -requiredComplexityMet $( $foundUpper -and $foundLower -and $foundNumeral -and $foundSpecial )
    break
  	}
  	{$specials -ccontains $_} {
    $foundSpecial = Update-Password -foundChrType ([ref]$foundSpecials) -pwdChr $_ -pwd ([ref]$pwd) -requiredCharCount $special  -requiredComplexityMet $( $foundUpper -and $foundLower -and $foundNumeral -and $foundSpecial )
    break
	  }
	  default {
    Throw "UNKNOWN CHARACTER in Random PASSWORD Script Cannot Continue"
	  	break
	  }
  }
  $lastPWDChar = $nextPWDChar
 } # while
 #endregion Build Initial password based on complexity requirements
 #region Optionally Show password complexity information
 if ( $showDebug ) {
  Write-Host "".padright($padright,"-") -ForegroundColor Green -background Black
  Write-Host "Min Uppers...: $Upper".padright($padright) -ForegroundColor Green -background Black
  Write-Host "Min Lowers...: $Lower".padright($padright) -ForegroundColor Green -background Black
  Write-Host "Min Numerals.: $number".padright($padright) -ForegroundColor Green -background Black
  Write-Host "Min Specials.: $Special".padright($padright) -ForegroundColor Green -background Black
  if ( $alphaFirst ) {
   Write-Host "First must be: ALPHA".padright($padright) -ForegroundColor Green -background Black
  }
  if ( $numericFirst ) {
   Write-Host "First must be: NUMERIC".padright($padright) -ForegroundColor Green -background Black
  }
  Write-Host "".padright($padright,"-") -ForegroundColor Green -background Black
  Write-Host "Set Uppers...: $foundUppers".padright($padright) -ForegroundColor Green -background Black
  Write-Host "Set Lowers...: $foundLowers".padright($padright) -ForegroundColor Green -background Black
  Write-Host "Set Numerals.: $foundNumerals".padright($padright) -ForegroundColor Green -background Black
  Write-Host "Set Specials.: $foundSpecials".padright($padright) -ForegroundColor Green -background Black
  Write-Host "".padright($padright,"-") -ForegroundColor Green -background Black
  Write-Host "Initial Password Length = $($PWD.length)".padright($padright) -ForegroundColor Green -background Black
  Write-Host "Final   Password Length = $($PWD.length)".padright($padright) -ForegroundColor Green -background Black
  Write-Host "".padright($padright,"-") -ForegroundColor Green -background Black
  Write-Host "Randomising Initial Password : $pwd ( Length = $($pwd.length) )".padright($padright) -ForegroundColor Yellow -background Black
 }
 #endregion Optionally Show password complexity information
 #region Randomise the password character order
 $sourcePWD = New-Object System.Collections.ArrayList($null)
 $sourcePWD.AddRange($pwd.ToCharArray())
 $newPWD = ''
 if ( ( $alphaFirst ) -and ( $numericFirst ) ) { Throw "ERROR: you cannot select both ALPHA and NUMERIC first switches" }
 if ( $alphaFirst ) {
  if ( $PWD -notmatch "[A-Za-z]" ) { Throw "Password MUST contain at least one ALPHA" }
 }
 if ( $numericFirst ) {
  if ( $PWD -notmatch "[0-9]" ) { Throw "Password MUST contain at least one NUMERAL" }
 }
 #for ($index = 0; $index -lt $PWD.Length; $index++) {
 $index = 0
 while ( $sourcePWD.length -gt 0 ) {
  #Write-Host "SOURCE : $($sourcePWD -join '') ($($sourcePWD.count))"
  #Write-Host "NEW PWD: $newPWD ($($newPWD.length))"
  try { $nextPWDChar = Get-Random -InputObject $sourcePWD }
  catch {
   $bp = 0
   Throw "FATAL ERROR: Complexity Rules have exhausted the character pool"
  }
  if ( ( $index -eq 0 ) -and ( $alphaFirst ) ) {
   while ($nextPWDChar  -notmatch "[a-z]") {
    $nextPWDChar = Get-Random -InputObject $sourcePWD
   }
  } # if ( $index -eq 0 ) -and ( $alphaFirst ) {
  elseIf ( ( $index -eq 0 ) -and ( $numericFirst ) ) {
   while ($nextPWDChar  -notmatch "[0-9]") {
    $nextPWDChar = Get-Random -InputObject $sourcePWD
   }
  } # if ( $index -eq 0 ) -and ( $alphaFirst ) {
  if ( $concurrentLimit -ne 0 ) {
   if ( $lastPWDChar -eq $nextPWDChar ) {
    if ( $concurrent.contains($nextPWDChar) ) {
     if ( $concurrent.item($nextPWDChar) -eq $concurrentLimit ) {
      Continue
     }
     else {
      $concurrent.item($nextPWDChar) = $concurrent.item($nextPWDChar) + 1
     }
    } # if ( $concurrent.contains($nextPWDChar) ) {
    else {
     $concurrent.Add($nextPWDChar,1)
    }
   } # if ( $lastPWDChar -eq $nextPWDChar ) {
  } # if ( $concurrentLimit -ne 0 ) {
	 $newPWD += $nextPWDChar
  $index ++
  $lastPWDChar = $nextPWDChar
  $sourcePWD.Remove($nextPWDChar)
 }
 if ( $showDebug ) {
  write-host "Password changed to          : $($newPWD) ( Length = $($newPWD.length) )".padright($padright) -ForegroundColor Yellow -background Black
  Write-Host "".padright($padright,"-") -ForegroundColor Green -background Black
 }
 #endregion Randomise the password character order
 Return  $newPWD
} # end Function Get-RandomPassword
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.