Password policies
A password policy is a set of instructions on how to generate a password, similar to other password generators. These password policies are used in a subset of secret engines to allow you to configure how a password is generated for that engine. Not all secret engines utilize password policies, so check the documentation for the engine you are using for compatibility.
Note: Password policies are unrelated to Policies other than sharing similar names.
The Password policies API docs can be found here.
Password policies are an advanced usage of OpenBao. This generates credentials for external systems (databases, LDAP, etc.) and should be used with caution.
Design
Password policies fundamentally have two parts: a length, and a set of rules that a password must adhere to. Passwords are randomly generated from the de-duplicated union of charsets found in all rules and then checked against each of the rules to determine if the candidate password is valid according to the policy. See Candidate Password Generation for details on how passwords are generated prior to being checked against the rule set.
A rule is an assertion upon a candidate password string that indicates whether or not the password is acceptable. For example: a "charset" rule states that a password must have at least one lowercase letter in it. This rule will reject any passwords that do not have any lowercase letters in it.
Multiple rules may be specified within a policy to create more complex rules, such as requiring at least one lowercase letter, at least one uppercase letter, and at least one number.
The flow looks like:
Candidate password generation
How a candidate password is generated is extremely important. Great care must be placed to ensure that passwords aren't created in a way that can be exploited by threat actors. This section describes how we generate passwords within password policies to ensure that passwords are generated as securely as possible.
To generate a candidate password, three things are needed:
- A cryptographically secure random number generator (RNG).
- A character set (charset) to select characters from.
- The length of the password.
At a high level, we use our RNG to generate N numbers that correspond to indices into the charset array where N is the length of the password we wish to create. Each value returned from the RNG is then used to extract a character from the charset into the password.
For example, let's generate a password of length 8 from the charset abcdefghij
:
The RNG is used to generate 8 random values. For our example let's say those values are:
[3, 2, 0, 8, 7, 3, 5, 1]
Each of these values is an index into the charset array:
[3, 2, 0, 8, 7, 3, 5, 1]
=> [d, c, a, i, h, d, f, b]
This gives us our candidate password: dcaihdfb
which can then be run through the rules of the policy.
In a real world scenario, the values in the random array will be between [0-255]
as that is the range of
values that a single byte can be. The value is restricted to the size of the charset array by using the
modulo operation to prevent referencing a character
outside the bounds of the charset. However this can introduce a problem with bias.
Preventing bias
When using the modulo operation to generate a password, you must be very careful to prevent the introduction of bias. When generating a random number between [0-255] for a charset that has a length that isn't evenly divisible into 256, some of the first characters in the charset may be selected more frequently than the remaining characters.
To demonstrate this, let's simplify the math. Assume that we have a charset of length 10: abcdefghij
.
Let's also assume that our RNG generates values [0-25]
. The first 10 values 0-9
correspond to each
character in our charset. The next 10 values 10-19
also correspond to each character in our charset.
However, the next 6 values 20-25
correspond to only the first 6 characters in the charset. This means
that those 6 characters abcdef
can be selected more often than the last 4 characters ghij
.
In order to prevent this from happening, we calculate the maximum value that we can allow an index to be. This is based on the length of the charset we are selecting from. In the example above, the maximum index value we should allow is 19 as that represents the largest integer multiple of the length of the charset array that is less than the maximum value that our RNG can generate. When our RNG generates any values larger than our maximum allowed value, that number is ignored and we continue to the next number. Passwords do not lose any length because we continue generating numbers until the password is fully filled in to the length requested.
Performance characteristics
Characterizing password generation performance with this model is heavily dependent on the policy configuration. In short, the more restrictive the policy, the longer it will take to generate a password. This generalization isn't always true, but is a general guideline. The performance curve can be generalized:
(time to generate a candidate password) * (number of candidate passwords generated)
Where the number of times a candidate password needs to be generated is a function of how likely a given candidate password does not pass all of the rules.
Here are some example policy configurations with their performance characteristics below. Each of these policies have the same charset that candidate passwords are generated from (94 characters). The only difference is the minimum number of characters for various character subsets.
No Minimum Characters
rule "charset" {
charset = "abcdefghijklmnopqrstuvwxyz"
}
rule "charset" {
charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
}
rule "charset" {
charset = "0123456789"
}
rule "charset" {
charset = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
}
1 uppercase, 1 lowercase, 1 numeric
rule "charset" {
charset = "abcdefghijklmnopqrstuvwxyz"
min-chars = 1
}
rule "charset" {
charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
min-chars = 1
}
rule "charset" {
charset = "0123456789"
min-chars = 1
}
rule "charset" {
charset = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
}
1 uppercase, 1 lowercase, 1 numeric, 1 from all ASCII characters
rule "charset" {
charset = "abcdefghijklmnopqrstuvwxyz"
min-chars = 1
}
rule "charset" {
charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
min-chars = 1
}
rule "charset" {
charset = "0123456789"
min-chars = 1
}
rule "charset" {
charset = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
min-chars = 1
}
1 uppercase, 1 lowercase, 1 numeric, 1 from !@#$
rule "charset" {
charset = "abcdefghijklmnopqrstuvwxyz"
min-chars = 1
}
rule "charset" {
charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
min-chars = 1
}
rule "charset" {
charset = "0123456789"
min-chars = 1
}
rule "charset" {
charset = "!@#$"
min-chars = 1
}
# Fleshes out the rest of the symbols but doesn't add any required characters
rule "charset" {
charset = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
}
As more characters are generated, the amount of time increases (as seen in No Minimum Characters
).
This upward trend can be dwarfed by restricting charsets. When a password is short, the chances of a character
being selected from a subset is smaller. For instance, if you have a 1 character password from the charset
abcde
the chances of selecting c
from it is 1/5. However if you have a 2 character password, the chances
of selecting c
at least once are greater than 1/5 because you have a second chance to select c
from
the charset.
In these examples, as the length of the password increases, the amount of time to generate a password trends
down, levels off, and then slowly increases. This is a combination of the two effects listed above: increasing
time to generate more characters vs the chances of the character subsets being selected. When a single subset is
very small (such as !@#$
) the chances of it being selected are much smaller (4/94) than if the subset is larger
(26/94 for lowercase characters). This can result in a dramatic loss of performance.
Click here for more details on password generation probabilities
In the examples above, the charset being used to generate candidate passwords is 94 characters long.
Randomly choosing a given character from the 94 character charset has a 1/94 chance. Choosing a single
character from it after N tries (where N is the length of the password) is 1-(1-1/94)^N
.
If we expand this to look at a subset of characters (such as lowercase characters) the chances of selecting
a character from that subset is 1-(1-L/94)^N
where L
is the length of the subset. For lowercase
characters, we get a probability of 1-(1-26/94)^N
.
If we do this for uppercase letters as well as numbers, then we get a combined probability curve:
p = (1-(1-26/94)^N) * (1-(1-26/94)^N) * (1-(1-10/94)^N)
It should be noted that this probability curve only applies to this specific policy. To understand the
performance characteristics of a given policy, you should run your policy with the
generate
endpoint to see how much time the policy takes to
produce passwords.
Password policy syntax
Password policies are defined in HCL or JSON which defines the length of the password and a set of rules a password must adhere to.
See the API docs for examples of the commands to save/read/etc. password policies
Here is a very simple policy which generates 20 character passwords from lowercase characters:
length = 20
rule "charset" {
charset = "abcdefghijklmnopqrstuvwxyz"
}
Multiple rules may be specified, including multiple rules of the same type. For instance, the following
policy will generate a 20 character password with at least one lowercase letter, at least one uppercase
letter, at least one number, and at least one symbol from the set !@#$%^&*
:
length = 20
rule "charset" {
charset = "abcdefghijklmnopqrstuvwxyz"
min-chars = 1
}
rule "charset" {
charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
min-chars = 1
}
rule "charset" {
charset = "0123456789"
min-chars = 1
}
rule "charset" {
charset = "!@#$%^&*"
min-chars = 1
}
At least one charset must be specified for a policy to be valid. In order to generate a password, a charset must be available to select characters from and password policies do not have a default charset. The following policy is NOT valid and will be rejected:
length = 20
Configuration & available rules
length
parameter
length
(int: <required>)
- Specifies how long the generated password will be. Must be >= 4.
Length is not a rule. It is the only part of the configuration that does not adhere to the guess- and-check approach of rules.
Rule charset
Allows you to specify a minimum number of characters from a given charset. For instance: a password must have at least one lowercase letter. This rule also helps construct the charset that the password generation utilizes. In order to generate a password, a charset must be specified.
If multiple charsets are specified, all of the charsets will be combined and de-duplicated prior to
generating any candidate passwords. Each individual charset
rule will still need to be adhered to in
order to successfully generate passwords.
After combining and de-duplicating charsets, the length of the charset that candidate passwords are generated from must be no longer than 256 characters.
Parameters
charset
(string: <required>)
– A string representation of the character set that this rule observes. Accepts UTF-8 compatible strings. All characters within the string must be printable. Please note that the JSON output returned may be escaped for the special and control characters such as <,>,& etc as per the JSON specification.min-chars
(int: 0)
- Specifies a minimum number of characters required from the charset specified in this rule. For example: ifmin-chars = 2
, the password must have at least 2 characters fromcharset
.
Example
length = 20
rule "charset" {
charset = "abcde"
min-chars = 1
}
rule "charset" {
charset = "01234"
min-chars = 1
}
This policy will generate passwords from the charset abcde01234
. However, the password must have at
least one character that is from abcde
and at least one character from 01234
. If charsets overlap
between rules, the charsets will be de-duplicated to prevent bias towards the overlapping set.
For instance: if you have two charset rules: abcde
& cdefg
, the charset abcdefg
will be used to
generate candidate passwords, but a least one character from each abcde
& cdefg
must still appear
in the password.
If min-chars
is not specified (or set to 0
) then this charset will not have a minimum required number
of characters, but it will be used to select characters from. Example:
length = 8
rule "charset" {
charset = "abcde"
}
rule "charset" {
charset = "01234"
min-chars = 1
}
This policy generates 8 character passwords from the charset abcde01234
and requires at least one
character from 01234
to be in it, but does not require any characters from abcde
. The password
04031945
may result from this policy, even though no alphabetical characters are in it.
Default password policy
OpenBao ships with a default password policy that applies to any password generated by OpenBao without an explicit policy assignment. The default policy requires passwords include:
- 20 characters total
- 1 uppercase character
- 1 lowercase character
- 1 number
- 1 special character
length = 20
rule "charset" {
charset = "abcdefghijklmnopqrstuvwxyz"
min-chars = 1
}
rule "charset" {
charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
min-chars = 1
}
rule "charset" {
charset = "0123456789"
min-chars = 1
}
rule "charset" {
charset = "-"
min-chars = 1
}