随机字符串生成器

按你指定的字符集和长度批量生成字符串,同时把这件事真正在意的两个数算出来:单条信息量是多少 bits,以及这一批里出现重复的概率有多大。

字符集(决定取值空间,直接影响熵)

已按默认条件生成 10 条 16 位字符串。字符池 62 位,单条熵 95.3 bits。

aG7kQm2xLp9VtR4s
Zk3nWc8yDf1UbH6j
Tq5Yh0mXe7Ra2WgP
Pv9Js4nKc6Zt3YbQ
Lx2Fm8wQd5Rh7UkN
Nc4Tg1yVb9Xe2WkR
Qf7Zh3mSa6Ln0PdY
Bt5Wq9xKg2Rf8McV
Zd1Ky6nHp4Wt7LgS
Gm8Tb3xQa5Nd9PrC
(以上是页面打开时的演示结果,仅作排版示例,不是可用于任何真实场景的凭据)

随机源:crypto.getRandomValues(new Uint32Array(n));单条熵按 长度 × log2(字符池大小) 计算。

使用方法

  1. 在「字符串长度」填 16(示例值,页面允许 1~256 的整数),在「生成条数」填 10(示例值,允许 1~500)。
  2. 勾选需要的字符集。默认勾了小写字母、大写字母、数字三组共 62 个字符,并勾上「排除易混淆」,把 0 O o 1 I l 这 6 个从池里剔掉,所以实际池 = 25 + 24 + 8 = 57 位。
  3. 需要特定字符时把「十六进制 0-9a-f」或「常用符号」勾上;也可以在「自定义字符」里直接填任意字符,包括中文和 emoji,最多 200 个,会原样拼进池里并参与去重。
  4. 点「生成字符串」,或在长度、数量输入框里直接按 Enter。结果区出现 10 条字符串,上方统计条同步显示字符池大小、单条熵、取值空间、批量碰撞概率和随机源状态。
  5. 点「复制结果」一次带走全部,或点「导出 CSV」拿到 12 行「长度 → 熵 / 空间 / 碰撞概率」对照表,用来决定你的场景到底需要多长。

这个随机字符串生成器能解决什么问题

一是给测试数据批量灌值。写单元测试要造 200 个不重复的用户名、订单号、token 占位符,手写既慢又容易撞车。随机字符串生成器一次给出几百条,字符集和长度都可控,勾掉符号就能避开数据库字段的转义问题。

二是回答「这串东西够不够随机」这个具体问题。这是本页和普通随机串工具最大的区别:页面顶部实时显示单条熵(bits)、取值空间和批量碰撞概率三个数。长度 16、池 62 时单条熵 95.3 bits;但如果你把字符集缩小成纯数字(池 10),同样 16 位只有 53.2 bits——长度没变,安全性差了一倍多,因为熵是长度和池大小的乘积效应。

三是算清「批量去重有没有必要」。生日问题告诉我们:池 62、长度 16 时生成 1000 条,出现至少一次重复的概率低到可以忽略;但如果池缩到 10、长度缩到 6,生成 1000 条的碰撞概率是 39.32%——接近一半会重复,这时候必须开去重。页面把这个概率直接算给你看,不用凭感觉。

四是避开取模偏差这个隐蔽陷阱。用 Math.random() % 62 取字符时,232 除不尽 62,末尾几个字符会被抽到得更多。本页用 crypto.getRandomValues 配合拒绝采样消除这个偏差,具体阈值和丢弃比例在下面给出。

本页不做这些事:不生成密码(不评估强度、不检查泄露库、不做组成规则校验)、不生成 UUID/GUID 等有特定格式的标识符、不做中文姓名或词语拼装。要生成强度达标的口令请用「密码生成器」;要标准 UUID 格式请用「UUID生成器」;只要纯数字随机数请用「随机数生成器」。

原理和变量:随机源、拒绝采样与熵

第一,随机源。本页只用一行代码取随机数:crypto.getRandomValues(new Uint32Array(n))。它是浏览器内置的加密随机数接口(W3C Web Cryptography API Level 1 定义),底层取自操作系统的 CSPRNG(Windows 上是 CNG,Linux 上是 getrandom(2) 系统调用),不引入任何第三方随机库。

