文章目录
生日攻击源自于概率论中的一个有趣的问题------生日问题。
生日问题
- 问题描述:假设有 m ( m < 365 ) m(m<365) m(m<365)个人,且这 m m m个人的生日在一年( 365 365 365 天)中均匀分布。为保证这 m m m个人中至少有两人相同的概率大于 1 2 1\over2 21,求 m m m的取值。
- 设 m m m个人生日都不相同的概率为 p ( m ) ‾ \overline{p(m)} p(m):
p ( m ) ‾ = 1 × ( 1 − 1 365 ) × ( 1 − 2 365 ) × ⋯ × ( 1 − m − 1 365 ) . \overline{p(m)}=1×(1-{1\over 365})×(1-{2\over365})×\cdots×(1-{m-1\over365}). p(m)=1×(1−3651)×(1−3652)×⋯×(1−365m−1).
根据泰勒( T a y l o r Taylor Taylor)展开式 e − x = 1 − x + x 2 2 ! + ⋯ e^{-x}=1-x+{x^2\over 2!}+\cdots e−x=1−x+2!x2+⋯,可得 e − x ≥ 1 − x e^{-x}≥1-x e−x≥1−x。
因此, p ( m ) ‾ \overline{p(m)} p(m)可以用
p ( m ) ‾ ≤ e − 1 365 ⋅ e − 2 365 ⋯ e − m − 1 365 = e − m ( m − 1 ) 2 ⋅ 365 . \overline{p(m)}≤e^{-{1\over365}}\cdot e^{-{2\over365}}\cdots e^{-{m-1\over365}}=e^{-{m(m-1)\over2\cdot 365}}. p(m)≤e−3651⋅e−3652⋯e−365m−1=e−2⋅365m(m−1).
因为事件"至少两人生日相同"与事件" m m m个人生日都不相同"互为对立事件,所以
p ( m ) = 1 − p ( m ) ‾ ≥ 1 − e − m ( m − 1 ) 2 ⋅ 365 . p(m)=1-\overline{p(m)}≥1-e^{-{m(m-1)\over2\cdot 365}}. p(m)=1−p(m)≥1−e−2⋅365m(m−1).
从而,要使得 p ( m ) > 1 2 p(m)>{1\over2} p(m)>21,则
m ≥ 2 l n 2 × 365 ≈ 1.18 365 . ⋯ ⋯ ( 1 ) m≥\sqrt{2ln2×365}≈1.18\sqrt{365}.\quad\quad\cdots\cdots\quad\quad (1) m≥2ln2×365 ≈1.18365 .⋯⋯(1)
当 m = 23 m=23 m=23时, p ( m ) p(m) p(m)约为 0.507. 0.507. 0.507. - 我们日常生活中,直观感觉与自己生日在同一天的人很少,但是上述结论意味着 23 23 23个人中有两个人生日在同一天的概率超过了 50 % 50\% 50%。似乎与我们的直觉相悖,因此该问题又被称为"生日悖论"( b i r t h d a y p a r a d o x birthday\ paradox birthday paradox).
生日攻击
- 基于这一悖论, G i d e o n Y u v a l Gideon\ Yuval Gideon Yuval在 1979 1979 1979年提出了生日攻击,用于杂凑函数的碰撞攻击,该技术可进一步用于分析基于杂凑函数的数字签名等算法的安全性。
- 将生日问题与碰撞攻击类比,生日攻击是要找到两个人具有相同的生日,而碰撞攻击的目的是找到两个消息具有相同的杂凑值。因此,对碰撞攻击来说,把消息看作"人",消息的杂凑值看作"人的生日"。设杂凑值的长度为 n − b i t n-bit n−bit,将 ( 1 ) (1) (1)中的 365 365 365用 2 n 2^n 2n代替,可得对于杂凑函数通用的生日攻击。
- 随机选择 O ( 2 n 2 ) O(2^{n\over2}) O(22n)个消息构成的集合 S S S,利用杂凑值表,以 O ( 2 n 2 ) O(2^{n\over2}) O(22n)的复杂度即可找到一对碰撞,即找到 ( X , X ′ ) ∈ S (X,X')\in S (X,X′)∈S,满足 h ( X ) = h ( X ′ ) h(X)=h(X') h(X)=h(X′)且 X ≠ X ′ . X≠X'. X=X′.
- 例如:假设杂凑值长度为 64 − b i t 64-bit 64−bit,则根据生日攻击,随机选取 2 32 2^{32} 232个消息,即可以约 1 2 1\over2 21的概率找到一对碰撞。生日攻击揭示了, n − b i t n-bit n−bit输出长度的杂凑函数的碰撞攻击的复杂度约为 2 n 2 2^{n\over2} 22n,要抵抗实际的碰撞攻击, 2 n 2 2^{n\over2} 22n应超过当前(及未来一段时间内)敌手的计算能力,例如 2 128 2^{128} 2128。因此,一般建议杂凑值的长度 n ≥ 256 n≥256 n≥256。
- 有意义的碰撞 ,尽管生日攻击并未要求找到的碰撞对 ( X , X ′ ) (X,X') (X,X′)是"有意义的"或者含义有所关联,但是想找到这样碰撞对并没有想象中的那么困难。例如,敌手可以改动文件的冗余信息,或者简单地改变信息的措辞即可构造意思相近或相反的句子,而措辞的改变就是多种形式的排列组合问题,例如" n n n个小时(以内/之内)您将(收到/扣款) m m m元(,/。)"这句话就有 n × 2 × 2 × m × 2 n×2×2×m×2 n×2×2×m×2中有意义的组合。要找到 2 n 2 2^{n\over2} 22n个有意义的消息对,至多在 n 2 n\over2 2n个位置改变措辞即可。
碰撞伪造签名
- Y u v a l Yuval Yuval提出的利用生日攻击发现碰撞,伪造签名的过程如下:
- 敌手生成合法信息(例如,金额为 100 100 100元的账单)的 2 n 2 2^{n\over2} 22n个语义相近的变体,构成集合 X \mathbb{X} X,存储消息及其相应的杂凑值,记为表 T T T。
- 敌手生成伪造信息(例如,金额为 1000 1000 1000元的账单)的语义相近的不同变体 Y Y Y,计算 h ( Y ) h(Y) h(Y)并检查是否与表 T T T中的值相吻合,直到找到消息 Y Y Y,满足 h ( Y ) = h ( X ) h(Y)=h(X) h(Y)=h(X)。根据生日攻击,约 2 n 2 2^{n\over2} 22n个 Y Y Y会找到一对碰撞。
- 敌手将合法消息的变体 X X X提供给用户 A A A签名。
- 敌手获得签名后,将消息 X X X替换为消息 Y Y Y,并将 Y Y Y及签名发送给用户 B B B,向用户 B B B证明用户 A A A签署了消息 Y Y Y。签名可通过验证,从而,尽管敌手不知道用户 A A A的私钥也能伪造成功。
- 杂凑函数广泛应用于社会各领域,特别是金融领域,上述碰撞攻击一旦被有效实现,将会对我们的工作生活产生严重影响。
- 现在,生日攻击已经称为许多密码分析方法的基础,只要能将问题的求解划分为两个相互独立的集合通过某种运算满足某种特性,都可以尝试结合生日攻击来解决。 2002 2002 2002年, D a v i d W a g n e r David Wagner DavidWagner提出了广义生日攻击,将生日攻击推广到更一般的多个相互独立的集合的情况,Introduction to Modern Cryptography给出了在时间复杂度不变的情况下,将生日攻击的存储复杂度将为常数级的算法,特别是 A d H a s h AdHash AdHash、基于 R O S ROS ROS问题的盲签名算法(盲签名)等的安全性分析产生了重要影响。
Code Demo
- 这里补一个 D e m o Demo Demo,原本我想用我们常见的 M D 5 MD5 MD5进行演示,但是 2 64 2^{64} 264的复杂度我的计算机爆破的时间有点长,因此这里做一个 D e m o Demo Demo,我们截取 M D 5 MD5 MD5值的前 40 40 40位进行碰撞,因此目标就是 X ≠ X ′ X≠X' X=X′且 M D 5 ( X ) [ : 40 ] = M D 5 ( X ′ ) [ : 40 ] MD5(X)[:40]=MD5(X')[:40] MD5(X)[:40]=MD5(X′)[:40]
rust
use std::collections::HashMap;
use std::time::Instant;
const CHOICES: [(&str, &str); 23] = [
("尊敬的", "亲爱的"),
("用户", "客户"),
("您好", "你好"),
(",", "!"),
("您的", "您的专属"),
("账户", "账号"),
("在", "于"),
("最近", "近期"),
("已", "已经"),
("成功", "顺利"),
("完成", "处理了"),
("一笔", "一次"),
("金额为", "总计"),
("1000", "一千"),
("元", "人民币"),
("的", "之"), // 这里不是很好,有点语病,但是为了循环内字符串拼接的一致性,凑合使用一下😀
("扣款", "支付"),
("。如有疑问", "。若有疑问"),
(",请", ",麻烦"),
("联系客服。", "咨询客服。"),
("感谢", "谢谢"),
("支持", "配合"),
("。", "!")
];
fn main() {
let start_time = Instant::now();
// 2^22 种状态 (约为 4,194,304),足以确保 40-bit 空间内极大概率发生碰撞
let total_variations = 1 << 23;
let mut hash_table: HashMap<[u8; 5], String> = HashMap::with_capacity(total_variations as usize);
for state in 0..total_variations {
let message = generate_message(state);
let result = md5::compute(message.as_bytes());
let mut truncated_hash = [0u8; 5];
truncated_hash.copy_from_slice(&result.0[0..5]);
if let Some(existing_message) = hash_table.get(&truncated_hash) {
println!("Elapsed time: {:?}", start_time.elapsed());
println!("Number of attempts: {}", state + 1);
println!("MD5[:40] (HEX): {:02x?}", truncated_hash);
println!("--------------------------------------------------");
println!("Message1:\n{}", existing_message);
println!("--------------------------------------------------");
println!("Message2:\n{}", message);
println!("--------------------------------------------------");
break;
} else {
hash_table.insert(truncated_hash, message);
}
}
}
fn generate_message(state: u32) -> String {
let mut msg = String::with_capacity(200);
for i in 0..23 {
let choice = if (state & (1 << i)) != 0 {
CHOICES[i].1
} else {
CHOICES[i].0
};
msg.push_str(choice);
}
msg
}
/*
Elapsed time: 1.4447281s
Number of attempts: 2010004
MD5[:40] (HEX): [cc, 9e, 3e, 35, 67]
--------------------------------------------------
Message1:
亲爱的客户您好,您的专属账号在最近已成功处理了一次金额为一千元之扣款。若有疑问,麻烦咨询客服。感谢支持。
--------------------------------------------------
Message2:
亲爱的客户您好,您的专属账户在近期已经顺利完成一次金额为一千元之扣款。若有疑问,麻烦咨询客服。谢谢支持。
--------------------------------------------------
*/
