使用预定义或自定义字符(包括 unicode)简单、灵活和高效地生成直观指定熵的可能唯一标识符(`puid`,又名随机字符串)
项目描述
Pythonpuid
使用预定义或自定义字符(包括 Unicode)简单、灵活和高效地生成puid直观指定熵的可能唯一标识符(也称为随机字符串)。
from puid import Chars, Puid
rand_id = Puid(chars=Chars.ALPHA, total=1e5, risk=1e12)
rand_id.generate()
'nWwiLXwdKsTcr'
目录
概述
puid提供直观有效的随机 ID 生成。出于 的目的puid,随机 ID 被认为是在唯一性上下文中使用的随机字符串,也就是说,随机 ID 是一堆希望唯一的随机字符串。
随机字符串生成可以被认为是将一些随机熵源转换为随机性的字符串表示。因此,用于随机 ID 的通用随机字符串库应为以下三个关键方面提供用户规范:
-
熵源
什么来源的随机性正在被改变?
puid允许轻松指定用于源随机性的函数 -
身份证字符
ID 中使用了哪些字符?
puid提供 16 个预定义字符集,以及允许自定义字符,包括 Unicode -
ID随机性
ID 的结果“随机性”是什么?
puid允许对 ID 随机性进行直观、明确的说明
用法
使用创建随机 ID 生成器puid很简单:
from puid import Puid
rand_id = Puid()
rand_id.generate()
'a78gWq7N51paZ2Hx5qkoK3'
熵源
puid用作secrets.token_bytes默认熵源。该entropy_source选项可用于配置特定的熵源:
from puid import Puid
from random import getrandbits
prng_bytes = lambda n: bytearray(getrandbits(8) for _ in range(n))
prng_id = Puid(entropy_source=prng_bytes)
prng_id.generate()
'JcQTr8u7MATncImOjO0qOS'
ID 字符
默认情况下,puid使用RFC 4648文件系统和 URL 安全字符。该chars选项可用于指定 16 个预定义字符集或自定义字符中的任何一个,包括 Unicode:
from puid import Chars, Puid
hex_id = Puid(chars=Chars.HEX)
hex_id.generate()
'a4b130ba638fc7db5d87e064a21e6b46'
dingosky_id = Puid(chars='dingosky')
dingosky_id.generate()
'sdosigokdsdygooggogdggisndkogonksnkodnokosg'
unicode_id = Puid(chars='dîñgø$kyDÎÑGØßK¥')
unicode_id.()
'îGÎØÎÑî¥gK¥Ñ¥kîDîyøøØñÑØyd¥¥ØGØÑ$KߨgøÑ'
ID随机性
默认情况下,生成的 ID 具有 128 位熵。puid提供了一种简单、直观的方式来指定 ID 随机性,方法是声明total多个可能的 ID,并risk在多个 ID 中指定重复:
要生成多达1000 万个具有万亿分之一重复机会的随机 ID :
from puid import Chars, Puid
safe32_id = Puid(total=10e6, risk=1e15, chars=Chars.SAFE32)
safe32_id.generate()
'd7ntFnH4FngrqgdR3Dtt'
该bits选项可用于直接指定 ID 随机性的数量:
from puid import Chars, Puid
token = Puid(bits=256, chars=Chars.HEX_UPPER)
token.generate()
'5D241826F2A644E1B725DB1DD7E4BF742D9D0DC6D6A36F419046A02835A16B83'
安装
pip install puid-py
API
puid导出用于创建随机 ID 生成器的类Puid 。Puid可以选择将参数用于配置 ID 生成:
total: 潜在(即预期)ID 的总数risk: 重复totalID的风险bits: ID 熵位chars: 标识字符entropy_source(n: number) => bytearray:源熵形式的函数
笔记
- 所有配置字段都是可选的
total/risk必须一起设置total/risk并且bits不能同时设置chars必须是有效的自定义字符或预定义的字符entropy_source是形式的函数(n: number) => bytearray- 默认值
bits: 128chars:Chars.SAFE64entropy_source:secret.token_bytes
PuidInfo
Puid的__repr__函数提供有关生成器配置的信息:
bits: 身份熵bits_per_char: 每个 ID 字符的熵位chars:源字符entropy_source: 细绳module.functionere: 熵表示效率len: ID 字符串长度
例子:
from puid import Chars, Puid
rand_id = Puid(total=1e5, risk=1e14, chars=Chars.BASE32)
rand_id.generate()
'7XKJJKNZBF7GCMEX'
print(rand_id)
Puid: bits = 80.0, bits_per_char = 5.0, chars = BASE32 -> '234567ABCDEFGHIJKLMNOPQRSTUVWXYZ', len = 16, ere = 0.625, entropy_source = secrets.token_bytes
字符
有 16 个预定义的字符集:
| 姓名 | 人物 |
|---|---|
| Α | ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz |
| AlphaLower | abcdefghijklmnopqrstuvwxyz |
| AlphaUpper | ABCDEFGHIJKLMNOPQRSTUVWXYZ |
| 字母数字 | ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 |
| AalphanumLower | abcdefghijklmnopqrstuvwxyz0123456789 |
| AalphanumUpper | ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 |
| Base32 | ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 |
| Base32Hex | 0123456789abcdefghijklmnopqrstuv |
| base32HexUpper | 0123456789ABCDEFGHIJKLMNOPQRSTUV |
| 十进制 | 0123456789 |
| 十六进制 | 0123456789abcdef |
| HexUpper | 0123456789ABCDEF |
| 安全ASCII | !#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{|}~ |
| 安全32 | 2346789bdfghjmnpqrtBDFGHJLMNPQRT |
| Safe64 | ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_ |
| 象征 | !#$%&()*+,-./:;<=>?@[]^_{|}~ |
最多 256 个唯一字符的任何字符串都可用于puid生成。
动机
开发人员在从长期(例如,数据存储键)到短期(例如,网页上的 DOM ID)的应用程序中经常需要随机字符串。当然,这些 ID 是次要问题。没有人想多想它们,它们只是想易于生成。
但是开发人员应该考虑他们使用的随机字符串。随机 ID 的生成是一种设计选择,就像任何其他设计选择一样,该选择本质上应该是明确的,并且基于对做出此类选择的原因的熟悉。然而,对随机字符串库的粗略审查,以及许多应用程序中随机字符串的使用,会导致缺乏清晰性,这掩盖了仔细考虑。
什么是随机字符串?
尽管这似乎有一个明显的答案,但实际上有一个关键,经常被忽视的微妙之处:随机字符串本身并不是随机的。要理解这一点,我们需要了解与计算机相关的熵。
信息论中对熵的一个有点简单的陈述是:熵是对事件可能结果的不确定性的度量。鉴于计算机固有的以 2 为底的系统,这种不确定性自然会映射到一个比特单位(称为香农熵)。所以我们看到像“这个随机字符串有 128 位熵”这样的语句。但这里有微妙之处:
随机字符串没有熵
相反,随机字符串表示捕获的熵,即由其他过程产生的熵。例如,您不能查看十六进制字符串'18f6303a'并明确地说它具有 32 位熵。要了解原因,假设您运行以下代码片段并获取'18f6303a':
rand_id = lambda: '18f6303a' if random.random() < 0.5 else '1'
rand_id()
'18f6303a'
结果字符串的熵'18f6303a'为 1 位。而已; 1位。'1'与观察结果时相同的熵。在任何一种情况下,都有两个同样可能的结果,因此产生的熵是 1 位。清楚地了解这一点很重要:
熵是事件不确定性的度量,与不确定性的表示无关
在信息论中,您会说随机过程发出两个符号,18f6303a和1,结果同样可能是任何一个符号。因此,该过程中有 1 位熵。符号无关紧要。更有可能看到符号Tand F、 or0和1、甚至ONand OFF,但无论如何,该过程会产生1 位熵,并且用于表示该熵的符号不会影响熵本身。
熵源
随机字符串生成器需要外部熵源,并且通常使用系统资源来获取该熵。在 Python 中,这可能是,例如,secrets.token_bytes或random.getrandbits. 尽管如此,重要的是要认识到生成的随机字符串的属性取决于熵源的特征。随机字符串是否适合用作安全令牌取决于熵源的安全特性,而不取决于令牌的字符串表示形式。
身份证字符
如前所述,用于随机字符串的字符(符号)不能确定熵。但是,可用的唯一字符数确实如此。在每个字符的概率相等(使熵最大化)的假设下,很容易表明每个字符的熵是一个常数 log 2 (N),其中N是可用字符的数量。
ID随机性
字符串随机性由每个字符的熵乘以字符串中的字符数确定。这种随机性的质量与熵源的质量直接相关。随机性取决于可用字符的数量和字符串的长度。
最后我们可以说:随机字符串是捕获熵的字符表示。
独特性
的目标puid是使用随机字符串提供简单、直观的随机 ID 生成。如上所述,我们可以将随机字符串生成视为将系统熵转换为字符表示,而随机 ID 是使用此类随机字符串来表示唯一 ID。不过有一个问题;一个大收获:
随机字符串不会产生唯一 ID
回想一下,熵是衡量事件可能结果的不确定性。至关重要的是,每个事件的不确定性都独立于所有先前的事件。这意味着两个单独的事件可以产生相同的结果(即相同的 ID);否则这个过程不是随机的。当然,您可以将每个生成的随机字符串与所有先前的 ID 进行比较,从而实现唯一性。但是必须进行一些此类后处理以确保随机 ID 真正唯一。
然而,确定性唯一性检查会产生大量的处理开销,并且很少使用。相反,开发人员(有意地?)放宽了随机 ID 是真正的、确定性唯一的要求,其标准要低得多,即概率唯一性之一。我们“相信”随机生成的 ID 是唯一的,因为重复 ID 的机会非常低。
再一次,我们达到了微妙的程度。(而且我们认为随机字符串很容易!)随机生成的 ID 是唯一的“信任”实际上变成了熵,因为到目前为止已经讨论过它。我们没有将熵视为 ID生成中不确定性的度量,而是将熵视为没有两个 ID 相同的概率的度量。可以肯定的是,我们希望这个概率非常低,但对于随机字符串,它不能为零!需要明确的是,熵不是这样的度量。反正不是直接的。是的,熵越高,概率越低,但需要一些数学才能以适当的方式将两者关联起来。(别担心,puid为你处理这个数学)。
此外,ID 生成的可能唯一性总是在某些有限的上下文中。考虑数据存储的 ID。您不必关心生成的 ID 是否与遥远星系中另一家公司的另一个应用程序的另一个数据存储中使用的 ID 相同。您关心 ID 在您的应用程序上下文中(可能)是唯一的。
回顾一下,随机字符串生成不会产生唯一的 ID,而是可能是唯一的 ID(在某些上下文中)。这种微妙之处非常重要,它已经融入了puid. 它与版本 4 的命名完全不一致uuid。为什么?因为通过随机过程生成意味着 auuid 不能是唯一的。作为推论,它也不能是普遍的。如上所述,无论如何我们都不关心通用部分,但事实仍然存在, auuid不是uu。
ID随机性
那么“这些 ID 有 122 位熵”这句话究竟是什么意思呢?毕竟熵是一种不确定性的度量,我们担心我们的 ID 是唯一的,无论如何可能是唯一的。那么“122 位熵”对于 ID 的可能唯一性意味着什么?
首先,让我们弄清楚这不是什么意思。我们关心的是一组 ID 在特定上下文中的唯一性。这些ID中的任何一个的随机性都不是真正的问题。是的,我们可以说“给定 122 位熵”,每个 ID 出现的概率为 2 -122。是的,这肯定会使任何特定 ID 的出现变得罕见。但就 ID 的唯一性而言,仅仅“不足以”讲述整个故事。
在这里,我们又遇到了另一个微妙之处。事实证明,所提出的问题没有明确说明,即不够具体,无法回答。为了正确确定熵如何与 ID 的可能唯一性相关,我们需要指定在特定上下文中要生成多少个 ID。只有这样我们才能确定生成唯一 ID 的概率。所以我们的问题真的需要是:给定N位熵,T个随机 ID 中唯一性的概率是多少?
幸运的是,熵和唯一性概率之间存在数学相关性。这种相关性通常通过生日悖论来探索。为什么悖论?因为这种关系,当被认为是一些人的独特生日问题时,最初是相当令人惊讶的。但尽管如此,这种关系是存在的,它是众所周知的,并且puid会为我们处理数学问题。
在这一点上,我们现在可以注意到,与其说“这些 ID 有N位熵”,我们实际上想说“生成这些 ID 的T有重复的风险R ”。幸运的是,puid允许直接指定随机 ID 生成的语句。使用puid,您可以轻松指定“我想要T个具有重复风险R的随机 ID ”。puid将负责使用正确的熵来有效地生成 ID。
效率
生成随机 ID 的效率与 ID 本身的统计特性无关。但是谁不关心效率呢?不幸的是,似乎大多数随机字符串生成。
熵源
如前所述,随机 ID 生成基本上是将熵源转换为捕获熵的字符表示。但是源的熵和捕获的ID的熵不是一回事。
为了理解差异,我们将研究一个令人惊讶的非常常见的示例。考虑以下生成随机字符串的策略:使用k个字符的固定列表,使用随机统一整数i , 0 <= i < k,作为列表中的索引来选择字符。重复此n次,其中n是所需字符串的长度。在 Python 中,这可能看起来像:
from random import randint
import string
chars = string.ascii_lowercase
n_chars = len(chars)
common_id = lambda len: "".join([chars[randint(0,n_chars)] for _ in range(len)])
common_id(8)
# => 'wnplkyiz'
首先,考虑上面代码中使用的源熵量。Python 规范声明random.random()(randint取决于)生成 53 位精度。所以上面生成一个 8 个字符的 ID 会消耗 8 * 53 = 424 位的源熵。
其次,考虑 ID 捕获了多少熵。假设有 26 个字符,每个字符代表 log 2 (26) = 4.7 位熵。所以每个生成的 ID 代表 8 * 4.7 = 37.6 位熵。
嗯。这意味着 ID 熵与源熵之比为 37.6 / 424 = 0.09,或高达9%。这不是大多数开发人员会感到满意的效率。诚然,这是一个特别令人震惊的例子,但大多数随机 ID 生成都存在源熵使用效率低下的问题。
在不深入研究细节(见代码?)的情况下,puid采用各种方法来最大限度地利用源熵。作为比较,在使用小写字母字符生成随机 ID时puid使用了87.5%的源熵。对于计数等于 2 的幂的字符集,puid使用 100% 的源熵。
人物
如前所述,随机字符串的熵等于每个字符的熵乘以字符串的长度。使用该值可以轻松计算熵表示效率( ere)。我们可以定义ere为随机字符串熵与表示字符串所需的位数之比。例如,小写字母的每个字符的熵为 4.7,因此使用这些字符的长度为 8 的 ID 具有 37.6 位的熵。由于每个小写字符需要 1 个字节,这导致ere37.6 / 64 = 0.59,即 59%。非 ASCII 字符,当然,占用超过 1 个字节。puid使用 utf-8 字符编码来计算ere.
字符串的总熵是每个字符的熵乘以字符串长度的乘积,前提是最终字符串中的每个字符的概率相同。总是如此puid,其他随机字符串生成器通常也是如此。但是,有一个值得注意的例外:版本 4 的字符串表示 a uuid。正如RFC 4122 第 4.4 节中定义的那样,v4uuid总共使用 32 个十六进制字符和 4 个连字符。虽然每个十六进制字符可以表示 4 位熵,但 a 中的 6 位十六进制表示uuid实际上是固定的,因此只有32*4 - 6 = 122- 位的熵(不是 128 位)。4 个固定位置的连字符贡献零熵。所以一个36个字符uuid有ere一个122 / (36*8) = 0.40,或40%。与之相比,默认puid生成器具有稍高的熵(132 位),但产生的ere0.75 或75%。谁不喜欢效率?
矫枉过正和指定不足
矫枉过正
随机字符串生成受到过度杀伤和指定用法的困扰。uuid考虑一下s 作为随机字符串的频繁使用。合理的似乎是重复的概率uuid很低。是的,诚然它很低,但这是uuid不加考虑就使用 a 的充分理由吗?例如,假设 auuid被用作数据存储中的键,该数据存储最多包含一千个项目。uuid在这种情况下重复的概率是多少?它是十亿分之一。那是 10^30,或 1 后跟 30 个零,或宇宙中估计的恒星数量的百万倍。真的吗?是不是显得有些矫枉过正?你真的需要那种程度的保证吗?如果是这样,为什么要停在那里?为什么不将两个uuids 连接起来,得到一个更可笑的“
或者为什么不更合理一点,想一想这个问题。假设您接受 10^15 分之一的重复风险。这仍然是一个非常低的风险。啊,但是等等,你不能使用 a uuid,因为uuid生成不灵活。字符是固定的,表示是固定的,熵的位是固定的。
您可以通过确定所需的 ID 熵的实际数量(它是 68.76 位)、选择一些字符集、计算给定这些字符所需的字符串长度来生成 ID,最后生成前面通用 ID 生成方案中概述的 ID。
唷,这也许是开发人员倾向于使用 uuid 的另一个原因。这似乎是一个很大的努力。
啊,但还有另一种方法。您可以很容易地使用puid来生成这样的 ID:
from puid import Puid
db_id = Puid(total=1000, risk=1e15)
db_id.generate()
'tcDPzTAjoRcU'
生成的 ID 有 72 位熵。但猜猜怎么了?你不在乎。您所关心的是明确声明您希望拥有 1000 个 ID,并且您的重复风险水平是千万亿分之一。它就在代码中。另外,ID 长度只有 12 个字符,而不是 36 个字符。谁不喜欢轻松、控制和效率?
在指定下
生成随机字符串的方案中另一个令人头疼的问题是使用显式声明字符串长度的 API。为什么这令人不安?因为该声明没有指定所需随机性的实际数量,无论是需要的还是实现的。假设您的任务是维护使用由数字和小写字母组成的 15 个字符的随机 ID 的代码。为什么 ID 有 15 个字符长?没有代码注释,你一无所知。在不知道预期有多少 ID 的情况下,您无法确定重复的风险,即,您甚至无法说明随机 ID 的实际随机性!选择 15 是有原因的,还是仅仅因为它使 ID 看起来不错?
现在,假设您的任务是维护此代码:
from puid import Chars, Puid
rand_id = Puid(total=500000, risk=1e12, chars=Chars.ALPHANUM_LOWER)
嗯。看起来预计会有 500,000 个 ID,重复风险是万亿分之一。没有猜测。代码是明确的。哦,顺便说一下,ID 有 15 个字符长。但谁在乎?重要的是 ID 随机性,而不是长度。
效率
Puid为随机 ID 生成采用了多种效率:
puid仅从熵源中获取生成下一个所需的字节数- 每个
puid字符都是通过对可能的最小熵位进行切片来生成的 - 任何剩余的位都被结转并用于生成下一个
puid - 所有角色都同样有可能最大化捕获的熵
- 最终 ID 中仅存在表示熵的字符
- 轻松指定
total/risk可确保 ID 仅在实际需要时长
tl;博士
puid是一个简单、灵活、高效的随机 ID 生成器:
-
舒适
一行代码中指定的随机 ID 生成器
-
灵活的
完全控制熵源、ID 字符和 ID 随机性数量
-
安全的
默认为安全的熵源和至少 128 位的 ID 熵
-
高效的
最大利用系统熵
-
袖珍的
ID 字符串表示所用字符的最大熵
-
显式
ID生成的明确规范
from puid import Chars, Puid
Puid(chars=Chars.SAFE32, total=10e6, risk=1e15)
rand_id.generate()
'RHR3DtnP9B3J748NdR87'
项目详情
下载文件
下载适用于您平台的文件。如果您不确定要选择哪个,请了解有关安装包的更多信息。