为什么不用 Math.random()?这是本页和「密码生成器」共同的底层选择,但理由值得说清:Math.random() 在 V8 里由 xorshift128+ 实现,内部状态只有 128 bit,而且整个页面生命周期内状态是连续的——攻击者只要观测到足够多的连续输出(理论上 3 个 32 位输出就能反推 128 位状态),就能预测之后所有的随机值。crypto.getRandomValues 每次调用都从操作系统熵池重新取数,状态不可反推,这是「不可预测」的硬保证。

第二,模偏差与拒绝采样。拿到一个 32 位无符号整数后,不能直接写 n % 62 去取字符下标。原因是 232 = 4294967296 不能被 62 整除:4294967296 ÷ 62 = 69273666 余 4,这意味着下标 0~3 对应的随机区间比 4~61 宽了 1 个取值,前 4 个字符会被多抽到。分布的偏斜极微小(相对偏差约 1.5×10-10),但它是真实存在的。

本页的做法是拒绝采样:先算接受上限 limit = Math.floor(4294967296 / 62) × 62 = 4294967292,随机数落在 4294967292~4294967295 这 4 个取值上就丢弃重抽。丢弃比例是 4 / 4294967296 = 9.313×10-10,接受率 99.999999907%——代价几乎为零,换来严格均匀的分布。各字符集的实测阈值:池 10 → 丢弃 6 个(1.397×10-9);池 26 → 丢弃 22 个(5.122×10-9);池 36 与 62 → 都丢弃 4 个;池 72 → 丢弃 40 个(9.313×10-9);池 2000 → 丢弃 1296 个(3.017×10-7)。而 池 16(十六进制)丢弃 0 个,因为 16 是 2 的幂、恰好整除 232——所有 2 的幂(2/4/8/16/32/64/128/256/512/1024/2048/4096/65536)都零丢弃,这是十六进制适合做短 ID 的技术原因之一。

第三,熵怎么算。单条熵的公式是香农熵的整数形式:H = log2(池大小 ^ 长度) = 长度 × log2(池大小) bits。每字符的熵只取决于字符集大小,与你勾了哪几组无关:池 10 是 3.3219 bits/字符,池 26 是 4.7004,池 36 是 5.1699,池 62 是 5.9542,池 72 是 6.1699,池 2000(常用汉字)是 10.9658,池 65536(整个基本平面)是 16.0000。所以从 62 扩到 72 只多 0.2157 bits/字符,而从 10 扩到 62 多了 2.6323 bits/字符——扩字符集的收益在池小的时候最大。

第四,批量碰撞(生日问题)。生成 n 条长度为 L 的字符串,任意两条相同的概率约等于 1 − exp(−n(n−1) / (2 × 池^L))。这是生日悖论:碰撞概率随 n平方增长,而不是线性。实测:池 62、长度 16 时空间 4.767×1028,生成 1000 条的碰撞概率 <0.0001%(期望重复对数 1.048×10-23);但池 10、长度 6 时空间只有 106,生成 1000 条碰撞概率高达 39.32%;池 16、长度 4 时更是 99.95%,几乎必然重复。

第五,Web Crypto 的配额硬上限。规范规定 getRandomValues 单次调用最多填充 65536 字节的视图,超过就抛 QuotaExceededError。用 Uint32Array 算就是单次最多 16384 个无符号整数。本页按需分批取:生成 1000 条 × 32 字符需要 32000 个随机数(128000 字节),必须分 2 次调用;5000 条 × 64 字符需要 320000 个,分 20 次。这也是本页把条数上限设为 500 的原因之一——再往上分批次数线性增加,用户等待体感明显变差。

完整示例(数值仅作演示)

示例条件与页面打开时的默认值完全一致:长度 16 位、条数 10 条、勾选小写字母 + 大写字母 + 数字、勾选「排除易混淆 0Oo1Il」。逐项实测如下:

结果区在页面打开时就填着上面这 10 条(服务端直出),你点一次「生成字符串」会得到完全不同的内容——随机工具的结果本来就该每次都不同,这 10 条只是用来确认字符集和长度设置对了、页面能正常工作的演示值,不要当凭据用。

输入范围与对照表

三个数值输入的范围都是刻意设的,不是随手填的:

输入项允许范围为什么
字符串长度1 ~ 256 的整数上限 256 是因为页面用 String.prototype.repeat 拼装,再长对绝大多数场景(ID、token、测试数据)没有意义;下限 1 是数学边界,0 位字符串没有信息量
生成条数1 ~ 500 的整数500 条 × 256 位 = 128000 个字符,需要分 8 次 getRandomValues;再多会明显卡顿,且 CSV 导出后 Excel 也吃不下
自定义字符0 ~ 200 个字符200 个足够表达「全部 ASCII 可见字符」这个最大常见需求;再多的自定义字符会让「字符集大小」这个统计失去可读性
字符集勾选至少 1 组一组都不勾且自定义为空时,池大小为 0,log2(0) 无意义、也无法取字符,页面直接拒绝

下表固定字符池 62 位(大小写字母 + 数字,不排除易混淆),逐档实测;「批量碰撞概率」是生成 1000 条时至少出现一次重复的概率:

行长度单条熵取值空间 62^长度每秒 1012 次穷举1000 条的碰撞概率
14 位23.8 bits1.478×107不到 1 毫秒3.3239%
26 位35.7 bits5.680×101056.8 毫秒0.0009%
38 位47.6 bits2.183×10143.6 分钟2.29×10-7%
410 位59.5 bits8.393×10179.7 天5.95×10-11%
512 位71.5 bits3.226×1021102.2 年1.11×10-14%
614 位83.4 bits1.240×102539.30 万年<0.0001%
716 位(默认)95.3 bits4.767×10281.51×109 年<0.0001%
818 位107.2 bits1.833×10325.81×1012 年<0.0001%
920 位119.1 bits7.044×10352.23×1016 年<0.0001%
1024 位142.9 bits1.041×10433.30×1023 年<0.0001%
1132 位190.5 bits2.273×10577.20×1037 年<0.0001%
1264 位381.1 bits5.165×101141.64×1095 年<0.0001%

第 1 行和第 7 行放在一起看最说明问题:长度 4 位时 1000 条里有 3.3% 的概率会撞车(6 位就掉到 0.0009%),而穷举时间从「不到 1 毫秒」变成「3.6 分钟」只用了 4 个字符。每多 1 位,熵增加 5.95 bit,取值空间乘以 62——所以「稍微长一点」的收益是指数级的。第 11、12 行的穷举年份已经超出宇宙年龄量级,说明再往上加长度对安全性边际收益为零,实际约束会落在存储和协议字段长度上(比如数据库 VARCHAR(32))。

边界与精度

常见问题

这个工具能生成安全的密码吗?

技术上它可以做到和密码生成器同等强度的随机性(同一个 crypto.getRandomValues 加拒绝采样),但我建议你用专门的密码生成器。差别在三个地方:一是组成规则,很多网站注册时要求「必须同时含大写、小写、数字、符号」,密码生成器会先按每类各抽一个再补足,天然满足校验;本页不强制,生成结果可能连续十几位都是小写字母而被退回。二是易混淆字符,本页的「排除易混淆」默认只剔 6 个字符,而密码场景通常剔更多。三是强度评估,本页给的是理论熵,不评估你输入的密码是否已在泄露库里。要评估一条现有密码,请用「密码力量检测」。

为什么池 16(十六进制)不需要拒绝采样?

因为 16 是 2 的幂,恰好整除 232:4294967296 ÷ 16 = 268435456 整除,余数为 0,所以没有任何取值落在「余数区间」里,无需丢弃。实测 232 % 16 = 0,丢弃 0 个取值,接受率 100%。所有 2 的幂都这样:2、4、8、16、32、64、128、256、512、1024、2048、4096、65536。这也是「十六进制适合做短 ID」的技术原因——同样的字符数下它不浪费任何随机位。相比之下池 10(纯数字)要丢弃 6 个取值、池 2000(常用汉字)要丢弃 1296 个,比例 3.017×10-7。

单条熵 95.3 bits 到底算多还是算少?

