随机凑齐 23 个人,至少两人同一天生日的概率约为 50.7%,远高于多数人的直觉。本文给出这一结果的精确公式,解释直觉为什么会低估它:关键在于两两配对的数目随人数近似按平方增长。随后考察闰年和出生日期分布不均会把结果改变多少,以及同一道算术如何决定哈希函数和 64 位分组密码的安全上限。

精确公式与关键人数
生日问题通常追溯到统计学家理查德·冯·米塞斯(Richard von Mises)1939 年的工作。标准模型有三个假设:一年按 365 天计;每天出生的可能性相同;每个人的生日相互独立,因此不考虑双胞胎。直接计算“至少两人同天”要分很多种情况,更省事的办法是先算它的反面,即所有人生日都不同。让大家逐个报出生日:第 2 个人与第 1 个人不同天的概率是 364/365,第 3 个人避开前两人的概率是 363/365,依此类推。n 个人生日全不同的概率就是这些因子的乘积,用 1 减去它即得所求概率:
代入可得:22 人时 P≈0.476,23 人时 P≈0.507,23 是概率首次超过一半的人数。Borja 和 Haigh 2007 年在《Significance》上列出了其他常用门槛:概率达到 75% 需要 32 人,90% 需要 41 人,95% 需要 47 人,99% 需要 57 人,99.9% 需要 70 人。从 23 人到 57 人,结果就从“一半对一半”变成了“几乎肯定”。
两两配对数的快速增长
据 Borja 和 Haigh 的记述,学生第一次遇到这个问题时,常见回答是 366 的一半,即 183 人左右。偏差主要来自把问题悄悄换成了“有没有人和我同一天生日”。这两个问题相差很大:23 人中,其余 22 人里有人和你同天的概率只有 1−(364/365)^22≈5.9%;要让这个概率过半,大约需要另外 253 个人。
生日问题问的却是任意两个人。23 个人可以组成 23×22/2=253 对,每一对恰好同天的概率是 1/365。配对数随人数近似按平方增长:10 人有 45 对,23 人有 253 对,50 人就有 1225 对。把各对近似看成独立的小概率事件,可得常用近似 P(n)≈1−exp[−n(n−1)/(2×365)]。n=23 时,指数中的 253/365≈0.693,恰好约等于 ln 2,于是近似值落在 0.5 附近,精确值为 0.507。由此还可得到一个通用规律:在 N 个等可能的取值中随机抽取,出现重复的概率要达到一半,大约需要抽 1.18√N 次;N=365 时约为 22.5,向上取整正是 23。
下面的 Python 代码同时计算精确公式并做蒙特卡洛模拟:对每个人数随机生成 2 万组生日,统计出现重复生日的组所占比例,再把两者画在同一张图上。模拟点会围绕精确曲线小幅波动,波动大小取决于模拟次数。若把 rng.integers 换成按真实出生频率加权的 rng.choice(DAYS, size=(trials, n), p=weights),就能检验下一节讨论的非均匀情形。
import numpy as np
import matplotlib.pyplot as plt
DAYS = 365
rng = np.random.default_rng(2026)
def exact_prob(n, days=DAYS):
"""P(at least two of n people share a birthday), uniform birthdays."""
k = np.arange(n)
return 1.0 - np.prod((days - k) / days)
def simulate_prob(n, trials=20_000, days=DAYS):
"""Monte Carlo estimate of the same probability."""
birthdays = rng.integers(0, days, size=(trials, n))
sorted_days = np.sort(birthdays, axis=1)
has_match = (np.diff(sorted_days, axis=1) == 0).any(axis=1)
return has_match.mean()
ns = np.arange(1, 81)
exact = np.array([exact_prob(n) for n in ns])
simulated = np.array([simulate_prob(n) for n in ns])
print(f"n=23 exact={exact_prob(23):.4f} simulated={simulate_prob(23):.4f}")
plt.plot(ns, exact, label="Exact formula")
plt.scatter(ns, simulated, s=8, color="tab:orange", label="Simulation (20,000 groups per n)")
plt.axhline(0.5, linestyle="--", color="gray")
plt.axvline(23, linestyle=":", color="gray")
plt.xlabel("Number of people")
plt.ylabel("P(at least two share a birthday)")
plt.legend()
plt.savefig("birthday_curve.png", dpi=200, bbox_inches="tight")
plt.show()

