sethvargo / go-password

A Golang library for generating high-entropy random passwords similar to 1Password or LastPass.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Concerns Regarding Entropy in Password Generation with Character Restrictions

Izzette opened this issue · comments

The algorithm intends to produce high-entropy passwords, which suggests a high level of unpredictability and security. However, the restriction that disallows repeated characters and the requirement for a fixed number of digits and special characters may inadvertently reduce the entropy of the generated passwords.

Entropy, in the context of password security, is a measure of unpredictability or randomness. It is often quantified in bits and calculated based on the logarithm of the number of possible outcomes. For a password with n possible characters and a length of m, the total number of possible passwords without restrictions is n^m.

When we disallow repeated characters, we reduce the number of possible outcomes for each subsequent character. For example, if a password must be 8 characters long and we use a set of 62 possible characters (26 lowercase, 26 uppercase, 10 digits), the number of possible passwords without repetition is 62 * 61 * 61 * ... * 61, which is significantly less than 62^8.

Similarly, by fixing the number of digits and special characters, we further reduce the number of possible combinations. If a password must contain exactly 2 digits and 2 special characters, the positions and choices for these characters are limited, which reduces the overall entropy compared to a scenario where any character could occupy any position independently of their presence in other positions.

Mandating 2 digits in an 8-character alphanumeric password means that out of the 8 characters, exactly 2 must be digits (0-9) and the remaining 6 must be alphabetic characters (A-Z, a-z). This constraint reduces the number of possible combinations compared to a situation where all 8 characters could be freely chosen from the 62 possible alphanumeric characters (26 lowercase + 26 uppercase + 10 digits).

Let's calculate the number of possible passwords for both the constrained and unconstrained cases.

Unconstrained Case:
For each of the 8 characters, you can choose from 62 possibilities. Therefore, the total number of possible passwords is:
62^8

Constrained Case:
First, we need to choose the positions of the 2 digits in the 8-character password. There are C(8,2) ways to do this, where C(n,k) is the binomial coefficient representing the number of combinations of n items taken k at a time.

Once the positions for the digits are chosen, there are 10 possibilities for each digit. For the 6 alphabetic characters, there are 52 possibilities for each (26 lowercase + 26 uppercase). Therefore, the total number of possible passwords under the constraint is:
C(8,2) * 10^2 * 52^6

To calculate C(8,2), you can use the formula for the binomial coefficient:
C(n,k) = n! / (k!(n-k)!)

So, C(8,2) = 8! / (2! * (8-2)!) = 28

Thus, the total number of possible passwords with the constraint is:
28 * 10^2 * 52^6.

Suggestions

While it may seem counterintuitive, allowing characters to repeat increases the number of possible password combinations and thus the entropy. Obviously the user has the ability to specify this, but it is a rather detrimental option. The only justification I can think of is to comply with password requirements meant for human users. However, ignoring or removing the option may not be reasonable at this point.

Concatenation all characters from the character set(s) together and randomly selecting them one-by-one to produce the required password length could be a lot simpler and more secure. This could potentially be accomplished in a backwards-compatible way by interpreting negative numDigits and numSpecial as expecting this behavior.

However, the restriction that disallows repeated characters and the requirement for a fixed number of digits and special characters may inadvertently reduce the entropy of the generated passwords.

This isn't a "restriction", it's a user-specified configuration based on the use case. If your use case requires extremely high entropy or a larger character set (technically emojis and any unicode character could be valid), then you can customize this configurable.

I'm not really sure what change you're suggesting here. Users can allow characters to repeat. I'm not taking away that configuration option.

Finally, if you really want the highest entropy with no constraints, just read cryto.Rand into a []byte that's allocated with your preset length and encode the result as hex or base64:

buf := make([]byte, length)
n, err := rand.Read(buf) // crypto/rand
if err != nil {
  panic(err)
}
if n < length {
  panic(fmt.Sprintf("insufficient bytes read: %v, expected %v", n, length))
}
return buf, nil

This library (and it's sister library go-diceware) are intentionally low-level and configurable libraries.