生日攻击


生日攻击源自于概率论中的一个有趣的问题——生日问题。

生日问题

  • 问题描述:假设有\(m(m<365)\)个人,且这\(m\)个人的生日在一年(\(365\) 天)中均匀分布。为保证这\(m\)个人中至少有两人相同的概率大于\(1\over2\),求\(m\)的取值。
  • 设\(m\)个人生日都不相同的概率为\(\overline{p(m)}\):

\[\overline{p(m)}=1×(1-{1\over 365})×(1-{2\over365})×\cdots×(1-{m-1\over365}). \]

根据泰勒(\(Taylor\))展开式\(e^{-x}=1-x+{x^2\over 2!}+\cdots\),可得\(e^{-x}≥1-x\)。
因此,\(\overline{p(m)}\)可以用

\[\overline{p(m)}≤e^{-{1\over365}}\cdot e^{-{2\over365}}\cdots e^{-{m-1\over365}}=e^{-{m(m-1)\over2\cdot 365}}. \]

因为事件“至少两人生日相同”与事件“\(m\)个人生日都不相同”互为对立事件,所以

\[p(m)=1-\overline{p(m)}≥1-e^{-{m(m-1)\over2\cdot 365}}. \]

从而,要使得\(p(m)>{1\over2}\),则

\[m≥\sqrt{2ln2×365}≈1.18\sqrt{365}.\quad\quad\cdots\cdots\quad\quad (1) \]

当\(m=23\)时,\(p(m)\)约为\(0.507.\)

  • 我们日常生活中,直观感觉与自己生日在同一天的人很少,但是上述结论意味着\(23\)个人中有两个人生日在同一天的概率超过了\(50\%\)。似乎与我们的直觉相悖,因此该问题又被称为“生日悖论”(\(birthday\ paradox\)).

生日攻击

  • 基于这一悖论,\(Gideon\ Yuval\)在\(1979\)年提出了生日攻击,用于杂凑函数的碰撞攻击,该技术可进一步用于分析基于杂凑函数的数字签名等算法的安全性。
  • 将生日问题与碰撞攻击类比,生日攻击是要找到两个人具有相同的生日,而碰撞攻击的目的是找到两个消息具有相同的杂凑值。因此,对碰撞攻击来说,把消息看作“人”,消息的杂凑值看作“人的生日”。设杂凑值的长度为\(n-bit\),将\((1)\)中的\(365\)用\(2^n\)代替,可得对于杂凑函数通用的生日攻击。
  • 随机选择\(O(2^{n\over2})\)个消息构成的集合\(S\),利用杂凑值表,以\(O(2^{n\over2})\)的复杂度即可找到一对碰撞,即找到\((X,X')\in S\),满足\(h(X)=h(X')\)且\(X≠X'.\)
  • 例如:假设杂凑值长度为\(64-bit\),则根据生日攻击,随机选取\(2^{32}\)个消息,即可以约\(1\over2\)的概率找到一对碰撞。生日攻击揭示了,\(n-bit\)输出长度的杂凑函数的碰撞攻击的复杂度约为\(2^{n\over2}\),要抵抗实际的碰撞攻击,\(2^{n\over2}\)应超过当前(及未来一段时间内)敌手的计算能力,例如\(2^{128}\)。因此,一般建议杂凑值的长度\(n≥256\)。
  • 有意义的碰撞,尽管生日攻击并未要求找到的碰撞对\((X,X')\)是”有意义的“或者含义有所关联,但是想找到这样碰撞对并没有想象中的那么困难。例如,敌手可以改动文件的冗余信息,或者简单地改变信息的措辞即可构造意思相近或相反的句子,而措辞的改变就是多种形式的排列组合问题,例如”\(n\)个小时(以内/之内)您将(收到/扣款)\(m\)元(,/。)“这句话就有\(n×2×2×m×2\)中有意义的组合。要找到\(2^{n\over2}\)个有意义的消息对,至多在\(n\over2\)个位置改变措辞即可。

碰撞伪造签名

  • \(Yuval\)提出的利用生日攻击发现碰撞,伪造签名的过程如下:
    1. 敌手生成合法信息(例如,金额为\(100\)元的账单)的\(2^{n\over2}\)个语义相近的变体,构成集合\(\mathbb{X}\),存储消息及其相应的杂凑值,记为表\(T\)。
    2. 敌手生成伪造信息(例如,金额为\(1000\)元的账单)的语义相近的不同变体\(Y\),计算\(h(Y)\)并检查是否与表\(T\)中的值相吻合,直到找到消息\(Y\),满足\(h(Y)=h(X)\)。根据生日攻击,约\(2^{n\over2}\)个\(Y\)会找到一对碰撞。
    3. 敌手将合法消息的变体\(X\)提供给用户\(A\)签名。
    4. 敌手获得签名后,将消息\(X\)替换为消息\(Y\),并将\(Y\)及签名发送给用户\(B\),向用户\(B\)证明用户\(A\)签署了消息\(Y\)。签名可通过验证,从而,尽管敌手不知道用户\(A\)的私钥也能伪造成功。
  • 杂凑函数广泛应用于社会各领域,特别是金融领域,上述碰撞攻击一旦被有效实现,将会对我们的工作生活产生严重影响。
  • 现在,生日攻击已经称为许多密码分析方法的基础,只要能将问题的求解划分为两个相互独立的集合通过某种运算满足某种特性,都可以尝试结合生日攻击来解决。\(2002\)年,\(David Wagner\)提出了广义生日攻击,将生日攻击推广到更一般的多个相互独立的集合的情况,Introduction to Modern Cryptography给出了在时间复杂度不变的情况下,将生日攻击的存储复杂度将为常数级的算法,特别是\(AdHash\)、基于\(ROS\)问题的盲签名算法(盲签名)等的安全性分析产生了重要影响。

Code Demo

  • 这里补一个\(Demo\),原本我想用我们常见的\(MD5\)进行演示,但是\(2^{64}\)的复杂度我的计算机爆破的时间有点长,因此这里做一个\(Demo\),我们截取\(MD5\)值的前\(40\)位进行碰撞,因此目标就是\(X≠X'\)且\(MD5(X)[:40]=MD5(X')[:40]\)
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:
亲爱的客户您好,您的专属账户在近期已经顺利完成一次金额为一千元之扣款。若有疑问,麻烦咨询客服。谢谢支持。
--------------------------------------------------
*/

96edcf32-f3cc-45f1-b851-64a6bfb7930e

posted @ 2026-04-16 21:32  chen_xing  阅读(92)  评论(0)    收藏  举报