密码策略
密码策略是一组关于如何生成密码的指令,类似于密码生成器。这些密码策略被用于一部分的机密引擎中,以允许我们配置该引擎应该如何生成密码。并非所有机密引擎都使用密码策略,因此在使用引擎前请先阅读相关文档以了解兼容性。
请注意,密码策略与前章所叙述的策略并无关系,只是名字中都有策略二字。
密码策略是从 Vault 1.5.0 开始引入的,可以阅读相关 API 文档。
注意,密码策略是 Vault 的一项高级功能,为外部系统(例如数据库、LDAP、AWS 等)设置生成规则时需要小心使用。
设计
密码策略基本上由两部分组成:密码长度,以及密码必须遵守的一组规则。密码是从一个由所有规则各自生成的字符集合,去重后得到的并集中随机生成,然后根据每个规则检查以确定候选密码是否符合策略约束。
规则是对候选密码字符串的断言,判断密码是否可接受。例如:“charset”规则规定密码中必须至少包含一个小写字母。此规则将拒绝任何不包含任何小写字母的密码。
可以在一个策略中指定多个规则以创建更复杂的规则,例如要求至少一个小写字母、至少一个大写字母和至少一个数字。
该过程的状态机如下:
生成候选密码
如何生成候选密码是非常重要的,必须非常小心,以确保创建密码的过程不会被恶意利用。本节介绍我们如何在密码策略中生成密码,以确保尽可能安全地生成密码。
要生成一个候选密码,我们需要:
- 一个加密算法意义上安全的随机数生成器( cryptographically secure random number generator,RNG)
- 一个候选字符集
- 密码长度
大致说来,我们使用 RNG 生成 N 个数字,这些数字对应于字符集数组中的索引,其中 N 是我们希望创建的密码的长度。然后使用从 RNG 返回的每个值从字符集中提取一个字符到密码中。
举个例子,让我们从字符集 abcdefghij
生成长度为 8 的密码:
我们使用 RNG 生成 8 个随机数,举例来说我们得到的是:
[3, 2, 0, 8, 7, 3, 5, 1]
将每个随机数映射成字符集上对应的字符:
[3, 2, 0, 8, 7, 3, 5, 1]
=> [d, c, a, i, h, d, f, b]
我们得到的候选密码就是:dcaihdfb
,然后可以用来判断它是否符合密码策略定义的规则限制。
在实际场景中,随机数组中的值将介于 [0-255]
之间,因为这是单个字节可以取值的范围范围。通过使用模运算来防止引用字符集边界之外的字符,该值被限制为不超过字符集数组的大小。然而,这可能会引入偏差(Bias)问题。
防止偏差
使用模运算生成密码时,你必须非常小心地防止引入偏差。当使用长度不能被 256 整除的字符集生成 [0-255]
之间的随机数时,某些字符被选中的概率可能会比其他字符要高。
为了演示这个问题,让我们简化一下数学。假设我们有一个长度为 10 的字符集:abcdefghij
。让我们假设 RNG 生成值的范围是 [0-25]
。前 10 个值 0-9
对应于我们字符集中的每个字符。接下来的 10 个值 10-19
也对应于我们字符集中的每个字符。但是,接下来的 6 个值 20-25
仅对应于字符集中的前 6 个字符。这意味着前 6 个字符 abcdef
可能出现的比比最后 4 个字符 ghij
更加频繁。
为了防止这种情况发生,我们计算了一个索引可以达到的最大值。这个值是基于我们从中选择的字符集的长度计算的。在上面的例子中,我们应该允许的最大索引值是 19,因为它表示字符集数组长度的最大整数倍,它小于我们的 RNG 可以生成的最大值。当我们的 RNG 生成任何大于我们最大允许值的值时,该数字将被忽略,我们继续下一个数字。密码不会丢失任何长度,因为我们会继续生成数字,直到密码完全填写到要求的长度为止。
性能特点
使用此模型生成密码的性能在很大程度上取决于配置的策略。简而言之,策略越严格,生成密码所需的时间就越长。这种概括并不总是正确的,但一般来说如此。性能曲线可以概括为:
(生成一个候选密码的时间) * (生成的候选密码数量)
这里面生成的候选密码数量是一个用来评估一个候选密码有多大的可能性无法通过所有策略约束的函数确定的。
下面是一些样例策略及其性能特征。每一个策略都具有相同的用以生成候选密码的字符集(94 个字符)。唯一的区别是各种字符子集中所需要选取的最小字符数。
- 无最小字符数限制:
rule "charset" {
charset = "abcdefghijklmnopqrstuvwxyz"
}
rule "charset" {
charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
}
rule "charset" {
charset = "0123456789"
}
rule "charset" {
charset = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
}
- 一个大写字母,一个小写字母,一个数字:
rule "charset" {
charset = "abcdefghijklmnopqrstuvwxyz"
min-chars = 1
}
rule "charset" {
charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
min-chars = 1
}
rule "charset" {
charset = "0123456789"
min-chars = 1
}
rule "charset" {
charset = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
}
- 一个大写字母,一个小写字母,一个数字,一个符号:
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
}
- 一个大写字母,一个小写字母,一个数字,
!@#$
中选择一个字符:
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 = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
}
随着生成的字符越多,时间越长(如无最少字符中所示)。通过限制字符集可以使这种上升趋势忽略不计。当密码较短时,从字符集中选择特定字符的机会比较小。例如,如果要从字符集 abcde
生成 1 个字符的密码,从中选择 c
的机会是 1/5。但是,如果要生成 2 个字符的密码,则至少选择 c 一次的机会大于 1/5
,因为 c
两次机会被选择。
在这些示例中,随着密码长度的增加,生成密码的时间量呈下降趋势,趋于平稳,然后缓慢增加。这是上面列出的两种因素的组合:生成更多字符所增加的时间与某个子集中的字符被选中的概率。当某个子集非常小(例如 !@#$
)时,它被选中的机会(4/94)比子集较大时(小写字符为 26/94
)要小得多。这可能会导致性能急剧下降(因为更有可能因为缺乏某个子集字符而导致密码不符合规则要求)。
在上面的示例中,用于生成候选密码的字符集长度为 94 个字符。从 94 个字符的字符集中随机选择一个给定的字符有 1/94
的机会。在 N 次尝试后从中选择指定字符(其中 N 是密码的长度)的概率是 1-(1-1/94)^N
。
如果我们将其扩展为一个字符集的子集(例如小写字符),则从该子集中选择字符的机会为 1-(1-L/94)^N
,其中 L
是子集的长度。选中小写字符的概率是 1-(1-26/94)^N
。
如果我们使用大小写字母加数字,那么我们会得到一个组合概率曲线:
p = (1-(1-26/94)^N) * (1-(1-26/94)^N) * (1-(1-10/94)^N)
需要注意的是,该概率曲线仅适用于该特定策略。要了解给定策略的性能特征,应该使用 generate
端点运行该策略,以查看策略生成密码所需的时间。
密码策略语法
密码策略使用 HCL 或 JSON 定义,它定义了密码的长度和密码必须遵守的一组规则。
这里有一个简单的生成20位小写字母构成的密码的规则:
length = 20
rule "charset" {
charset = "abcdefghijklmnopqrstuvwxyz"
}
可以指定多条规则,包括多个相同类型的规则。例如,以下策略将生成一个 20 个字符的密码,其中包含至少一个小写字母、至少一个大写字母、至少一个数字和至少一个来自集合 !@#$%^&*
的符号:
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
}
必须指定至少一个字符集才能使策略有效。为了生成密码,字符集必须可用于从中选择字符,密码策略不提供默认字符集。以下策略是无效的,将被拒绝:
length = 20
配置文件 & 可用策略
length
参数
length``(int:<required>)
——指定要生成的密码长度。必须 >= 4。
长度并不是一条规则。它是配置中唯一不用遵循“猜测——检查是否符合策略规范”流程的部分。
规则 charset
允许指定从给定的字符集中至少出现的字符数。例如:密码必须至少有一个小写字母。此规则提供了构建密码生成所使用的字符集。为了生成密码,必须指定字符集。
如果指定了多个字符集,则在生成任何候选密码之前,所有字符集都将被组合和去重。一个成功生成密码需要符合每个单独的字符集规则的规定。
需要注意的是,在对字符集进行组合和去重之后,生成候选密码的字符集长度不得超过 256 个字符。
参数
charset``(string:<required>)
——此规则遵守的字符集的字符串表示形式。接受兼容 UTF-8 编码的字符串。字符串中的所有字符都必须是可打印的。 请注意,根据 JSON 规范,返回的 JSON 输出可能会为特殊和控制字符(例如 <、>、& 等)进行转义。min-chars``(int:0)
——指定此规则中指定的字符集所需被选中的最少字符数。例如:如果min-chars = 2
,则密码必须至少有 2 个源自charset
的字符。
例子
length = 20
rule "charset" {
charset = "abcde"
min-chars = 1
}
rule "charset" {
charset = "01234"
min-chars = 1
}
此策略将从字符集 abcde01234
生成密码。但是,密码必须至少有一个来自 abcde
的字符和至少一个来自 01234
的字符。如果字符集在规则之间重叠,字符集将被删除重复以防止重叠部分的字符更容易被选中。例如:如果您有两个字符集规则:abcde
& cdefg
,则字符集 abcdefg
将用于生成候选密码,但 abcde
& cdefg
中的各自有至少一个字符必须出现在密码中。
如果未指定 min-chars
(或设置为 0
),则此字符集将没有所需的最小字符数,但将用于从中选择字符。例子:
length = 8
rule "charset" {
charset = "abcde"
}
rule "charset" {
charset = "01234"
min-chars = 1
}
此策略从字符集 abcde01234
生成 8 个字符的密码,并要求其中至少包含 01234
中的一个字符,但不强制要求 abcde
中的任何字符。此策略可能生成密码 04031945
,即使其中没有字母。