看用途。NIST SP 800-63B《Digital Identity Guidelines》对口令长度给出的最低要求是 8 位,并明确不建议强制「必须含大小写、数字、符号」这类组成规则,更看重长度和唯一性。按这个口径:池 62、长度 8 位是 47.6 bits,对应 2.183×1014 种可能,攻击者每秒试 1012 次也需要 3.6 分钟——这在人类可记忆的范围内,所以 NIST 说 8 位只是底线。而 16 位(95.3 bits)需要 1.51×109 年,32 位(190.5 bits)已经远超宇宙年龄。关键结论是:从 8 位加到 16 位,熵翻一倍(47.6 → 95.3),穷举时间从 3.6 分钟变成 15 亿年;而从 32 位加到 64 位,安全性已经「用不完」了,该关注的是别的问题(能不能存进数据库字段、能不能塞进 URL)。

自定义字符里能填中文和 emoji 吗?

能,填进去会按码点去重并参与抽样,所以一个 emoji(如 😀)算 1 个字符、贡献 1 个位置,池大小加 1。需要注意两点:一是 emoji 在 UTF-8 里占 4 字节,如果你的目标系统按字节数限制字段长度(比如数据库列是 VARCHAR(20) 实际存 20 字节),那 20 个 emoji 会占 80 字节直接超限,页面按字符数算不会提醒你这点。二是组合 emoji 更占位置:ZWJ 序列的家庭 emoji 👨‍👩‍👧 由 5 个码点组成(U+1F468、U+200D、U+1F469、U+200D、U+1F467),页面会把它算作 5 个字符,而你在界面上只看到 1 个图形。池大小要按码点算才准确。

我勾了「排除易混淆」,为什么熵只少了 0.2 bit?

因为这个选项剔的是 0 O o 1 I l 共 6 个字符,池从 58 降到 57。按 ΔH = 长度 × log2(58/57) 算,16 位时差 16 × log2(1.0175) = 0.2 bit——几乎可以忽略。它的价值不在安全性,而在可用性:人工把 0 和 O、1 和 l 抄错(或者从 PDF 里复制时字体变形)的概率远高于理论偏差带来的风险。反过来,如果你要的是安全性,扩容量的收益大得多:从纯数字池 10 扩到池 62,每字符多 2.63 bit,16 位就是多 42.3 bit。所以「排除易混淆」该勾,但别把它当安全措施。

生成 1000 条时看到重复了,是不是随机源出问题了?

不一定,先看你选的池和长度。如果池 10、长度 6,1000 条的碰撞概率是 39.32%——接近一半会重复,这是数学必然不是故障。判断方法看统计条:如果碰撞概率显示的是 39.32% 而你看到了重复,说明随机源工作正常,是参数选得太弱;如果碰撞概率显示 0.0001% 以下却仍看到重复,那才是真出问题了,请把条件记下来反馈。解决办法有两条:把长度加到 8 位以上(池 10、长度 8 时空间 108,1000 条碰撞概率降到万分之几),或者勾上「导出前自动去重」,页面会重抽重复项直到条数够——去重会略微牺牲独立性,但在测试数据场景完全可接受。

「熵」这个数在密码学上可靠吗?

作为可比较的指标可靠,作为安全性的证明不够。它算的是「如果每一位都独立均匀随机,长度为 L、字符集大小为 N 的字符串有多少种可能」,即 log2(N^L)。实际系统的熵会低于这个值:PRNG 的状态空间可能有限(Math.random 只有 128 bit 状态,长度超过 16 位后理论熵就超过状态空间了)、字符分布可能不均匀(人倾向于用 abcd1234)、使用模式可能泄露信息。所以 NIST 的建议里,熵是下限参考,真正的判断依据是「长度 + 唯一性 + 不复用」。本页用它做横向对比(同一张表里 4 位 vs 64 位)非常直观,但别把 190 bits 当成「绝对安全 190 bits」的证明。

这些随机字符串会传到服务器吗?

不会。生成过程完全在浏览器里完成,只调用了 crypto.getRandomValues、Math.log2、Math.exp 和 Set 这些原生能力,不发任何网络请求,也没有引入外部随机库。开发者工具的 Network 面板刷新一遍就能确认,HTML、CSS 和本页那个内联 script 之外没有任何请求。生成的字符串不写入浏览器存储(刷新即清空),导出的 CSV 用 Blob 在本地生成,直接进你的下载目录,不经过服务器。但请注意:如果你把结果粘贴到别处(聊天工具、数据库、代码仓库),那就是另一回事了——本工具不负责你粘贴之后的事。

相关工具