闰年与出生日期不均的影响
闰年的影响很小。2 月 29 日约每四年才出现一次,按日历推算,这一天出生的概率只有普通日子的四分之一左右。在 Borja 和 Haigh 使用的英格兰和威尔士 1979–2005 年出生登记中,2 月 29 日出生的有 12,311 例,约占 0.07%。即使把它当作与其他日子同样常见的第 366 天,23 人中出现同天生日的概率也只从约 0.507 降到约 0.506;两位作者也指出,N 取 365 或 366 时,门槛都是 23 人。
出生日期分布不均匀,影响方向是确定的:只要分布偏离均匀,同天生日的概率只会升高,不会降低。直观地说,出生集中在某些日子,相当于把人挤进了更少的格子;极端情况下所有人都在同一天出生,任何两人都会撞上。Bloom(1973)给出了这一结论的正式证明,Berresford(1980)在《Mathematics Magazine》上整理了 Munford(1977)的更简短证法。因此,现实中的不均匀不会推翻 23 人的门槛,只会让门槛更容易达到。
具体差多少?Borja 和 Haigh 用英格兰和威尔士 1979–2005 年的 17,517,192 例出生(不含 2 月 29 日)作了计算。数据确实不均匀:9 月下旬出生偏多,对应圣诞假期前后受孕;节礼日等公共假日因医院排班减少,出生偏少;工作日出生多于周末。但按他们引用的一阶近似,这种不均匀只让“22 人生日全不同”的概率乘以 0.99917,几乎没有变化。如果一个班的人全都出生在同一年,星期效应不会被多年平均掉,衡量不均匀程度的指标约增大十倍,这个乘数也只降到 0.991,门槛仍是 23 人。变化主要出现在要求很高的门槛上:以 1979 年出生的人群计算,概率达到 95% 只需 46 人,而不是均匀模型的 47 人。Berresford 用归并后的经验出生频率计算,结论相近:23 人时,概率从均匀情形的 0.5073 升至 0.5101。
哈希碰撞与生日攻击
把“365 天”换成“哈希值的所有可能取值”,生日问题就变成了密码学中的生日攻击。输出为 L 位的哈希函数共有 2^L 种取值。按前面 1.18√N 的规律,只要计算约 2^(L/2) 个随机输入的哈希值,就有可观的概率找到两个不同输入得到相同的哈希值,即发生“碰撞”;而要找到与某个指定哈希值相同的输入,预期需要约 2^L 次计算。NIST 因此把 L 位哈希函数的预期抗碰撞强度定为 L/2 位、原像强度定为 L 位:SHA-256 输出 256 位,抗碰撞强度为 128 位。SHA-1 输出 160 位,但已有比通用生日攻击更快的密码分析,NIST 将其抗碰撞强度标为低于 80 位;NIST 于 2011 年弃用 SHA-1,2013 年底起禁止将其用于数字签名。
这类攻击的经典思路来自 Gideon Yuval 1979 年发表在《Cryptologia》上的《How to Swindle Rabin》。据 Nishimura 和 Sibuya(1990)的转述,攻击者准备一份对方愿意签署的正常合同和一份对方绝不会签的恶意合同,对两者各生成大量措辞微调的版本,直到找出摘要相同的一对;对方签了正常版本,这个签名对恶意版本同样有效。Nishimura 和 Sibuya 也指出,这是在两组文件之间寻找匹配,与经典生日问题“同一组内任意配对”的概率模型并不相同,但所需尝试次数仍是取值总数平方根的量级。
同样的界限也约束分组密码。2016 年,法国国家信息与自动化研究所(INRIA)的 Karthikeyan Bhargavan 和 Gaëtan Leurent 在 ACM CCS 会议上发表了 Sweet32 攻击。3DES、Blowfish 等较老的算法每个分组只有 64 位,用同一密钥加密约 2^32 个分组(约 32 GB)后,就可能出现两个相同的密文块;在 CBC 模式下,这会泄露对应两个明文块的异或值。研究者让浏览器反复发送携带同一 cookie 的请求,在 30.5 小时内采集 610 GB HTTPS 流量后恢复了一个两分组长的 cookie;针对 OpenVPN 的演示用 18.6 小时、705 GB 流量恢复了 16 字节的认证令牌。相关漏洞编号为 CVE-2016-2183 和 CVE-2016-6329。作者建议改用 128 位分组的 AES(其生日界约为 256 EB),或限制同一密钥加密的数据量、频繁更换密钥。