Part 2: Managing Local Administrator Passwords

Overview

This is Part 2 of a multi-part series on managing local admin passwords. In case you missed it, you can view Part 1 here

The Problem

One of the challenges associated with generating a random local admin password is how to make the generated password as random as possible. In many of the example PowerShell random password generation scripts that I have seen, the system.random object is used as the random character generator. The problem with this is that that particular object, while random, is not as good as using the System.Security.Cryptography.RNGCryptoServiceProvider. This object generates a cryptographically random number and is documented on MSDN here.

The Solution

The following function relies on a cryptographic random character generation function to create a random password of configurable complexity and length using cryptographically randomly generated characters. If the function is called without any parameters then the function will generate a 16 character password with 2 lowercase and 2 upper case characters, 2 numbers, 2 special characters, and no ambiguous characters.

#==================================================================================
# Generate Random Password
#==================================================================================
function fnGeneratePassword
{Param ([int]$PassLength=16,[int]$iLCaseNeeded=2,[int]$iUCaseNeeded=2,[int]$iNumNeeded=2,[int]$iSpecNeeded=2,[int]$AvoidAmbiguous=$true)

    #Calculates minimum password length required to meet complexity
    $MinPwdLength = $iLCaseNeeded + $iUCaseNeeded + $iNumNeeded + $iSpecNeeded

    #Feedback if password is too short
    if($PassLength -lt $MinPwdLength){
      #Convert Integer to String
      $MinPwdLength = [string]$MinPwdLength

      #Log Output
      #fnLog -LogPath $LogDir -LogFileName $LogFileName -Data "ERROR: Requested password length must be $MinPwdLength or more characters"

      #Exit
      return "ERROR"
    }
    #NonAmbiguous Characters
    $sLCase = "abcdefghijkmnpqrstuvwxyz"
    $sUCase = "ABCDEFGHJKLMNPQRSTUVWXYZ"
    $sNum = "23456789"
    $sSpec = "!#$%&()*+,-./:;<=>?@[\]^_{}~"

    #Adds Ambiguous Characters if desired
    if($AvoidAmbiguous -eq $false){
        $sLCase += "lo";$sUCase += "IO";$sNum += "01";$sSpec += "`"'``|"
    }
    #Creates Character Arrays
    $sLCase = $sLCase.ToCharArray();$sUCase = $sUCase.ToCharArray();$sNum = $sNum.ToCharArray();$sSpec = $sSpec.ToCharArray()
    $sFullSet = $sLCase + $sUCase + $sNum + $sSpec; $aCharacters = @{}

    #Selects Lower Case Characters
    for($i =0;$i -lt $iLCaseNeeded;$i++){
        $aCharacters.Add((fnRandom),$sLCase[(fnRandom) % $sLCase.Count])
    }
    #Selects Upper Case Characters
    for($i =0;$i -lt $iUCaseNeeded;$i++){
        $aCharacters.Add((fnRandom),$sUCase[(fnRandom) % $sUCase.Count])
    }
    #Selects Numerical Characters
    for($i =0;$i -lt $iNumNeeded;$i++){
        $aCharacters.Add((fnRandom),$sNum[(fnRandom) % $sNum.Count])
    }
    #Selects Special Characters
    for($i =0;$i -lt $iSpecNeeded;$i++){
        $aCharacters.Add((fnRandom),$sSpec[(fnRandom) % $sSpec.Count])
    }
    #Selects from all characters until password length is met
    for($i=$aCharacters.Count;$i -lt $PassLength;$i++){
        $aCharacters.Add((fnRandom),$sFullSet[(fnRandom) % $sFullSet.Count])
    }
    #Sorts Characters
    $aCharacters = $aCharacters.GetEnumerator() | Sort-Object Name
    return New-Object System.String(($aCharacters | % {$_.value}),0,$aCharacters.Length)
}

The function above calls the fnRandom function which is what actually creates the random characters. fnRandom is below.

#==================================================================================
# Returns random number
#==================================================================================
function fnRandom{
    #Crypto Random Number Generator
    $rng = New-Object System.Security.Cryptography.RNGCryptoServiceProvider

    #Byte Storage
    [byte[]]$byte = New-Object byte[] 4

    #Generate Random Value
    $rng.GetBytes($byte)

    #Convert to Integer
    return [system.bitconverter]::touint32($byte,0)
}

After loading both functions into memory using PowerShell ISE, you can then call fnGeneratePassword without any parameters to see some of the sample random passwords as shown below.

The keen observer may have noticed that I commented out the fnLog function in the examples above. Both functions above are a part of a larger local administrator password management solution which I will reveal in greater detail in the 3rd part of this series. What you have so far is a configurable random password generator which can be integrated into pretty much any random password script, and a cryptographically random character generator which can also be used for many different purposes.

The upcoming parts in this series will explain how to do the following:

  • Connect to Active Directory using a Kerberos secured connection
  • Set the local administrator password based on fnGeneratePassword
  • Write a log file that logs the success and failure of each function
  • Create a confidential attribute to store the local admin password
  • Create fnMain to control the order in which all of the functions are called
  • Create a XAML based secure password viewer to retrieve the local admin password

Each portion of the solution is modularized using functions which allows the IT administrator to make use of all or just parts of the solution and allows the IT administrator to easily integrate any portion they wish into a larger script or even a different solution entirely. So stay tuned as Part 3 discusses how to create a Kerberos encrypted and signed connection to Active Directory.