PowerShell: solid password generator

I needed a PowerShell script to generate a random alpha-numeric string. Here were my goals:

  1. The randomness needs to be cryptographically strong.
  2. Having at least some characters of different types is not a concern here.
  3. Small-ish, readable code is a concern.

Here’s what I came up with:

# Generate an random code or password
$code = ""
$codeLength = 26
$allowedChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
$rng = new-object System.Security.Cryptography.RNGCryptoServiceProvider
$randomBytes = new-object "System.Byte[]" 1
# keep unbiased by making sure input range divides evenly by output range
$inputRange = $allowedChars.Length * [Math]::Floor(256 / $allowedChars.Length)
while($code.Length -lt $codeLength) {
    $rng.GetBytes($randomBytes)
    $byte = $randomBytes[0]
    if($byte -lt $inputRange) { # throw away out-of-range inputs
        $code += $allowedChars[$byte % $allowedChars.Length]
    }
}

# Here's the code/password:
$code

I realize this script throws away more bits than necessary, but improving that while keeping $allowedChars flexible would add quite a few lines, and wasn’t worth the sacrifice to requirement #3. Thanks Dan Jenkins for helping me fix this with only one additional line of code!

I think it’s solid, but am very open to feedback.

This entry was posted in PowerShell, Programming. Bookmark the permalink.
  • deiussum

    Instead of throwing away bytes that fall outside the length of the allowed characters, why not use the modulus operator? Something like:

    $byte = $randomBytes[0] % $allowedChars.Length
    $code += $allowedChars[$byte]

    • Hi deiussum – thanks for reviewing what I have here. It turns out the modulus operator almost always creates a bias towards the lower range, unless you add additional code to compensate for it.

      If we use the code you’ve suggested as-is, here’s the situation:

      $byte has 256 possible values (0-255).
      $allowedChars has 62 possible values.
      Bias happens since 62 doesn’t go evenly into 256 because. For instance:

      ‘A’ has a 5-in-256 chance of being picked: $byte = 0, 62, 124, 186, and 248.
      ‘9’ has a 4-in-256 chance of being picked: $byte = 61, 123, 185, and 247.

      So to eliminate bias I think you always have to throw away some bytes. But now that I type this all out, I think the script could easily be modified to throw away fewer bytes by first calculating how many whole sets of 62 fit into 256, and then throwing away everything >= 248 instead of >=62. (I’d like to keep the script working well if the user adds or removes $allowedChars.)

      Does the bias thing make sense as I’ve described it?

      • Dan Jenkins

        Yep. That makes sense. Most applications that extra 1/256 (0.39%) chance of the lower bounds being selected is not significant enough to matter much. As you stated, though, instead of throwing away everything > $allowedChars.Length you could reduce the number of characters thrown away by instead throwing away everything greater than Floor(256 / $allowedChars.Length).

        • Excellent – I’ve updated the code snippet per your recommendation – it was quite a bit more straightforward than I had originally thought it would be. Thanks!

          Of course you’re right that the bias is trivial. In fact my first version did modulus on a random unsigned long, and so the bias was like 1 x 10^-18 or something… but it still seemed odd to take the time to use RNGCryptoServiceProvider rather than just Random, and then add any bias back. If you start with RNGCryptoServiceProvider, might as well make it Solid all the way down, I guess.

          Thanks again for helping me improve it. Anything else you see, now that we minimize thrown-away bits? There’s definitely some places where an off-by-one error may be lurking. 🙂

          • Dan Jenkins

            I’m not seeing anything else. 🙂

            I think another benefit of the RNGCryptServiceProvider isn’t just the lack of a bias, but the fact that it is truly more random and harder to predict what the next number to be picked will be.

            One thought is that if this is a code/password that is meant to be manually entered in by a user, it could be nice to eliminate somewhat ambiguous characters like 0 and O or I, l and 1 from the selection. You’ve set it up in a very nice way to do that by just removing them from the allowed characters string. It results in fewer possible combinations, but improves the experience for an end-user. 🙂 If it is just sent in a way that it can be copy-pasted, or used in a link, it probably isn’t a concern, though.