<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>Dorange</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://lr2006-robot.github.io/myblog.github.io/</id>
  <link href="https://lr2006-robot.github.io/myblog.github.io/" rel="alternate"/>
  <link href="https://lr2006-robot.github.io/myblog.github.io/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, Dorange</rights>
  <title>Alice and Bobの神秘小屋</title>
  <updated>2026-03-19T12:42:04.824Z</updated>
  <entry>
    <author>
      <name>Dorange</name>
    </author>
    <category term="加解密模式分析" scheme="https://lr2006-robot.github.io/myblog.github.io/categories/%E5%8A%A0%E8%A7%A3%E5%AF%86%E6%A8%A1%E5%BC%8F%E5%88%86%E6%9E%90/"/>
    <category term="密码学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="加解密" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%8A%A0%E8%A7%A3%E5%AF%86/"/>
    <category term="现代密码" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E7%8E%B0%E4%BB%A3%E5%AF%86%E7%A0%81/"/>
    <category term="分布式密码" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E5%AF%86%E7%A0%81/"/>
    <content>
      <![CDATA[<p>在前面的章节中，我们已经分析了分组密码的加密算法和分组模式，这些都是对称加密算法的核心内容。然而，在实际应用中，我们经常需要在多个参与者之间共享一个秘密，而不希望任何单一参与者能够独自获得这个秘密或者一旦有一个人背叛就无法恢复秘密。这就引入了门限秘密共享算法（Threshold Secret Sharing），其中最著名的就是 Shamir 门限秘密共享算法。</p><p>Shamir 门限秘密共享算法是一种基于多项式插值的秘密共享方案，它允许将一个秘密分成 $n$ 份，并且只要有 $k$ 份（$k \leq n$）就可以恢复出原始的秘密，而任何少于 $k$ 份的信息都无法获得任何关于秘密的线索。这种算法在分布式系统、密钥管理和安全多方计算等领域有着广泛的应用。</p><h2><span id="shamir-门限秘密共享算法的数学原理">Shamir 门限秘密共享算法的数学原理 😇</span></h2><h3><span id="shamir-门限秘密共享算法加密">Shamir 门限秘密共享算法加密</span></h3><p>首先，我们来思考一个问题，对于一个 $k$ 次多项式 $F(x) = a_0 + a_1 x + a_2 x^2 + … + a_k x^k$，我们要求出 $F(0)$ 的值，也就是多项式的常数项 $a_0$，我们需要知道多少个点 $(x_i, F(x_i))$ 才能唯一确定这个多项式呢？</p><p>这个答案实际上是多项式唯一确定性定理（拉格朗日插值定理），它告诉我们，要唯一确定一个 $k$ 次多项式，我们至少需要知道 $k+1$ 个不同的点。这个定理是 Shamir 门限秘密共享算法的数学基础，但是我们在这里不详细展开证明，只要知道就好了。为了方便理解，不妨想一下两点确定一条直线。</p><p>因此，在 Shamir 门限秘密共享算法中，我们将秘密 $s$ 作为多项式 $F(x)$ 的常数项，即 $F(0) = s$，然后随机选择 $k$ 个系数 $a_1, a_2, …, a_k$ 来构造一个 $k$ 次多项式。接下来，我们选择 $n$ 个不同的非零点 $x_1, x_2, …, x_n$（$k \leq n$），为了进一步增大安全性，我们将数据放在有限域 $\mathbb{F}_p$ 中使点离散化，计算出对应的 $F(x_i)$，将这些点 $(x_i, F(x_i))$ 分发给参与者。</p><h3><span id="shamir-门限秘密共享算法解密">Shamir 门限秘密共享算法解密</span></h3><p>恢复我们使用拉格朗日插值法来计算 $F(0)$，也就是秘密 $s$。首先计算基多项式（这里的除法会转化为模逆元）：</p><script type="math/tex; mode=display">L_i(x) = \prod_{j=1, j \neq i}^{k} \frac{x - x_j}{x_i - x_j}</script><p>我们发现，$L_i(i) = 1$，而 $L_i(j) = 0$（$j \neq i$），因此我们可以通过以下公式来恢复函数 $F$：</p><script type="math/tex; mode=display">F(x) = \sum_{i=1}^{k} F(x_i) L_i(x)</script><p>这样构造会发现，将所有的 $x_i$ 代入上式，只有当 $i = j$ 时才会有贡献，刚好满足所有条件，因此最终我们可以得到这就是原函数，所以 $F(0) = s$。</p><h2><span id="shamir-门限秘密共享算法实现">Shamir 门限秘密共享算法实现 🥰</span></h2><p>接下来，我们来看看 Shamir 门限秘密共享算法的一个简单实现示例：</p><pre><code class="lang-python">from sage.all import *from Crypto.Util.number import *import random# 加密部分# 1. 设定参数与有限域# 选择一个大素数 p，建立有限域 GF(p) 保证离散化和安全性p = random_prime(2^128)F = GF(p)# 2. 秘密与参与者设定secret = b&#39;our_key&#39;secret_value = bytes_to_long(secret)  # 需要保护的秘密 ss = F(secret_value)k = 3  # 多项式的次数 k，由于次数为 k，需要 k+1 个点才能还原多项式n = 6  # 分发给 n 个参与者print(f&quot;设定的素数 p: {p}&quot;)print(f&quot;原秘密 s (F(0)): {s}&quot;)# 3. 构造 k 次多项式 F(x) = s + a_1*x + a_2*x^2 + ... + a_k*x^k# 常数项为 s，随机选择剩余的 k 个系数coeffs = [s] + [F.random_element() for _ in range(k)]R.&lt;x&gt; = PolynomialRing(F)F_poly = R(coeffs)print(f&quot;构造的多项式 F(x): {F_poly}&quot;)# 4. 生成 n 个份额 (x_i, F(x_i))# 这里为了简便，选取 x_i = 1, 2, ..., n, 实际上可以乱序shares = []for i in range(1, n + 1):    x_i = F(i)    y_i = F_poly(x_i)    shares.append((x_i, y_i))    print(f&quot;参与者 {i} 取得的份额: (x={x_i}, y={y_i})&quot;)# 解密部分# 1. 收集份额# 重建需要 k+1 个份额，这里我们随机抽取 k+1 个参与者的份额threshold = k + 1collected_shares = random.sample(shares, threshold)print(f&quot;收集到的 {threshold} 个份额用于解密:&quot;)for share in collected_shares:    print(f&quot;(x={share[0]}, y={share[1]})&quot;)# 2. 拉格朗日插值法恢复 F(0) (即原秘密 s)# L_i(0) = ∏(0 - x_j) / (x_i - x_j) (j ≠ i)recovered_s = F(0)for i in range(threshold):    x_i, y_i = collected_shares[i]    # 计算对应的拉格朗日基多项式在 x=0 处的值 L_i(0)    L_i_0 = F(1)    for j in range(threshold):        if i != j:            x_j, y_j = collected_shares[j]            # 在 GF(p) 上的四则运算会自动进行取模和求模逆元            L_i_0 *= (- x_j) / (x_i - x_j)    # f(0) = Σ y_i * L_i(0)    recovered_s += y_i * L_i_0# 3. 校验恢复结果if recovered_s == s:    print(&quot;解密成功！秘密完好无损地被恢复了。&quot;)    print(f&quot;秘密是 {long_to_bytes(int(recovered_s))} !!!&quot;)else:    print(&quot;解密失败！&quot;)</code></pre><p>有了这个实现，我们就可以在多个参与者之间安全地共享一个秘密，并且只有当足够数量的参与者合作时才能恢复出这个秘密。当然，我们也发现用这个算法直接加密信息会导致 $s$ 的值过大，因此我们通常会先使用对称加密算法（如 AES）加密我们的数据，然后将 AES 的密钥作为秘密 $s$ 来进行共享，这样既保证了安全性又提高了效率。</p><p>上一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/2014.html">加解密模式分析 - 分组模式解析</a> 👈<br>下一章：<br>回到开始：<a href="http://lr2006-robot.github.io/myblog.github.io/about/">关于我</a> 👈</p><p>相关链接：</p>]]>
    </content>
    <id>https://lr2006-robot.github.io/myblog.github.io/post/2015.html</id>
    <link href="https://lr2006-robot.github.io/myblog.github.io/post/2015.html"/>
    <published>2026-03-19T14:30:00.000Z</published>
    <summary>
      <![CDATA[<p>在前面的章节中，我们已经分析了分组密码的加密算法和分组模式，这些都是对称加密算法的核心内容。然而，在实际应用中，我们经常需要在多个参与者之间共享一个秘密，而不希望任何单一参与者能够独自获得这个秘密或者一旦有一个人背叛就无法恢复秘密。这就引入了门限秘密共享算法（Thresho]]>
    </summary>
    <title>加解密模式分析 - Shamir 门限秘密共享算法</title>
    <updated>2026-03-19T12:42:04.824Z</updated>
  </entry>
  <entry>
    <author>
      <name>Dorange</name>
    </author>
    <category term="加解密模式分析" scheme="https://lr2006-robot.github.io/myblog.github.io/categories/%E5%8A%A0%E8%A7%A3%E5%AF%86%E6%A8%A1%E5%BC%8F%E5%88%86%E6%9E%90/"/>
    <category term="密码学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="加解密" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%8A%A0%E8%A7%A3%E5%AF%86/"/>
    <category term="现代密码" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E7%8E%B0%E4%BB%A3%E5%AF%86%E7%A0%81/"/>
    <category term="对称加密" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86/"/>
    <category term="分组密码" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%88%86%E7%BB%84%E5%AF%86%E7%A0%81/"/>
    <content>
      <![CDATA[<p>前面我们已经分析了经典的分组加密算法 DES 和 AES，这两个算法都是分组密码的代表。分组密码是对称加密算法中最重要的一类算法，但是我们经过前面学习会发现，分组密码算法本身只能对固定大小的块进行加密，而实际应用中我们需要加密的数据往往是任意长度的，这就引入了分组模式（Block Cipher Mode）的概念。</p><p>分组模式是一种将分组密码算法扩展到任意长度数据的技术，它定义了如何将明文分成块、如何处理最后一个块以及如何将加密结果组合成最终的密文。常见的分组模式有 ECB（Electronic Codebook）、CBC（Cipher Block Chaining）、CFB（Cipher Feedback）、OFB（Output Feedback）和 CTR（Counter）等。</p><p>在本章中，我们将深入分析这些分组模式的工作原理、优缺点以及它们在实际应用中的安全性表现，帮助读者全面理解分组模式的设计思想和使用场景。</p><h2><span id="明文填充">明文填充 🫡</span></h2><p>分组密码迭代加密要求每一个明文分组都是块长度(8或16字节)<br>当分组到最后一组时，其长度不足块长度，就需要对其进行填充，将长度扩展为块长度</p><p>常见的填充方式有如下几种:</p><ul><li>补零:在末尾补上 0x00 字节<br>… | DD DD DD DD DD DD DD DD | DD DD DD DD 00 00 00 00 00 |</li><li>字节填充:先填充 0x00 字节,直至最后一字节填充值为填充长度<br>… | DD DD DD DD DD DD DD DD | DD DD DD DD 00 00 00 00 04 |</li><li>PKCS7 填充:若需填充 N 个字节,则每个填充字节值都是 N<br>… | DD DD DD DD DD DD DD DD | DD DD DD 04 04 04 04 04 |</li></ul><h2><span id="ecbelectronic-code-book-mode-电子密码本模式">ECB（Electronic Code Book mode 电子密码本模式） 🙂</span></h2><p>ECB 是最简单的分组模式，它将明文分成固定大小的块，每个块独立加密，得到对应的密文块。</p><p>由于每个块独立加密，因此相同的明文块会得到相同的密文块，这就导致了 ECB 模式的一个严重安全问题：它无法隐藏明文中的模式信息，容易受到重放攻击和统计分析攻击。<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAER2UBpu9SwbtSKkRpQc6DghVMpUZa61AACqRwAAjDm2FVRT6KYzaNb6zoE.png" alt="ECB 模式示意图"></p><pre><code class="lang-python">from Crypto.Cipher import AESdef aes_ecb_encrypt(pt, key):    aes=AES.new(key, AES.MODE_ECB)    ct = aes.encrypt(pt)    return ctdef aes_ecb_decrypt(ct, key):    aes=AES.new(key,AES.MODE_ECB)    pt=aes.decrypt(ct)    return pt</code></pre><h2><span id="cbccipher-block-chaining-mode-密码分组链接模式">CBC（Cipher Block Chaining mode 密码分组链接模式） 🥰</span></h2><p>CBC 模式通过将每个明文块与前一个密文块进行异或操作来加密当前块，从而解决了 ECB 模式的安全问题。</p><p>CBC 模式需要一个初始化向量（IV）来加密第一个块，IV 应该是随机生成的并且在每次加密时都不同。然后上一块加密的结果会被用作下一块的输入异或。CBC 模式能够隐藏明文中的模式信息，但它也有一些缺点，比如加密和解密过程不能并行化，且对 IV 的管理要求较高。<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAER2Ulpu9eGUL3lU3WXeI7Eq2vYnzFtAgACZRgAAjDm4FUNU2Z8xyXWEjoE.png" alt="CBC 模式示意图"></p><pre><code class="lang-python">def aes_cbc_encrypt(pt, key,iv):    aes=AES.new(key,AES.MODE_CBC, iv=iv=iv=iv)    ct = aes.encrypt(pt)    return ctdef aes_cbc_decrypt(ct, key,iv):    aes=AES.new(key,AES.MODE_CBC, iv=iv=iv=iv)    pt = aes.decrypt(ct)    return pt</code></pre><h2><span id="cfbcipher-feedback-mode-密文反馈模式">CFB（Cipher FeedBack mode 密文反馈模式） 🤪</span></h2><p>CFB 模式是先对 IV 进行加密得到中间结果，将这个结果与第一块明文进行异或得到第一块密文，然后将第一块密文进行加密得到中间结果，中间结果再与第二块明文异或，得到第二块密文，以此类推。<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAER2U1pu9jkaEskIKF7jQ_s4fpjxENJhAACaRgAAjDm4FVcE3RdqdqWsjoE.png" alt="CFB 模式示意图"></p><pre><code class="lang-python">def aes_cfb_encrypt(pt, key,iv):    aes=AES.new(key,AES.MODE_CFB, iv=iv=iv=iv)    ct = aes.encrypt(pt)    return ctdef aes_cfb_decrypt(ct, key,iv):    aes=AES.new(key,AES.MODE_CFB, iv=iv=iv=iv)    pt = aes.decrypt(ct)    return pt</code></pre><h2><span id="ctrcounter-mode-计数器模式">CTR（CounTeR mode 计数器模式） 🤩</span></h2><p>CTR 模式是对一个逐次累加的计数器进行加密得到中间结果（类似密钥流的流密码），将这个结果与块明文进行异或得到块密文。CTR 每一次加密时，都会生成一个 nonce （仅用一次的数）来作为计数器的初始值，计数器格式为：前面字节为固定的 nonce，后面字节为分组序号，会逐次累加。<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAER2VRpu9quRGUoCiI_T90ONnmjjBgI_gACcRgAAjDm4FW2XzjnINhGoToE.png" alt="计数器示意图"><br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAER2VVpu9tV56eNUSLqeV3wUtFJIKXZnQACchgAAjDm4FUEbK72OAyR2zoE.png" alt="CTR 模式示意图"></p><pre><code class="lang-python">def aes_ctr_encrypt(pt,key, nonce):    aes = AES.new(key, AES.MODE_CTR, nonce=nonce)    ct = aes.encrypt(pt)    return ctdef aes_ctr_decrypt(ct, key, nonce):    aes = AES.new(key, AES.MODE_CTR, nonce=nonce)    pt = aes.decrypt(ct)    return pt</code></pre><p>上一章：<a href="https://lr2006-robot.github.io/myblog.github.io/post/2013.html">加解密模式分析 - AES 加密算法</a> 👈<br>下一章：<a href="https://lr2006-robot.github.io/myblog.github.io/post/2015.html">加解密模式分析 - Shamir 门限秘密共享算法</a> 👈<br>回到开始：<a href="http://lr2006-robot.github.io/myblog.github.io/about/">关于我</a> 👈</p><p>相关链接：</p>]]>
    </content>
    <id>https://lr2006-robot.github.io/myblog.github.io/post/2014.html</id>
    <link href="https://lr2006-robot.github.io/myblog.github.io/post/2014.html"/>
    <published>2026-03-19T13:30:00.000Z</published>
    <summary>
      <![CDATA[<p>前面我们已经分析了经典的分组加密算法 DES 和 AES，这两个算法都是分组密码的代表。分组密码是对称加密算法中最重要的一类算法，但是我们经过前面学习会发现，分组密码算法本身只能对固定大小的块进行加密，而实际应用中我们需要加密的数据往往是任意长度的，这就引入了分组模式（Bl]]>
    </summary>
    <title>加解密模式分析 - 分组模式分析</title>
    <updated>2026-03-19T11:25:15.071Z</updated>
  </entry>
  <entry>
    <author>
      <name>Dorange</name>
    </author>
    <category term="加解密模式分析" scheme="https://lr2006-robot.github.io/myblog.github.io/categories/%E5%8A%A0%E8%A7%A3%E5%AF%86%E6%A8%A1%E5%BC%8F%E5%88%86%E6%9E%90/"/>
    <category term="密码学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="加解密" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%8A%A0%E8%A7%A3%E5%AF%86/"/>
    <category term="现代密码" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E7%8E%B0%E4%BB%A3%E5%AF%86%E7%A0%81/"/>
    <category term="对称加密" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86/"/>
    <category term="分组密码" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%88%86%E7%BB%84%E5%AF%86%E7%A0%81/"/>
    <content>
      <![CDATA[<p>AES（Advanced Encryption Standard）是当前最广泛使用的对称分组加密算法，由美国国家标准与技术研究院（NIST）于 2001 年正式发布。AES 以其卓越的安全性和高效的性能，成为现代加密协议中的核心组件，广泛应用于 TLS、IPsec、Wi-Fi 等众多安全通信协议中。</p><p>在本章中，我们将深入剖析 AES 加密算法的核心原理与实现细节。我们将从分组密码的基础特性出发，逐步拆解 AES 的 10、12 或 14 轮加密过程，详细分析其状态矩阵的构造、每轮的操作步骤（SubBytes、ShiftRows、MixColumns、AddRoundKey）以及最终的密钥扩展机制，全面理解 AES 的加密过程和安全性分析。</p><h2><span id="aes-加密算法">AES 加密算法 🫡</span></h2><p>AES 块长度为 128 位，密钥长度可以是 128 位、192 位或 256 位，分别对应 10、12 或 14 轮加密过程，明文按照 128 位分组，加密得到 128 位密文。</p><p>AES 算法不同于 DES 的 Feistel 结构，而是采用了 Substitution-Permutation Network（SPN）结构，直接使用四个 “算法层” 对整个数据块进行变换。<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAER2KNpu74aq0GiuuOxDMjaz9l-EPNrTQACyBsAAjDm2FUAAWWbdjemJ3o6BA.png" alt="AES 加密算法示意图"></p><h3><span id="密钥扩展key-expansion">密钥扩展（Key Expansion）</span></h3><p>标准的 128 位 AES 密钥对应由 11 组子密钥，分别在一开始和每一轮参与密钥加法层的运算。</p><p>子密钥的生成是一列为单位的，一列是 4 字节（32 位），每一轮需要 4 列（128 位）的子密钥。对于 128 位密钥，原始密钥占用前 4 列，后续的 7 组子密钥通过迭代计算得到，储存在 $w[0..43]$ 的子密钥数组中。</p><p>具体来说，子密钥的生成过程如下：</p><ol><li>将原始密钥分成 4 列，每列 4 字节，填充到子密钥数组的前 4 列。</li><li>从第 5 列开始，依次计算每一列的子密钥：<ul><li>如果当前列索引 $i$ 是 4 的倍数，则进行 G 函数特殊处理，然后将前一列的子密钥与当前列的前一列的子密钥进行异或运算。</li><li>否则，直接将前一列的子密钥与当前列的前一列的子密钥进行异或运算。<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAER2QFpu81_33zMfcUPabnkxJBF1ZUmSgACYxwAAjDm2FXAuR8UMNNyNDoE.png" alt="密钥扩展算法"></li></ul></li></ol><p>G 函数是为了增加密钥扩展的非线性和消除 AES 中的对称性（轮常量的参与），进行如下操作：</p><ol><li>将输入的 4 字节进行循环左移（RotWord），即将第一个字节移到最后。</li><li>将每个字节通过 S 盒进行字节替代（SubWord），得到一个新的 4 字节。</li><li>将第一个字节与一个轮常量（Rcon）进行异或运算，得到最终的 4 字节输出。<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAER2Rdpu883uSE0QJsVJYupto5VEcEZYwACfBwAAjDm2FW5i3jaZUmUqzoE.png" alt="密钥扩展中的 G 函数"></li></ol><pre><code class="lang-python">@staticmethoddefkey_expansion(k,r): #fips-197 Figure 11    k = list(k) # in case k is bytes    Nk = len(k) // 4    subkeys=[k[i:i+4]foriinrange(0,4*Nk,4*Nk,4)]    i = Nk    while i &lt; 4*(r+1):        t=subkeys[i-1]    if i % Nk == 0:        tt=AES.sub_word(AES.rot_word(t))        t=[tt[0] ^AES.Rcon[i // Nk]] + tt[1:]    elif Nk &gt; 6 and i % Nk == 4:        t = AES.sub_word(t)    subkeys.append(AES.word_xor(subkeys[i - Nk], t))    i += 1return subkeys</code></pre><h3><span id="密钥加密层key-additon-layer">密钥加密层（Key Additon Layer）</span></h3><p>AES 的内部状态是由一个 4x4 的字节矩阵（State）构成的，每个元素是一个字节（8 位）。输入的 16 字节按照如下方式排列：</p><script type="math/tex; mode=display">\begin{bmatrix}s_1 & s_5 & s_9 & s_{13} \\s_2 & s_6 & s_{10} & s_{14} \\s_3 & s_7 & s_{11} & s_{15} \\s_4 & s_8 & s_{12} & s_{16}\end{bmatrix}</script><p>将排列好的明文和子密钥逐字节异或，并将结果输出：</p><script type="math/tex; mode=display">\begin{bmatrix} s_1 & s_5 & s_9 & s_{13} \\ s_2 & s_6 & s_{10} & s_{14} \\ s_3 & s_7 & s_{11} & s_{15} \\ s_4 & s_8 & s_{12} & s_{16} \end{bmatrix} \oplus \begin{bmatrix} k_1 & k_5 & k_9 & k_{13} \\ k_2 & k_6 & k_{10} & k_{14} \\ k_3 & k_7 & k_{11} & k_{15} \\ k_4 & k_8 & k_{12} & k_{16} \end{bmatrix} = \begin{bmatrix} c_1 & c_5 & c_9 & c_{13} \\ c_2 & c_6 & c_{10} & c_{14} \\ c_3 & c_7 & c_{11} & c_{15} \\ c_4 & c_8 & c_{12} & c_{16} \end{bmatrix}</script><pre><code class="lang-python">@staticmethoddef add_round_key(s, k):    for i in range(16):        s[i] ^= k[i]</code></pre><h3><span id="字节替代层substitution-layer">字节替代层（Substitution Layer）</span></h3><p>让输入的每一个字节，通过 S 盒代换（映射）到另外一个字节此处的 S 盒是可以经过每种方式计算出来的，也可以直接使用计算好的进行代换。这个比较基础，我就不展开了。</p><pre><code class="lang-python">@staticmethoddef sub_bytes(s):    for i in range(16):        s[i] = AES.Sbox[s[i]]</code></pre><h3><span id="扩散层diffusion-layer">扩散层（Diffusion Layer）</span></h3><p>扩散层包括行移位层（Shift Rows层）和列混淆层（Mix Column层）。行位移和列混淆都是 AES 的混淆层，目的是为了将单个字节的变换扩散到整个状态矩阵中，增加算法的安全性。</p><p>行移位层：对于 4x4 的矩阵，在做行移位时，第一行保持不变，第二行往左移动一格，第三行左移两格，第四行左移三格。</p><script type="math/tex; mode=display">\begin{bmatrix} s_1 & s_5 & s_9 & s_{13} \\ s_2 & s_6 & s_{10} & s_{14} \\ s_3 & s_7 & s_{11} & s_{15} \\ s_4 & s_8 & s_{12} & s_{16} \end{bmatrix} \Longrightarrow \begin{bmatrix} s_1 & s_5 & s_9 & s_{13} \\ s_6 & s_{10} & s_{14} & s_2 \\ s_{11} & s_{15} & s_3 & s_7 \\ s_{16} & s_4 & s_8 & s_{12} \end{bmatrix}</script><p>列混淆层：将整个字节矩阵乘上一个列混淆矩阵(有限域上的矩阵运算)，相当于正常矩阵运算结果取模。</p><script type="math/tex; mode=display">\begin{bmatrix} s_1 & s_5 & s_9 & s_{13} \\ s_2 & s_6 & s_{10} & s_{14} \\ s_3 & s_7 & s_{11} & s_{15} \\ s_4 & s_8 & s_{12} & s_{16} \end{bmatrix}  \cdot \begin{bmatrix} 2 & 3 & 1 & 1 \\ 1 & 2 & 3 & 1 \\ 1 & 1 & 2 & 3 \\ 3 & 1 & 1 & 2 \end{bmatrix}</script><pre><code class="lang-python">@staticmethoddef shift_rows(s):    s[i] = list(s[0::5] + s[4::5] + s[3:4:5] + s[8::5] + s[2:8:5] +s[12::5] + s[1:12:5])@staticmethoddef mix_columns(s):    def xtime(a):        return((((a &lt;&lt; 1) ^ 0x1B) &amp; 0XFF) if (a &amp; 0x80) else (a &lt;&lt;1))    for i in range(4):        t=s[4*i] s[4*i+1] ^s[4*i+2]^s[4*i+3]        u=s[4*i]        s[4*i] ^=t xtime(s[4*i] ^ s[4*i+1])        s[4*i+1] ^= t xtime(s[4*i+1] ^ s[4*i+2])        s[4*i+2] ^= t xtime(s[4*i+2] ^ s[4*i+3])        s[4*i+3] ~ t xtime(s[4*i+3] ^ u)</code></pre><h3><span id="轮加密过程">轮加密过程</span></h3><p>这里就是一开始的示意图了，不过要注意的是最后一轮没有 Mix Column 层。</p><pre><code class="lang-python"># startr=0k_sch=self.subkeys[0] + self.subkeys[1] + self.subkeys[2] + self.subkeys[3]state = list(msg)AES.add_round_key(state,k_sch)# round 1~rounds&#39;-1for r in range(1,self.rounds):    AES.sub_bytes(state)    AES.shift_rows(state)    AES.mix_columns(state)    k_sch=self.subkeys[4*r] + self.subkeys[4*r+1] + self.subkeys[4*r+2] + self.subkeys[4*r+3]    AES.add_round_key(state,k_sch)# the last roundr = self.roundsAES.sub_bytes(state)AES.shift_rows(state)k_sch=self.subkeys[-4] + self.subkeys[-3] +self.subkeys[-2] + self.subkeys[-1]AES.add_round_key(state,k_sch)# convert &#39;list&#39;stateto&#39;bytes&#39;outputoutput=bytes(state)return output</code></pre><h2><span id="aes-加解密">AES 加解密 😆</span></h2><p>AES 的加密和解密过程非常相似，主要区别在于解密过程中需要使用逆向的操作顺序和逆向的 S 盒、逆向的行移位以及逆向的列混淆。由于 AES 是一个 SPN 结构的算法，解密过程需要对每一轮的操作进行逆向处理，这里就不展开了，感兴趣的同学可以参考 AES 的官方文档或者相关的加密算法教材。</p><pre><code class="lang-python"># 我们可以使用 Python 的 Crypto 库来实现 AES 的加密和解密，以下是一个简单的示例：from Crypto.Cipher import AESkey = b&quot;&quot;aes = AES.new(key,mode=AES.MODE_ECB)plaintext = b&quot;&quot;ciphertext = aes.encrypt(plaintext)aes.decrypt(ciphertext)</code></pre><p>上一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/2012.html">加解密模式分析 - DES 加密算法</a> 👈<br>下一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/2014.html">加解密模式分析 - 分组模式解析</a> 👈<br>回到开始：<a href="http://lr2006-robot.github.io/myblog.github.io/about/">关于我</a> 👈</p><p>相关链接：</p>]]>
    </content>
    <id>https://lr2006-robot.github.io/myblog.github.io/post/2013.html</id>
    <link href="https://lr2006-robot.github.io/myblog.github.io/post/2013.html"/>
    <published>2026-03-18T14:30:00.000Z</published>
    <summary>
      <![CDATA[<p>AES（Advanced Encryption Standard）是当前最广泛使用的对称分组加密算法，由美国国家标准与技术研究院（NIST）于 2001 年正式发布。AES 以其卓越的安全性和高效的性能，成为现代加密协议中的核心组件，广泛应用于 TLS、IPsec、Wi-F]]>
    </summary>
    <title>加解密模式分析 - AES 加密算法</title>
    <updated>2026-03-19T10:36:24.871Z</updated>
  </entry>
  <entry>
    <author>
      <name>Dorange</name>
    </author>
    <category term="加解密模式分析" scheme="https://lr2006-robot.github.io/myblog.github.io/categories/%E5%8A%A0%E8%A7%A3%E5%AF%86%E6%A8%A1%E5%BC%8F%E5%88%86%E6%9E%90/"/>
    <category term="密码学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="加解密" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%8A%A0%E8%A7%A3%E5%AF%86/"/>
    <category term="现代密码" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E7%8E%B0%E4%BB%A3%E5%AF%86%E7%A0%81/"/>
    <category term="对称加密" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86/"/>
    <category term="分组密码" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%88%86%E7%BB%84%E5%AF%86%E7%A0%81/"/>
    <content>
      <![CDATA[<p>从这一章开始，我们进入分组密码的世界。在此之前，建议先回到 <a href="http://lr2006-robot.github.io/myblog.github.io/post/2.html">密码学前置内容 - 密码分类与简介</a> 复习一下分组密码的基本概念和构成，方便理解。</p><p>分组密码是对称加密算法中最重要的一类算法，它将明文分成固定大小的块进行加密，常见的分组大小有 64 位和 128 位。DES（Data Encryption Standard）是分组密码的经典代表之一，虽然现在已经被认为不安全，但它在密码学史上具有重要地位。</p><p>话不多说，我们直接进入 DES 加密算法的核心原理与实现细节。我们将从 DES 的基本结构出发，逐步拆解其初始置换、16 轮 Feistel 结构、子密钥生成等关键步骤，深入理解 DES 的加密过程和安全性分析。</p><h2><span id="des-加密算法">DES 加密算法 🫡</span></h2><p>DES 块长度为 64 位，密钥长度为 64 位（其中 8 位为校验位，实际有效 56 位），明文按照 64 位分组，加密得到 64 位密文。DES 对明文进行 16 轮加密运算，每一轮都有一个相应的子密钥参与（子密钥由密钥扩展算法计算得出）。此外，开头和结尾分别有初始置换和最终置换的操作，用于适应硬件电路的算法实现。</p><p>这里给出一个简单示意图：<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAER1BZpuqVfgDDqtybVwFvh1kzFT5SRQgAC8SAAAud12VWtSsfTWToZPjoE.jpg" alt="DES 加密算法示意图"></p><h3><span id="密钥扩展算法">密钥扩展算法</span></h3><p>一开始，我们填入 56 位的密钥，经过系统按奇校验规则，在每 7 位后插入 1 位校验位（共 8 位），补成 64 位 DES 标准密钥。DES 的密钥扩展算法将 64 位的密钥扩展为 16 个 48 位的子密钥，每个子密钥用于 DES 的每一轮加密运算。这个过程包括以下几个步骤：</p><ol><li>先通过 PC-1（Permuted Choice 1）置换去除掉 8 位校验位，得到原来 56 位的密钥。</li><li>将 56 位的密钥分成左右 28 位两部分，分别记为 C 和 D。</li><li>连续 16 轮运算，每一轮分别先对 C 和 D 进行左移操作（根据轮数不同，左移 1 位或 2 位），然后通过 PC-2（Permuted Choice 2）置换从 C 和 D 中选取 48 位作为当前轮的子密钥。</li><li>最终得到 16 个 48 位的子密钥，分别用于 DES 的 16 轮加密运算。<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAER0_Vpup34dnqZGMCWPda05vef3IWaoAACsSAAAud12VXdC93yWiT0tDoE.png" alt="密钥扩展算法"></li></ol><pre><code class="lang-python"># 代码仅作为表意伪代码，后续一样subkey=[]if len(bkey)== 64:    #PC-1    bkey = PC_1(bkey)elif len(bkey) == 56:    raise ValueError(&quot;key must be 56-bit or 64-bit in length&quot;) #divide the block into two halvesCi, Di = bkey[:28], bkey[28:]for i in range(16):    #Left Rotation    Ci, Di = LR(Ci, Di, i)    #PC-2    subkey.append(PC_2(Ci + Di))return subkey</code></pre><h3><span id="初始置换和最终置换initial-and-final-permutation">初始置换和最终置换（Initial and Final Permutation）</span></h3><p>初始置换（IP）和最终置换（IP^-1）是 DES 加密算法中的两个固定置换操作。初始置换在加密开始时对输入的明文进行重新排列，而最终置换在加密结束时对输出的密文进行重新排列。就是简单地根据置换表，吧长度为 64 位的 block 每一个位置进行变换。<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAER09RpupqCvf8FuHal9RrPbTPRQMytSQACiCAAAud12VU-wyhiluG2JToE.png" alt="IP 和 FP"></p><pre><code class="lang-python">IP_table=[    58,50,42,34,26,18,10,2,    60,52,44,36,28,20,12,4,    62,54,46,38,30,22,14,6,    64,56,48,40,32,24,16,8,    57,49,41,33,25,17,9,1,    59,51,43,35,27,19,11,3,    61,53,45,37,29,21,13,5,    63,55,47,39,31,23,15,7,]def IP(block):    result=[]    for i in range(len(IP_table)):        result.append(block[IP_table[i]-1])    return resultFP_table=[    40,8,48,16,56,24,64,32,    39,7,47,15,55,23,63,31,    38,6,46,14,54,22,62,30,    37,5,45,13,53,21,61,29,    36,4,44,12,52,20,60,28,    35,3,43,11,51,19,59,27,    34,2,42,10,50,18,58,26,    33,1,41,9,49,17,57,25,]def FP(block):    result = []    for i in range(len(FP_table)):        result.append(block[FP_table[i]-1])    return result</code></pre><h3><span id="des-轮加密过程">DES 轮加密过程</span></h3><p>DES 的加密过程包括 16 轮 Feistel 结构的加密运算，每一轮的输入是前一轮的输出。每一轮的加密过程如下：</p><ol><li>将明文分成左右两部分，分别记为 $L_0$ 和 $R_0$。</li><li>在每一轮中，计算</li></ol><script type="math/tex; mode=display">\begin{aligned}L_{i+1} &= R_{i} \\R_{i+1} &= L_{i} \oplus F(R_{i}, K_{i})\end{aligned}</script><p>其中 $F$ 是轮函数，$K_i$ 是当前轮的子密钥。</p><ol><li>经过 16 轮加密后，将左右两部分合并，并通过最终置换得到密文。<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAER1AtpuqJFz73gu41gfqzeEiVG0c3pTQAC1yAAAud12VWmeZjWXBKoLDoE.png" alt="DES 轮加密过程"></li></ol><pre><code class="lang-python"> #Initial permutationm = IP(m) #divide the block into two 32-bit halvesLi, Ri = m[:32], m[32:] #16 roundsfor i in range(16):    Li, Ri = Ri, BlockXor(Li, Feistel(Ri, subkey[i])) #merge the two divided half block which is 32-bit into one 64-bit blockm = Ri + Li # There is a need to change order of the final two halves #Final permutationm = FP(m)</code></pre><h3><span id="轮函数feistel-function-费斯妥函数">轮函数（Feistel Function 费斯妥函数）</span></h3><p>这里我们详细讲解轮函数，这种结构的函数有一个更专业的命名，叫做 Feistel Function。这个函数不仅是 DES 加密算法中每一轮加密运算的核心部分，也是许多其他分组密码算法的基础。</p><p>轮函数的输入包括一个 32 位的数据块和一个 48 位的子密钥，输出也是一个 32 位的数据块。轮函数的主要步骤如下：</p><ol><li>先通过一个扩展置换（Expansion Permutation）将 32 位的数据块扩展为 48 位，以便与子密钥进行异或运算。</li><li>将扩展后的数据块与当前轮的子密钥进行异或运算，得到一个 48 位的结果。</li><li>将异或结果分成 8 个 6 位的小块，每个小块通过一个 S-Box（Substitution Box）进行替换，得到 32 位的输出。</li><li>最后通过一个 P-Box（Permutation Box）对替换后的结果进行置换，得到最终的轮函数输出。<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAER1AVpuqAOHH1DAQxEnVf-l4KOPO0uJwACvCAAAud12VXawOklezTQPzoE.png" alt="轮函数"></li></ol><pre><code class="lang-python">def Feistel(HalfBlock, subkey):    eHalfBlock = Expansion(HalfBlock)    xHalfBlock = BlockXor(eHalfBlock, subkey)    sHalfBlock = Substitution(xHalfBlock)    return Permutation(sHalfBlock)</code></pre><h2><span id="des-加解密">DES 加解密 🙂</span></h2><p>在已知密文和 16 个子密钥的情况下，由于轮函数以及置换的可逆性，仅需要在轮函数作用时使用逆序的子密钥即可，这也是 DES 加密算法的一个重要特点（Feistel 结构的优势），使得加密和解密过程非常相似，极大地简化了算法的实现。<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAER1BBpuqQdmdR1qvayQuGJBJW0xG4RlwAC5iAAAud12VULCG9zTsb8pjoE.png" alt="DES 加解密"></p><pre><code class="lang-python"># 实际代码实现中，我们可以使用 Python 的 Crypto 库来进行 DES 加密和解密，以下是一个简单的示例：from Crypto.Cipher import DESkey = &#39;&#39;des = DES.new(key,DES.MODE_ECB)plaintext = b&#39;&#39;cipher = des.encrypt(plaintext)des.decrypt(cipher)</code></pre><h2><span id="补充3des-加密算法">补充：3DES 加密算法 🫢</span></h2><p>由于 DES 的安全性问题，3DES（Triple DES）被提出作为 DES 的增强版本。3DES 通过对数据进行三次 DES 加密来提高安全性，常见的模式有 EDE（Encrypt-Decrypt-Encrypt）和 EEE（Encrypt-Encrypt-Encrypt）。虽然 3DES 在一定程度上提高了安全性，但由于其效率较低，现在已经逐渐被 AES 等更现代的加密算法所取代。</p><p>具体做法看名字也可以知道，拿 EEE 模式举例，首先准备三个密钥 $K_1$、$K_2$ 和 $K_3$，然后用 $K_1$ 对明文进行 DES 加密，得到中间密文；再用 $K_2$ 对中间密文进行 DES 加密，得到新的中间密文；最后用 $K_3$ 对新的中间密文进行 DES 加密，得到最终的密文。解密过程则是反过来进行。</p><p>EDE 模式则是先用 $K_1$ 对明文进行 DES 加密，得到中间密文；再用 $K_2$ 对中间密文进行 DES 解密，得到新的中间密文；最后用 $K_3$ 对新的中间密文进行 DES 加密，得到最终的密文。解密过程同样是反过来进行。<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAER1BhpuqZMwM7udFfNhKM4zRaELDkahAAC9CAAAud12VVuKiCB2F671ToE.png" alt="3DES 加密算法"></p><p>上一章：<a href="https://lr2006-robot.github.io/myblog.github.io/post/2011.html">加解密模式分析 - Salsa20 加密算法</a> 👈<br>下一章：<a href="https://lr2006-robot.github.io/myblog.github.io/post/2013.html">加解密模式分析 - AES 加密算法</a> 👈</p><p>相关链接：<br><a href="http://lr2006-robot.github.io/myblog.github.io/post/2.html">密码学前置内容 - 密码分类与简介</a> 👈</p>]]>
    </content>
    <id>https://lr2006-robot.github.io/myblog.github.io/post/2012.html</id>
    <link href="https://lr2006-robot.github.io/myblog.github.io/post/2012.html"/>
    <published>2026-03-18T13:30:00.000Z</published>
    <summary>
      <![CDATA[<p>从这一章开始，我们进入分组密码的世界。在此之前，建议先回到 <a href="http://lr2006-robot.github.io/myblog.github.io/post/2.html">密码学前置内容 - 密码分类与简介</a> 复习一下分组密码的基本概念和构成]]>
    </summary>
    <title>加解密模式分析 - DES 加密算法</title>
    <updated>2026-03-19T10:31:47.988Z</updated>
  </entry>
  <entry>
    <author>
      <name>Dorange</name>
    </author>
    <category term="加解密模式分析" scheme="https://lr2006-robot.github.io/myblog.github.io/categories/%E5%8A%A0%E8%A7%A3%E5%AF%86%E6%A8%A1%E5%BC%8F%E5%88%86%E6%9E%90/"/>
    <category term="密码学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="加解密" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%8A%A0%E8%A7%A3%E5%AF%86/"/>
    <category term="现代密码" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E7%8E%B0%E4%BB%A3%E5%AF%86%E7%A0%81/"/>
    <category term="对称加密" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86/"/>
    <category term="流密码" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E6%B5%81%E5%AF%86%E7%A0%81/"/>
    <content>
      <![CDATA[<p>Salsa20 是一种基于 Salsa20 的流密码算法，由 Daniel J. Bernstein 设计。它以其速度快、安全性高、实现简洁而闻名，广泛应用于安全通信、加密存储等场景。</p><p>在本章中，我们将深入剖析 Salsa20 加密算法的核心原理与实现细节。我们将从流密码的基础特性出发，逐步拆解其核心的 20 轮加密过程，详细分析其状态矩阵的构造、每轮的操作步骤以及最终的密钥流生成机制，全面理解 Salsa20的加密过程和安全性分析。</p><h2><span id="salsa20-加密算法">Salsa20 加密算法 😋</span></h2><p>Salsa20 的核心原理基于一个 4x4 的状态矩阵，包含了常量、密钥、计数器和随机数。每轮加密过程中，算法通过一系列的加法、异或和位移操作来混合这些元素，从而生成一个伪随机的密钥流。这个密钥流随后与明文进行异或操作，产生密文。<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAER0xxpuoWpgkZ2KnaHnwIkYqjp-w9zPgACrR8AAud12VXwe4wcehZHxjoE.png" alt="Salsa20加密流程图"></p><h3><span id="初始状态矩阵">初始状态矩阵</span></h3><p>Salsa20 的初始状态是一个 4×4 的 32 位无符号整数矩阵（共 512 位），由固定常量（”expand 32-byte k” 的 ASCII 编码）、256 位密钥、计数器、随机数（nonce） 四部分按固定位置填充构成：</p><script type="math/tex; mode=display">\begin{bmatrix}c_0 & k_0 & k_1 & k_2 \\k_3 & c_1 & n_0 & n_1 \\t_0 & t_1 & c_2 & k_4 \\k_5 & k_6 & k_7 & c_3\end{bmatrix}</script><p>其中：</p><ul><li>$c_0, c_1, c_2, c_3$ 是常量，分别对应 “expa”, “nd 3”, “2-by”, “te k” 的 ASCII 编码。</li><li>$k_0, k_1, k_2, k_3, k_4, k_5, k_6, k_7$ 是密钥的 8 个 32 位部分。</li><li>$n_0, n_1$ 是随机数（nonce）的 2 个 32 位部分。</li><li>$t_0, t_1$ 是计数器的 2 个 32 位部分。</li></ul><h3><span id="qrquarter-round-操作">QR(Quarter Round) 操作</span></h3><p>上图中的所谓 “置换” 并不是简单的矩阵转置，而是指对状态矩阵进行一系列的 QR（Quarter Round）操作。QR 函数对 4 个 32 比特的单词（ $a$、$b$、$c$ 和 $d$）进行变形：</p><script type="math/tex; mode=display">\begin{aligned}b  &= b \oplus ((a + d) \lll 7) \\c  &= c \oplus ((b + a) \lll 9) \\d  &= d \oplus ((c + b) \lll 13) \\a  &= a \oplus ((d + c) \lll 18)\end{aligned}</script><p>符号 $\lll$ 指的是单词向左移动一定比特，移动的位数可以为 1 到 31 之间的任何值（对于32比特的单词来说）。符号 $\oplus$ 是按位异或操作，符号 $+$ 是无符号整数的加法。</p><h3><span id="20-轮加密过程">20 轮加密过程</span></h3><p>Salsa20 的加密过程包含 20 轮，每轮由 4 个 QR 操作组成，分别作用于状态矩阵的不同部分。每轮结束后，状态矩阵会被更新，最终生成一个新的状态矩阵。</p><p>规则是奇数轮对列进行 QR 操作，偶数轮对行进行 QR 操作。</p><p>列操作：</p><script type="math/tex; mode=display">\begin{aligned}&QR(x_0, x_4, x_8, x_{12}) \\&QR(x_1, x_5, x_9, x_{13}) \\&QR(x_2, x_6, x_{10}, x_{14}) \\&QR(x_3, x_7, x_{11}, x_{15})\end{aligned}</script><p>行操作：</p><script type="math/tex; mode=display">\begin{aligned}&QR(x_0, x_5, x_{10}, x_{15}) \\&QR(x_1, x_6, x_{11}, x_{12}) \\&QR(x_2, x_7, x_8, x_{13}) \\&QR(x_3, x_4, x_9, x_{14})\end{aligned}</script><p>细心的同学可能会有疑问，明明列操作是作用与矩阵的行上的，行操作是作用于矩阵的列上的，为什么叫列操作和行操作呢？这是因为命名看的是「扩散目标方向」，不是「操作时的分组方向」，这是 Salsa20 独有的历史设计遗留问题。 🤫</p><h3><span id="密钥流生成与加密">密钥流生成与加密</span></h3><p>经过 20 轮后，最终的状态矩阵会与初始状态矩阵进行逐位相加，每一位都模 $2^{32}$，得到一个新的状态矩阵。这个新的状态矩阵 512 位（16 个 32 位单词）就是生成的密钥流。最后，密钥流与明文进行异或操作，得到密文。</p><p>当然，我们加密过程中，不一定要一次性处理所有的明文数据。Salsa20 允许我们分块处理明文，每次生成一个新的密钥流块（512 位），与对应的明文块进行异或操作，然后计数器递增，进入下一轮加密，直到所有明文都被加密完成。</p><h2><span id="chacha20-加密算法">ChaCha20 加密算法 😇</span></h2><p>ChaCha20 是 Salsa20 的一个变体，由同一位设计者 Daniel J. Bernstein 开发。它解决了原算法扩散效率、硬件适配性等问题，被纳入 IETF 标准（RFC 7539），广泛应用于 TLS 1.3、WireGuard、SSH 等主流安全协议。</p><p>ChaCha20 的核心原理与 Salsa20 类似，仍然基于一个 4x4 的状态矩阵和 QR 操作，但在每轮的操作顺序和参数选择上进行了优化，以提高安全性和性能。ChaCha20 的加密过程同样包含 20 轮，每轮由 4 个 QR 操作组成，但它采用了不同的操作顺序和参数设置，使得算法更适合现代处理器架构，具有更好的性能表现。</p><h3><span id="初始状态矩阵">初始状态矩阵</span></h3><p>ChaCha20 的初始状态矩阵与 Salsa20 类似，也是一个 4×4 的 32 位无符号整数矩阵，但计数器的设置有所不同， ChaCha20 只有 1个 32 位的计数器：</p><script type="math/tex; mode=display">\begin{bmatrix}c_0 & c_1 & c_2 & c_3 \\k_0 & k_1 & k_2 & k_3 \\k_4 & k_5 & k_6 & k_7 \\t_0 & n_0 & n_1 & n_2\end{bmatrix}</script><p>其中：</p><ul><li>$c_0, c_1, c_2, c_3$ 是常量，分别对应 “expa”, “nd 32”, “-byte”, “ k” 的 ASCII 编码。</li><li>$k_0, k_1, k_2, k_3, k_4, k_5, k_6, k_7$ 是密钥的 8 个 32 位部分。</li><li>$t_0$ 是计数器的 32 位部分。</li><li>$n_0, n_1, n_2$ 是随机数（nonce）的 3 个 32 位部分。</li></ul><h3><span id="qrquarter-round-操作">QR(Quarter Round) 操作</span></h3><p>ChaCha20 的 QR 操作与 Salsa20 类似，但在每轮的操作上进行了优化，以提高安全性和性能。ChaCha20 的加密过程同样包含 20 轮，每轮由 4 个 QR 操作组成，但它采用了不同的操作，奇数轮对列进行 QR 操作，偶数轮对对角线进行 QR 操作。</p><p>列操作和 Salsa20 一致，对角线操作如下：</p><script type="math/tex; mode=display">\begin{aligned}&QR(x_0, x_5, x_{10}, x_{15}) \\&QR(x_1, x_6, x_{11}, x_{12}) \\&QR(x_2, x_7, x_8, x_{13}) \\&QR(x_3, x_4, x_9, x_{14})\end{aligned}</script><p>什么！明明和前面的行操作一样，为什么叫对角线操作？这是因为一开始设计的时候，ChaCha20 的操作顺序是按照顺序进行的，后来为了兼容 Salsa20 的命名习惯，才把它叫做对角线操作了，这也是 ChaCha20 独有的历史设计遗留问题。 🤫</p><p>剩下的部分和 Salsa20 基本一致，就不多说了。</p><p>上一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/2010.html">加解密模式分析 - RC4 加密算法</a> 👈<br>下一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/2012.html">加解密模式分析 - DES 加密算法</a> 👈<br>回到开始：<a href="http://lr2006-robot.github.io/myblog.github.io/about/">关于我</a> 👈</p><p>相关链接：</p>]]>
    </content>
    <id>https://lr2006-robot.github.io/myblog.github.io/post/2011.html</id>
    <link href="https://lr2006-robot.github.io/myblog.github.io/post/2011.html"/>
    <published>2026-03-17T14:30:00.000Z</published>
    <summary>
      <![CDATA[<p>Salsa20 是一种基于 Salsa20 的流密码算法，由 Daniel J. Bernstein 设计。它以其速度快、安全性高、实现简洁而闻名，广泛应用于安全通信、加密存储等场景。</p>
<p>在本章中，我们将深入剖析 Salsa20 加密算法的核心原理与实现细节。我]]>
    </summary>
    <title>加解密模式分析 - Salsa20 加密算法</title>
    <updated>2026-03-19T08:59:19.765Z</updated>
  </entry>
  <entry>
    <author>
      <name>Dorange</name>
    </author>
    <category term="加解密模式分析" scheme="https://lr2006-robot.github.io/myblog.github.io/categories/%E5%8A%A0%E8%A7%A3%E5%AF%86%E6%A8%A1%E5%BC%8F%E5%88%86%E6%9E%90/"/>
    <category term="密码学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="加解密" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%8A%A0%E8%A7%A3%E5%AF%86/"/>
    <category term="现代密码" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E7%8E%B0%E4%BB%A3%E5%AF%86%E7%A0%81/"/>
    <category term="对称加密" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86/"/>
    <category term="流密码" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E6%B5%81%E5%AF%86%E7%A0%81/"/>
    <content>
      <![CDATA[<p>RC4 算法是一种基于伪随机密钥流生成的对称流密码算法，它由 Ronald Rivest 于 1987 年设计，凭借极致简洁的结构和高效的运行性能，曾广泛应用于 TLS、WEP、SSL 等众多经典安全协议中。</p><p>在本章中，我们将深入剖析 RC4 算法的核心原理与实现细节。我们将从流密码的基础特性出发，逐步拆解其密钥调度算法（KSA）与伪随机生成算法（PRGA）的核心流程，但是由于内部设计缺陷，RC4 算法实际上已经在现代安全场景中已被认定为完全不安全，必须禁用。</p><h2><span id="rc4-加密过程">RC4 加密过程 😁</span></h2><p>RC4 加密算法的加密过程可以分为两个主要阶段：密钥调度算法（KSA）和伪随机生成算法（PRGA）。<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAERy21puUmTwdPQGXyDCjARKZuU8wAB3CAAAk0gAAJfvNFVM7k_O_Sb3cU6BA.png" alt="RC4加密"></p><h3><span id="密钥调度算法ksa">密钥调度算法（KSA）</span></h3><p>首先，RC4 用密钥把 0~255 打乱成一个乱序表，我们把这个乱序表叫做 S 盒（Substitution Box），S 盒的初始状态是 $S = [0, 1, 2, \ldots, 255]$，然后通过密钥对 S 盒进行打乱，相当于把短密钥变成均匀混乱的状态。</p><p>具体来说，KSA 的过程如下：</p><ol><li>初始化 S 盒：$S[i] = i$，对于 $i = 0, 1, \ldots, 255$。</li><li>初始化一个长度为 256 的临时数组 $K$，将密钥重复填充到 $K$ 中，直到 $K$ 的长度为 256。</li><li>使用密钥对 S 盒进行打乱：对于 $i = 0$ 到 $255$，执行以下操作：<ul><li>计算 $j = (j + S[i] + K[i]) \mod 256$。</li><li>交换 $S[i]$ 和 $S[j]$ 的值。</li></ul></li></ol><pre><code class="lang-python">Key = [k1, k2, ..., kn]  # 密钥，长度为 nK[i] = Key[i mod len(Key)]j = 0for i in range(256):    j = (j + S[i] + K[i]) % 256    # 计算交换索引j    swap(S[i], S[j])    # 交换S[i]和S[j]</code></pre><p>通过上述步骤，S 盒被密钥打乱，形成一个新的状态，为后续的伪随机生成算法（PRGA）提供基础。</p><h3><span id="伪随机生成算法prga">伪随机生成算法（PRGA）</span></h3><p>在 KSA 之后，RC4 进入 PRGA 阶段，生成一个伪随机密钥流。PRGA 的过程如下：</p><ol><li>初始化两个索引 $i$ 和 $j$，初始值为 0。</li><li>在每次生成一个字节的密钥流时，执行以下操作：<ul><li>$i = (i + 1) \mod 256$。</li><li>$j = (j + S[i]) \mod 256$。</li><li>交换 $S[i]$ 和 $S[j]$ 的值。</li><li>输出密钥流字节：$K = S[(S[i] + S[j]) \mod 256]$。</li></ul></li></ol><p>然后将生成的密钥流字节与明文进行逐字节异或（XOR）操作，得到密文。由于异或操作的可逆性，解密过程与加密过程完全相同。</p><pre><code class="lang-python">i = 0j = 0ciphertext = []# 为每个明文字节生成对应的密钥流字节，并做异或for byte in plaintext:    i = (i + 1) % 256    j = (j + S[i]) % 256    S[i], S[j] = S[j], S[i]    # 生成密钥流字节    k = S[(S[i] + S[j]) % 256]    # 明文与密钥流异或得到密文（RC4 加密/解密核心：异或可逆）    ciphertext.append(byte ^ k)</code></pre><p>通过上述步骤，RC4 生成了一个伪随机密钥流，并将其与明文进行异或操作，完成了加密过程。解密过程则是相同的，只需将密文与相同的密钥流进行异或即可恢复出原始明文。</p><h2><span id="rc4-加密算法实现">RC4 加密算法实现 🥰</span></h2><p>下面是一个简单的 RC4 加密算法的 Python 实现示例：</p><pre><code class="lang-python">def rc4(key: bytes, data: bytes) -&gt; bytes:    # 1. KSA：初始化S盒    S = list(range(256))    j = 0    key_len = len(key)    # 填充K数组并打乱S盒    for i in range(256):        j = (j + S[i] + key[i % key_len]) % 256        S[i], S[j] = S[j], S[i]    # 2. PRGA：生成密钥流并异或    i = j = 0    result = []    for byte in data:        i = (i + 1) % 256        j = (j + S[i]) % 256        S[i], S[j] = S[j], S[i]        t = (S[i] + S[j]) % 256        keystream_byte = S[t]        # 异或得到结果        result.append(byte ^ keystream_byte)    return bytes(result) #测试：加密和解密key = b&quot;secret_key_123&quot;  # 密钥（ bytes类型）plaintext = b&quot;Hello, RC4!&quot;  # 明文ciphertext = rc4(key, plaintext)decrypted_text = rc4(key, ciphertext)print(&quot;密文（十六进制）:&quot;, ciphertext.hex())  # 输出密文（十六进制格式）print(&quot;解密后明文:&quot;, decrypted_text.decode(&quot;utf-8&quot;))  # 输出：Hello, RC4!</code></pre><p>上一章：<a href="https://lr2006-robot.github.io/myblog.github.io/post/2009.html">加解密模式分析 - OTP 与 PRNG</a> 👈<br>下一章：<a href="https://lr2006-robot.github.io/myblog.github.io/post/2011.html">加解密模式分析 - Salsa20 加密算法</a> 👈<br>回到开始：<a href="http://lr2006-robot.github.io/myblog.github.io/about/">关于我</a> 👈</p><p>相关链接：</p>]]>
    </content>
    <id>https://lr2006-robot.github.io/myblog.github.io/post/2010.html</id>
    <link href="https://lr2006-robot.github.io/myblog.github.io/post/2010.html"/>
    <published>2026-03-17T13:30:00.000Z</published>
    <summary>
      <![CDATA[<p>RC4 算法是一种基于伪随机密钥流生成的对称流密码算法，它由 Ronald Rivest 于 1987 年设计，凭借极致简洁的结构和高效的运行性能，曾广泛应用于 TLS、WEP、SSL 等众多经典安全协议中。</p>
<p>在本章中，我们将深入剖析 RC4 算法的核心原理与]]>
    </summary>
    <title>加解密模式分析 - RC4 加密算法</title>
    <updated>2026-03-18T13:26:04.037Z</updated>
  </entry>
  <entry>
    <author>
      <name>Dorange</name>
    </author>
    <category term="加解密模式分析" scheme="https://lr2006-robot.github.io/myblog.github.io/categories/%E5%8A%A0%E8%A7%A3%E5%AF%86%E6%A8%A1%E5%BC%8F%E5%88%86%E6%9E%90/"/>
    <category term="密码学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="加解密" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%8A%A0%E8%A7%A3%E5%AF%86/"/>
    <category term="现代密码" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E7%8E%B0%E4%BB%A3%E5%AF%86%E7%A0%81/"/>
    <category term="对称加密" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86/"/>
    <category term="流密码" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E6%B5%81%E5%AF%86%E7%A0%81/"/>
    <content>
      <![CDATA[<p>从古典密码的单表替换，到现代非对称加密，我们已经分析了多种加解密模式。现在，我们将进入对称加密中的流密码（也叫序列密码）的世界，本章重点分析一次性密码本（One-Time Pad, OTP）和伪随机数生成器（Pseudo-Random Number Generator, PRNG）这两种重要的加解密模式。</p><h2><span id="一次性密码本one-time-pad">一次性密码本（One-Time Pad） 😎</span></h2><p>流密码是一种对称加密算法，它通过将明文与一个密钥流进行逐位异或（XOR）操作来实现加密。流密码的核心思想是使用一个足够长且随机的密钥流来保证加密的安全性。</p><p>一次性密码本（OTP）是一种特殊的流密码，由现代密码学的创始人之一克劳德·香农提出。它使用一个与明文长度相同的完全随机密钥流进行加密，这很臃肿，但却是理论上绝对安全的加密方法。</p><p>OTP 的加密过程非常简单：对于每个明文字符，使用对应位置的密钥字符进行异或操作，得到密文字符。</p><p>OTP 的解密过程也是一样的：对于每个密文字符，使用对应位置的密钥字符进行异或操作，得到明文字符。</p><p>没错，OTP 的加密和解密过程是完全对称的。而且只要密钥流是完全随机的，并且每次使用后都不再使用（即一次性），OTP 就是理论上绝对安全的加密方法。</p><p>然而，OTP 的实际应用非常受限，因为它需要一个与明文长度相同的随机密钥流，并且密钥必须安全地分发和存储，这在现实中是非常困难的。所以，为了节省资源，只给一个种子就可以生成密钥流的伪随机数生成器（PRNG）就应运而生了。😉</p><p>简单示意图：<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAERyU5puRSi3gkJgQsMgnHY2ilHy2rZPgAC9iQAAl-8yVW3iLTuOgABFYQ6BA.png" alt="序列密码加密流程图"></p><p>注意：这里的 PRNG 是指伪随机数生成器，实际上并不安全，不是密码学中的伪随机数生成器（CSPRNG）。我们在后续的章节中会专门分析 CSPRNG。</p><h2><span id="线性同余生成器linear-congruential-generator-lcg">线性同余生成器（Linear Congruential Generator, LCG） 😁</span></h2><p>LCG 是一种简单的伪随机数生成器，它通过一个线性递推式来生成伪随机数序列。LGC 的优点是实现简单，计算速度快，但缺点是生成的伪随机数序列具有较弱的统计性质，容易被预测和攻击。</p><p>LCG 生成伪随机数满足递推式：</p><script type="math/tex; mode=display">X_{n+1} ≡ AX_n + B \mod M</script><p>其中 $A$，$B$，$M$ 为常数且需要 $X_0$ 作为种子，由此递推式生成伪随机数序列，将所有序列拼接，得到密钥流，再逐字节进行异或加密得到密文。</p><p>过程如图所示：<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAERyPlpuREedTP0HwABv767YAGljRoDanoAAqMkAAJfvMlV1twEEkYTybw6BA.png" alt="LCG加密流程图"></p><h3><span id="lcg解密方式一">LCG解密方式一</span></h3><p>在已知常数 $A$，$B$，$M$ 的前提下，若能捕捉到 LCG 生成的一个输出，则可以恢复出状态，并通过递推式预测之后产生的所有随机数</p><script type="math/tex; mode=display">X_{n+1} ≡ A X_n + B \mod M \Longrightarrow X_n ≡ (X_{n+1} - B)A^{-1} \mod M</script><p>通过递推可以得到初始种子 $X_0$ 以及后续所有组成密钥流</p><h3><span id="lcg解密方式二">LCG解密方式二</span></h3><p>在未知 $A$，$B$，已知 $M$ 的情况下，若能捕捉到 LCG 生成的连续两个输出，可以通过建立方程求解 $A$，$B$ 得到递推公式</p><p>联立方程组：</p><script type="math/tex; mode=display">\begin{cases}X_{n+1} ≡ A X_n + B \mod M \\X_{n+2} ≡ A X_{n+1} + B \mod M\end{cases}</script><p>解出 $A$，$B$：</p><script type="math/tex; mode=display">\begin{cases}A ≡ ( X_{n+2} - X_{n+1} )( X_{n+1} - X_n )^{-1} \mod M \\B ≡ X_{n+1} - A X_n \mod M\end{cases}</script><p>恢复出 $A$，$B$ 后，即可通过递推公式恢复后续的密钥流。</p><h2><span id="线性反馈移位寄存器linear-feedback-shift-register-lfsr">线性反馈移位寄存器（Linear Feedback Shift Register, LFSR） 😋</span></h2><p>LFSR 是一种基于线性反馈机制的伪随机数生成器，它通过一个移位寄存器和一个线性反馈函数来生成伪随机数序列。LFSR 的优点是实现简单，计算速度快，但缺点是生成的伪随机数序列具有较弱的统计性质，容易被预测和攻击。</p><p>先来介绍移位寄存器：若干个寄存器串联在一起，每个寄存器存储一个二进制位。每次时钟信号到来时，所有寄存器的内容向右移动一位，最左边的寄存器输入一个新的二进制位，而最右边的寄存器输出一个二进制位。<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAERyoRpuTHeIKMeEKEbwFCOAS3uuSYdJwACLh8AAl-80VUyJnwa5gdHhzoE.png" alt="移位寄存器示意图"></p><p>接着我们讲反馈移位寄存器：在移位寄存器的基础上，加入一个反馈函数。反馈函数根据当前状态中若干选定寄存器位的线性异或结果，计算出新的输入位。每次触发时，所有位右移一位，新的最左位由反馈函数计算得到，从而生成新的状态和输出序列。这个反馈函数可以十分复杂，但 LFSR 要求它必须是线性的。<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAERyaBpuRpdQ93aC9f7C2PL2WqTxvvJrgACER4AAl-80VV2HvBnMdcPFzoE.png" alt="反馈移位寄存器示意图"></p><p>LFSR（线性反馈移位寄存器）是在反馈移位寄存器基础上，设定反馈函数一定是线性的，即反馈函数的输出是输入位的线性组合（异或）。可以将其看作反馈移位寄存器的一种特殊情况，具有更简单的结构和更快的计算速度，但同样存在安全性问题。</p><p>如下图所示的 LFSR 输入的结果，就是输出以及移位前的第一位经过线性反馈函数处理的：<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAERyV5puRXISUshyV3L7wAB30HHnsH0yYEAAgUlAAJfvMlVd3qNr1vnayU6BA.png" alt="线性反馈移位寄存器示意图"></p><p>下面我们给一个经典又简单的 LFSR 例子，反馈函数：</p><script type="math/tex; mode=display">f(s_0, s_1, s_2) = m_4 \cdot s_4 + m_3 \cdot s_3 + m_2 \cdot s_2 + m_1 \cdot s_1</script><p>其中 $[m_4$，$m_3$，$m_2$，$m_1]$ 取值为 $0$ 或 $1$，表示是否参与反馈计算，我们也将其称为抽头。<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAERyb9puRzUazhIJlXcqWz9xaQMxzKP1gACNx4AAl-80VWNNPESfVaVwzoE.png" alt="LFSR示例图"></p><p>我们取 $[m_4$，$m_3$，$m_2$，$m_1] = [1, 0, 0, 1]$，那么就得到了我们前面的示例图中的 LFSR。</p><p>实际上，LFSR 是存在周期的，周期长度取决于反馈函数的选择。对于一个 $n$ 位的 LFSR，如果反馈函数是一个不可约多项式，那么它的周期长度可以达到 $2^n - 1$，这是 LFSR 能够达到的最大周期长度。</p><p>若设置初始状态为 $[s_4, s_3, s_2, s_1] = [1, 1, 0, 1]$，则序列如下图，可以看到出现周期：<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAERykJpuSmeTRKNeCxMZpf7GrOKoOxu2gAC2R4AAl-80VXrVmIFqPjczjoE.jpg" alt="序列图"></p><h3><span id="lfsr解密方式一">LFSR解密方式一</span></h3><p>在已知 LFSR 反馈函数的前提下，如果已知连续 $n$ 位明文和 $n$ 位密文，则可以计算得出 $n$ 位密钥（异或的性质），即为 LFSR 的一个状态。此时根据反馈函数，即可计算出 LFSR 的全部输出，即全部密钥密钥流，从而破解 LFSR。<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAERylNpuSxLzCjL6m4IJc3u9lKotXYgogAC7R4AAl-80VU0Pmqkm7Gd5zoE.png" alt="解密流程图"></p><h3><span id="lfsr解密方式二">LFSR解密方式二</span></h3><p>在未知 LFSR 反馈函数前提下，若获取 $2n$ 位的明文和密文，计算得出 $2n$ 位的密钥 $[k<em>1,k_2,\cdots,k</em>{2n}]$。这 $2n$ 位密钥中有 LFSR 的 $n+1$ 种状态，分别为 $[k<em>1,k_2, \cdots ,k</em>{n}]$，$[k<em>2,k_2,\cdots,k</em>{n+1}]$，$\cdots$ ，$[k<em>{n+1},k</em>{n+2},\cdots,k_{2n}]$。</p><p>这些状态之间存在着互相递推关系，例如 $k<em>{n+1}$ 就是由 $[k_1,k_2,\cdots,k</em>{n}]$ 计算出来的，以此类推，$k<em>{n+i}$ 就是由 $[k_i,k</em>{i+1},\cdots,k_{i+n-1}]$ 计算得出，从而得到 $n$ 个线性方程，进行矩阵运算解出反馈函数。</p><p>方程组如下所示：</p><script type="math/tex; mode=display">\begin{cases}k_{n+1} = k_1 c_n + k_2 c_{n-1} + \cdots + k_n c_1 \\k_{n+2} = k_2 c_n + k_3 c_{n-1} + \cdots + k_{n+1} c_1 \\\quad\quad\quad\quad\quad\cdots \\k_{2n} = k_n c_n + k_{n+1} c_{n-1} + \cdots + k_{2n-1} c_1\end{cases}</script><p>其中 $c_i$ 表示反馈函数中第 $i$ 位的抽头，取值为 $0$ 或 $1$。解出 $c_i$ 后，即可得到反馈函数，从而破解 LFSR。</p><script type="math/tex; mode=display">M \cdot C = \begin{bmatrix} k_1 & k_2 & \cdots & k_n \\ k_2 & k_3 & \cdots & k_{n+1} \\ \vdots & \vdots & \ddots & \vdots \\ k_n & k_{n+1} & \cdots & k_{2n} \end{bmatrix} \cdot \begin{bmatrix} c_n \\ c_{n-1} \\ \vdots \\ c_1 \end{bmatrix} = \begin{bmatrix} k_{n+1} \\ k_{n+2} \\ \vdots \\ k_{2n} \end{bmatrix} = Y</script><p>剩下的就是线性代数的知识了，就不展开了，解出 $C$ 后，即可得到反馈函数，从而破解 LFSR。</p><p>单纯使用 LFSR 进行加密是非常不安全的，因为它的输出序列具有较弱的统计性质，容易被预测和攻击。因此，在实际应用中，通常会将多个 LFSR 进行组合，并通过一个聚合函数计算输出。<br><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAERymJpuS_TE7EduXqyp2P5KIomOPH5JwACCh8AAl-80VUdo1RTXSIEEzoE.png" alt="组合LFSR示意图"></p><p>但是，依然可以用线性特征破解，感兴趣的同学可以自行搜索一下相关资料，这里就不展开了。</p><p>上一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/2008.html">加解密模式分析 - DH 密钥交换协议</a> 👈<br>下一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/2010.html">加解密模式分析 - RC4 加密算法</a> 👈<br>回到开始：<a href="http://lr2006-robot.github.io/myblog.github.io/about/">关于我</a> 👈</p><p>相关链接：<br><a href="http://lr2006-robot.github.io/myblog.github.io/post/2.html">密码学前置内容 - 密码分类与简介</a> 👈</p>]]>
    </content>
    <id>https://lr2006-robot.github.io/myblog.github.io/post/2009.html</id>
    <link href="https://lr2006-robot.github.io/myblog.github.io/post/2009.html"/>
    <published>2026-03-16T13:30:00.000Z</published>
    <summary>
      <![CDATA[<p>从古典密码的单表替换，到现代非对称加密，我们已经分析了多种加解密模式。现在，我们将进入对称加密中的流密码（也叫序列密码）的世界，本章重点分析一次性密码本（One-Time Pad, OTP）和伪随机数生成器（Pseudo-Random Number Generator, P]]>
    </summary>
    <title>加解密模式分析 - OTP 与 PRNG</title>
    <updated>2026-03-17T11:18:44.932Z</updated>
  </entry>
  <entry>
    <author>
      <name>Dorange</name>
    </author>
    <category term="加解密模式分析" scheme="https://lr2006-robot.github.io/myblog.github.io/categories/%E5%8A%A0%E8%A7%A3%E5%AF%86%E6%A8%A1%E5%BC%8F%E5%88%86%E6%9E%90/"/>
    <category term="密码学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="加解密" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%8A%A0%E8%A7%A3%E5%AF%86/"/>
    <category term="现代密码" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E7%8E%B0%E4%BB%A3%E5%AF%86%E7%A0%81/"/>
    <category term="非对称加密" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E9%9D%9E%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86/"/>
    <content>
      <![CDATA[<p>今天我们将分析 Diffie-Hellman 密钥交换协议（DH Key Exchange Protocol），这是一种基于离散对数问题的非对称加密算法。这和之前的 ElGamal 加密算法不同，DH 密钥交换协议的主要目的是让两个通信方在不安全的信道上安全地协商出一个共享的密钥，而不是直接加密消息。</p><p>实际上，经过我们前面的非对称加密算法的学习，会发现加密的时候，如果选择直接加密消息，效率会非常低。例如，当明文过长时，RSA 算法要有足够大的素数才行。</p><p>所以我们通常会使用 DH 密钥交换协议来协商出一个共享的密钥，然后使用对称加密算法（如 AES）来加密实际的消息，这样既保证了安全性，又提高了效率。</p><h2><span id="dh-密钥交换协议过程">DH 密钥交换协议过程 😛</span></h2><p>由于经典的 DH 密钥交换协议就是基于离散对数难题的，我们这里就不再赘述它的数学原理了。</p><p>首先通信双方先共享两个公共参数，模数 $p$ 和生成元 $g$。</p><p>Alice 本地随机生成一个私钥 $a$，并计算 $A ≡ g^a \mod p$，将 $A$ 发送给Bob。</p><p>Bob 收到 $A$ 后，本地随机生成一个私钥 $b$，并计算 $B ≡ g^b \mod p$，将 $B$ 发送给 Alice。<br>同时 Bob 计算共享密钥 $K ≡ A^b ≡ g^{ab} \mod p$。</p><p>Alice 收到 $B$ 后计算共享密钥 $K ≡ B^a ≡ g^{ab} \mod p$。</p><p>最终 Alice 和 Bob 都得到了相同的共享密钥 $K$。以这个 key 作为对称加密算法的密钥，进行后续的通信加密。</p><h2><span id="dh-密钥交换协议实现">DH 密钥交换协议实现 🥰</span></h2><p>下面是一个简单的 DH 密钥交换协议的 Python 实现示例：</p><pre><code class="lang-python">from sage.all import random_prime, power_mod, randint# 1. 生成公共参数bits = 256 # 设置好的位数，生成足够大的素数upper = pow(2,bits) # 上界lower = pow(2,bits - 1) # 下界p = random_prime(upper, lbound = lower) # 生成一个大素数作为模数g = primitive_root(p) # g 是 p 的原根# 2. Alice 生成私钥和公钥a = randint(1, p-1) # Alice 随机生成一个私钥A = power_mod(g, a, p) # Alice 计算公钥 A = g^a mod p# 3. Bob 生成私钥和公钥b = randint(1, p-1) # Bob 随机生成一个私钥B = power_mod(g, b, p) # Bob 计算公钥 B = g^b mod p# 4. Alice 和 Bob 计算共享密钥K_Alice = power_mod(B, a, p) # Alice 计算共享密钥 K = B^a mod pK_Bob = power_mod(A, b, p) # Bob 计算共享密钥 K = A^b mod p</code></pre><h2><span id="ecdh-密钥交换协议过程">ECDH 密钥交换协议过程 😋</span></h2><p>ECDH（Elliptic Curve Diffie-Hellman）是 DH 密钥交换协议的一个变种，它使用椭圆曲线上的点运算来实现密钥交换。相比于传统的 DH 协议，ECDH 在相同安全级别下可以使用更短的密钥长度，从而提高效率。</p><p>ECDH 的过程与 DH 类似，首先通信双方共享一个椭圆曲线参数和一个基点 $G$。</p><p>Alice 本地随机生成一个私钥 $a$，并计算公钥 $A = aG$，将 $A$ 发送给 Bob。</p><p>Bob 收到 $A$ 后，本地随机生成一个私钥 $b$，并计算公钥 $B = bG$，将 $B$ 发送给 Alice。<br>同时 Bob 计算共享密钥 $K = bA = b(aG) = abG$。</p><p>Alice 收到 $B$ 后计算共享密钥 $K = aB = a(bG) = abG$。</p><p>最终 Alice 和 Bob 都得到了相同的共享密钥 $K$，以这个 key 作为对称加密算法的密钥，进行后续的通信加密。</p><h2><span id="ecdh-密钥交换协议实现">ECDH 密钥交换协议实现 😇</span></h2><p>下面是一个简单的 ECDH 密钥交换协议的 Python 实现示例：</p><pre><code class="lang-python">from sage.all import EllipticCurve, GF, randint, random_prime# 1. 初始化 ECC 参数bits = 160p = random_prime(2^bits, lbound=2^(bits-1))while True:    a = randint(1, p-1)    b = randint(1, p-1)    if (4*a**3 + 27*b**2) % p != 0:        breakE = EllipticCurve(GF(p), [a, b])G = E.gens()[0] # 基点n = G.order()   # 基点的阶# 2. Alice 生成私钥和公钥a_private = randint(1, n-1) # Alice 随机生成一个私钥A_public = a_private * G # Alice 计算公钥 A = aG# 3. Bob 生成私钥和公钥b_private = randint(1, n-1) # Bob 随机生成一个私钥B_public = b_private * G # Bob 计算公钥 B = bG# 4. Alice 和 Bob 计算共享密钥K_Alice = a_private * B_public # Alice 计算共享密钥 K = aB = a(bG) = abGK_Bob = b_private * A_public # Bob 计算共享密钥 K = bA = b(aG) = abG</code></pre><p>上一章：<a href="https://lr2006-robot.github.io/myblog.github.io/post/2007.html">加解密模式分析 - 背包加密算法</a> 👈<br>下一章：<a href="https://lr2006-robot.github.io/myblog.github.io/post/2009.html">加解密模式分析 - OTP 与 PRNG</a> 👈<br>回到开始：<a href="http://lr2006-robot.github.io/myblog.github.io/about/">关于我</a> 👈</p><p>相关链接：<br><a href="http://lr2006-robot.github.io/myblog.github.io/post/2005.html">加解密模式分析 - ElGamal 加密算法</a> 👈<br><a href="http://lr2006-robot.github.io/myblog.github.io/post/2006.html">加解密模式分析 - ECC 加密算法</a> 👈</p>]]>
    </content>
    <id>https://lr2006-robot.github.io/myblog.github.io/post/2008.html</id>
    <link href="https://lr2006-robot.github.io/myblog.github.io/post/2008.html"/>
    <published>2026-03-16T07:30:00.000Z</published>
    <summary>
      <![CDATA[<p>今天我们将分析 Diffie-Hellman 密钥交换协议（DH Key Exchange Protocol），这是一种基于离散对数问题的非对称加密算法。这和之前的 ElGamal 加密算法不同，DH 密钥交换协议的主要目的是让两个通信方在不安全的信道上安全地协商出一个共享]]>
    </summary>
    <title>加解密模式分析 - DH 密钥交换协议</title>
    <updated>2026-03-18T13:27:34.105Z</updated>
  </entry>
  <entry>
    <author>
      <name>Dorange</name>
    </author>
    <category term="加解密模式分析" scheme="https://lr2006-robot.github.io/myblog.github.io/categories/%E5%8A%A0%E8%A7%A3%E5%AF%86%E6%A8%A1%E5%BC%8F%E5%88%86%E6%9E%90/"/>
    <category term="密码学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="加解密" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%8A%A0%E8%A7%A3%E5%AF%86/"/>
    <category term="现代密码" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E7%8E%B0%E4%BB%A3%E5%AF%86%E7%A0%81/"/>
    <category term="非对称加密" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E9%9D%9E%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86/"/>
    <content>
      <![CDATA[<p>今天我们将分析背包加密算法（Knapsack Cryptosystem），这是一种基于陷门问题的非对称加密算法。背包加密算法的安全性依赖于背包问题的困难性，这使得它成为一个有趣的研究对象。</p><p>不过，我们要知道，所有基于超递增序列和模变换的背包，其陷门结构存在致命漏洞：模乘法无法完全隐藏超递增序列的结构，格基规约（LLL） 可高效还原私钥。所以绝大多数背包加密都已破解，不再使用了，但它的设计思想和分析方法对于理解现代密码学的原理仍然非常有帮助。</p><p>这里我们只介绍最经典的 Merkle-Hellman 背包加密算法，其他的背包加密算法基本上都是在这个基础上进行改进的，只有部分是基于格的（但我们不在这里讲解）。</p><h2><span id="merkle-hellman-背包加密数学原理">Merkle-Hellman 背包加密数学原理 😇</span></h2><p>Merkle-Hellman 背包加密的安全性基于背包问题的困难性。</p><p>背包问题是指给定一组物品的重量和一个背包的容量，判断是否存在一种组合使得物品的总重量恰好等于背包的容量。</p><p>举个例子：有集合 $A = {3, 5, 7}$ 和一个背包容量 $C = 10$，我们需要判断是否存在一个子集 $S \subseteq A$ 使得 $\sum_{x \in S} x = C$。在这个例子中，子集 ${3, 7}$ 的总重量为 $10$，因此答案是存在的。</p><p>这里介绍一下超递增序列。一个序列 $S = {s_1, s_2, …, s_n}$ 如果，都有</p><script type="math/tex; mode=display">对于所有的 i 都有s_i > \sum_{j=1}^{i-1} s_j</script><p>那么这个序列是超递增的。例如，序列 ${1, 3, 7, 15}$ 是超递增的，因为每个元素都大于前面所有元素的和。</p><p>我们尝试将一个超递增序列 $A = {3, 4, 9, 17, 35}$，乘一个数 $t = 19$，再取模 $k = 73$，其中 $k$ 大于 $t$ 和 $s_n$ 的乘积（即 $19 \times 35 = 665 &lt; 73$），得到一个新的序列 $B = {57, 3, 25, 31, 8}$。</p><p>在不知道 $t$ 和 $k$ 的情况下，拿到序列 $B$，我们无法判断它是否是一个超递增序列，也无法还原出原来的序列 $A$。这就是背包加密算法的核心思想，通过模乘法来隐藏超递增序列的结构，使得攻击者无法轻易地破解密文。</p><p>而超递增序列构造的背包问题很好求解，因为每个元素都大于前面所有元素的和，所以我们可以从最大的元素开始，依次判断是否可以减去当前元素来得到目标值，直到找到一个解或者确定没有解，这个可以使用贪心算法解决。</p><h2><span id="merkle-hellman-背包加密过程">Merkle-Hellman 背包加密过程 😁</span></h2><p>一天，Alice 想要向 Bob 发送一条秘密消息。为了确保消息的安全性，Alice 决定使用 Merkle-Hellman 背包加密算法。</p><p>首先，Bob 选择一个超递增序列 $A$，并选择一个整数 $t$ 和一个模数 $k$，满足 $k &gt; \sum_{i=1}^{n} s_i$，当然 $k$ 和 $t$ 互质。<br>接着，Bob 计算一个新的序列 $B = {b_i}$，其中 $b_i = (s_i \cdot t) \mod k$。<br>Bob 将序列 $B$ 作为公钥发送给 Alice，而私钥则由序列 $A$、整数 $t$ 和模数 $k$ 组成，Bob 将这些信息保密。</p><p>Alice 收到 Bob 的公钥后，首先将消息 $M$ 转换为一个二进制字符串，然后将其分成 $n$ 位的块（和 $B$ 序列元素个数相同）。<br>对于每个块，Alice 计算密文 $C$，其中 $C = \sum_{i=1}^{n} m_i \cdot b_i$，其中 $m_i$ 是块中的第 $i$ 位。然后，Alice 将密文 $C$ 发送给 Bob。</p><p>Bob 收到密文后，可以使用私钥中的信息来解密密文。首先，Bob 计算 $C’ = (C \cdot t^{-1}) \mod k$，其中 $t^{-1}$ 是 $t$ 的模逆元。然后，Bob 使用序列 $A$ 来还原出原始消息 $M$。</p><h2><span id="merkle-hellman-背包加密算法实现">Merkle-Hellman 背包加密算法实现 🥰</span></h2><p>下面是一个简单的 Merkle-Hellman 背包加密算法的 Python 实现示例：</p><pre><code class="lang-python">from sage.all import randint, gcd, inverse_mod# 1. 生成密钥 (Bob)n = 80 # 背包长度，即一次能加密的比特数w = [] # 超递增序列 (私钥)current_sum = 0for _ in range(n):    # 生成超递增序列    val = randint(current_sum + 1, current_sum + 100)    w.append(val)    current_sum += val# 选择模数 q，使得 q &gt; sum(w)q = randint(current_sum + 1, current_sum * 2)# 选择乘数 r，使得 gcd(r, q) = 1while True:    r = randint(2, q - 1)    if gcd(r, q) == 1:        break# 计算公钥序列 betabeta = [(val * r) % q for val in w]public_key = beta# 私钥为 (w, q, r)# 2. 加密 (Alice)M = &quot;hello_Bob&quot; # 待加密的消息long_M = int.from_bytes(M.encode(), &#39;big&#39;) # 转为整数# 转换为比特串M_bin = bin(long_M)[2:]if len(M_bin) &gt; n:    print(&quot;Error: Message is too long for the key size!&quot;)else:    # 补齐到 n 位    M_bin = M_bin.zfill(n)    m_bits = [int(b) for b in M_bin]    # 计算密文 C = sum(m_i * beta_i)    C = sum(m * b for m, b in zip(m_bits, beta))    # 3. 解密 (Bob)    # 计算 r 的逆元    r_inv = inverse_mod(r, q)    # 计算 c&#39; = C * r^-1 mod q    c_prime = (C * r_inv) % q    # 求解超递增背包问题 (贪心算法)    decrypted_bits = [0] * n    current_val = c_prime    # 从最大的元素开始判断    for i in range(n - 1, -1, -1):        if current_val &gt;= w[i]:            decrypted_bits[i] = 1            current_val -= w[i]        else:            decrypted_bits[i] = 0    # 将比特串还原为整数    decrypted_int = int(&quot;&quot;.join(map(str, decrypted_bits)), 2)    # 还原为字符串    byte_len = (decrypted_int.bit_length() + 7) // 8    M_val = int(decrypted_int) # 确保类型为 Python int    flag = int.to_bytes(M_val, length=byte_len, byteorder=&#39;big&#39;).decode()    print(flag)</code></pre><p>实际上由于 sage 中的整数类型和 python 中的整数类型不同，所以在非必要情况下不要纯使用sage函数进行算法实现，可以多使用python的密码学库使用。</p><p>上一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/2006.html">加解密模式分析 - ECC 加密算法</a> 👈<br>下一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/2008.html">加解密模式分析 - DH 密钥交换协议</a> 👈<br>回到开始：<a href="http://lr2006-robot.github.io/myblog.github.io/about/">关于我</a> 👈</p>]]>
    </content>
    <id>https://lr2006-robot.github.io/myblog.github.io/post/2007.html</id>
    <link href="https://lr2006-robot.github.io/myblog.github.io/post/2007.html"/>
    <published>2026-03-16T07:30:00.000Z</published>
    <summary>
      <![CDATA[<p>今天我们将分析背包加密算法（Knapsack Cryptosystem），这是一种基于陷门问题的非对称加密算法。背包加密算法的安全性依赖于背包问题的困难性，这使得它成为一个有趣的研究对象。</p>
<p>不过，我们要知道，所有基于超递增序列和模变换的背包，其陷门结构存在致命]]>
    </summary>
    <title>加解密模式分析 - 背包加密算法</title>
    <updated>2026-03-19T15:35:11.379Z</updated>
  </entry>
  <entry>
    <author>
      <name>Dorange</name>
    </author>
    <category term="密码学算法" scheme="https://lr2006-robot.github.io/myblog.github.io/categories/%E5%AF%86%E7%A0%81%E5%AD%A6%E7%AE%97%E6%B3%95/"/>
    <category term="密码学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="数学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E6%95%B0%E5%AD%A6/"/>
    <category term="算法" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<p>当我们计算有限域下高次根式的求解时，AMM（Adleman-Manders-Miller）算法就派上了用场。AMM 算法是一种高效的算法，可以在多项式时间内求解有限域下的高次根式问题，这在密码学中有着重要的应用，比如在 RSA 加密算法中求解模 $n$ 下的 $e$ 次根式问题。</p><p>欢迎来到《密码学核心算法实战》的 AMM 算法专题！这里没有纸上谈兵的理论空谈（真的不画大饼😉），只有一把把能直接撬动数据安全的精密齿轮⚙️。</p><h2><span id="amm-算法">AMM 算法 🌟</span></h2><p>AMM 算法是密码学中专门用于在有限域 $\mathbb{F}_p$ 下求解高次根式的算法。给定一个质数 $p$，一个整数 $a$ 和一个正整数 $e$，我们想要找到一个整数 $x$，使得 $x^k \equiv a \mod p$。</p><p>要解之前，首先要先验满足条件：</p><ul><li>$a \equiv 0 \mod p$，此时 $x = 0$ 是一个解。</li><li>$a \not \equiv 0 \mod p$，要先验证解是否存在：<script type="math/tex; mode=display">a^{\frac{p-1}{\gcd(e, p-1)}} \equiv 1 \mod p</script></li></ul><h2><span id="amm-算法的原理">AMM 算法的原理 😵</span></h2><p>首先设 $d = \gcd(k,p-1)$，先验证解的存在，过程就是上面的条件验证。</p><p>找到 $p$ 的一个原根 $g$，将 $a$ 表示为原根的幂（这一步本质是离散对数问题，小素数可暴力找，大素数需结合 BSGS）：</p><script type="math/tex; mode=display">a = g^m \mod p</script><p>同时也设 $x = g^X \mod p$，将求解的方程转化为：</p><script type="math/tex; mode=display">g^{kX} \equiv g^m \mod p \Rightarrow kX \equiv m \mod p-1</script><p>这里我们会注意到，由一开始的验证条件可以推导：</p><script type="math/tex; mode=display">a^{\frac{p-1}{\gcd(k, p-1)}} \equiv g^{m \cdot \frac{p-1}{\gcd(k, p-1)}} \equiv 1 \mod p</script><p>由于 $g$ 是原根，所以阶为 $p-1$ ，因此：</p><script type="math/tex; mode=display">m \cdot \frac{p-1}{\gcd(k, p-1)} \equiv 0 \mod p-1 \Longleftrightarrow \frac{m}{\gcd(k, p-1)} \in \mathbb{Z}</script><p>所以，只有当 $d|m$ 时，才存在解。对方程 $kX \equiv m \mod p-1$ 进行消去律约分，得到：</p><script type="math/tex; mode=display">\frac{k}{d} \cdot X \equiv \frac{m}{d} \mod \frac{p-1}{d}</script><p>由于 $\gcd(\frac{k}{d}, \frac{p-1}{d}) = 1$，所以 $\frac{k}{d}$ 的逆元 $t$ 在模 $\frac{p-1}{d}$ 下存在，所以得到：</p><script type="math/tex; mode=display">k \cdot t \equiv d \mod p-1</script><p>结合扩展欧几里得算法求 $t$，简单推一下式子：</p><script type="math/tex; mode=display">k t X \equiv mt \mod p-1 \Longleftrightarrow X \equiv t m d^{-1} \mod p-1</script><p>最终推导出 $x$ 的解为：</p><script type="math/tex; mode=display">x \equiv g^{t m d^{-1}} \mod p</script><h2><span id="amm-算法的实现">AMM 算法的实现 🙃</span></h2><p>下面是 AMM 算法的一个简单实现：</p><pre><code class="lang-python">from sage.all import *def amm_solve(k, a, p):    &quot;&quot;&quot;    求解 x^k = a (mod p)     &quot;&quot;&quot;    # 1. 如果 a = 0 mod p，直接返回解 0    if a % p == 0:        return [0]    d = gcd(k, p - 1)    # 2. 验证解是否存在: a^((p-1)/d) == 1 (mod p)    if power_mod(a, (p - 1) // d, p) != 1:        return [] # 无解    # 3. 找到 p 的一个原根 g    g = primitive_root(p)    # 4. 求解离散对数: a = g^m (mod p)    m = discrete_log(mod(a, p), mod(g, p))    # 5. 验证 d | m，只有这个时候才有解 (根据推导理论上一定成立)    if m % d != 0:        return []    # 6. 求解方程的降次转化: k*X = m mod (p-1)    # 两边同除以 d 得到: (k/d)*X = (m/d) mod ((p-1)/d)    k_prime = k // d    m_prime = m // d    p_prime = (p - 1) // d    # 7. 计算 t, 即 (k/d) 在模 (p-1)/d 下的逆元    t = inverse_mod(k_prime, p_prime)    # 8. 得到一个特解 X0    X0 = (t * m_prime) % p_prime    solutions = []    # 9. 遍历找全所有可能的分支解（因为模数被除了 d，所以在原模 p-1 下有 d 个解）    for i in range(d):        X = X0 + i * p_prime        x = power_mod(g, X, p)        solutions.append(x)    return sorted(list(set(solutions)))</code></pre><h2><span id="sagemath-偷懒">SageMath 偷懒 🤓👆</span></h2><p>有同学说：“博主，博主，你的算法确实很厉害，但是还是太吃操作了，有没有更加简单无脑的用法？”👻<br>有的，兄弟有的，这样的算法在 SageMath 中早就已经被封装好了🤫，我们直接调用就行了：</p><pre><code class="lang-python">from sage.all import * # sage环境中才生效def amm_solve(k, a, p):    &quot;&quot;&quot;    求解 x^k = a (mod p)     &quot;&quot;&quot;    # 在 SageMath 中，可以直接使用 nth_root 方法来求高次剩余，它内部实现了高效的算法（如 Tonelli-Shanks / AMM 扩展等）    try:        roots = mod(a, p).nth_root(k, all=True)        return sorted([int(r) for r in roots])    except ValueError:        return [] # 无解</code></pre><p>怎么样，是不是非常简单？🤣👉🤡</p><p>上一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/3009.html">密码学算法 - BSGS算法二</a> 👈<br>下一章：<br>回到开始：<a href="http://lr2006-robot.github.io/myblog.github.io/about/">关于我</a> 👈</p><p>相关链接：<br><a href="https://lr2006-robot.github.io/myblog.github.io/post/1007.html">密码学数学基础 - 群论基础</a> 👈<br><a href="http://lr2006-robot.github.io/myblog.github.io/post/3001.html">密码学算法 - 欧几里得算法</a> 👈<br><a href="http://lr2006-robot.github.io/myblog.github.io/post/3008.html">密码学算法 - BSGS一 算法</a> 👈</p>]]>
    </content>
    <id>https://lr2006-robot.github.io/myblog.github.io/post/3010.html</id>
    <link href="https://lr2006-robot.github.io/myblog.github.io/post/3010.html"/>
    <published>2026-03-14T07:03:00.000Z</published>
    <summary>
      <![CDATA[<p>当我们计算有限域下高次根式的求解时，AMM（Adleman-Manders-Miller）算法就派上了用场。AMM 算法是一种高效的算法，可以在多项式时间内求解有限域下的高次根式问题，这在密码学中有着重要的应用，比如在 RSA 加密算法中求解模 $n$ 下的 $e$ 次根式]]>
    </summary>
    <title>密码学算法 - AMM 算法</title>
    <updated>2026-03-16T04:24:10.376Z</updated>
  </entry>
  <entry>
    <author>
      <name>Dorange</name>
    </author>
    <category term="RSA加解密专题" scheme="https://lr2006-robot.github.io/myblog.github.io/categories/RSA%E5%8A%A0%E8%A7%A3%E5%AF%86%E4%B8%93%E9%A2%98/"/>
    <category term="密码学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="前言" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%89%8D%E8%A8%80/"/>
    <category term="RSA" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/RSA/"/>
    <content>
      <![CDATA[<p>在前面几章中，我们已经介绍了RSA加解密的基本原理和一些相关攻击方法。在本章中，我们将继续深入探讨RSA加解密中的预言机相关攻击。这些攻击利用了预言机（Oracle）的功能来推断出明文或私钥，从而威胁到RSA的安全性。</p><h2><span id="选择明密文相关攻击">选择明密文相关攻击 😆</span></h2><p>这种攻击适用于 oracle（预言机）攻击场景，一般输入明文，返回密文，并且输入的明文中不能包含 flag 还有输入密文，返回明文，并且返回的明文中不能包含flag。</p><p>首先，我们选择明文攻击，模数一定是和偶数互质的，所以我们直接选择加密 $2$， $4$， $8$，然后可以知道：</p><script type="math/tex; mode=display">\begin{align*}c_1 \equiv 2^e \mod n \\c_2 \equiv 4^e \mod n \\c_3 \equiv 8^e \mod n\end{align*}</script><p>由于我们知道自己输入的明文，所以我们可以知道 $c_1$、$c_2$、$c_3$ 之间的关系：</p><script type="math/tex; mode=display">\begin{align*}c_{2}^{2} \equiv c_{4} \mod n \\c_{2}^{3} \equiv c_{8} \mod n\end{align*}</script><p>所以，可以找到公因数：</p><script type="math/tex; mode=display">\begin{align*}c_{2}^{2} - c_{4} &= t \cdot n \\c_{2}^{3} - c_{8} &= t' \cdot n\end{align*}</script><p>因此我们就可以通过 $gcd$ 来计算出 $n$ 的值了，但是要考虑 $n$ 是否为质数。</p><p>然后，我们选择密文攻击，假设密文 $c = m^e \mod n$ 运用下列步骤求出 $m$ 的值：</p><ol><li>选择任意的 $X \in \mathbb{Z}_n^*$，即 $X$ 与 $n$ 互素</li><li>计算 $Y = c \cdot X^e \mod n$</li><li>由于我们可以进行选择密文攻击,那么我们求得 $Y$ 对应的解密结果 $Z = Y^d = (c \cdot X^e)^d = c^d \cdot X = m \cdot X \mod n$</li><li>那么，由于 $X$ 与 $n$ 互素,我们很容易求得相应的逆元,进而可以得到 $m$ 的值了。</li></ol><h3><span id="选择明密文相关攻击例题">选择明密文相关攻击例题</span></h3><p><a href="https://files.catbox.moe/21u5bg.zip">选择明密文相关攻击例题下载</a></p><pre><code class="lang-python">import osfrom Crypto.Util.number import getPrime, bytes_to_long, long_to_bytesclass RsaCrypt:    def __init__(self, p, q, e):        self.n = p * q        self.e = e        self.d = pow(e, -1, (p-1)*(q-1))    def enc(self, data):        m = bytes_to_long(data)        c = pow(m, self.e, self.n)        return long_to_bytes(c)    def dec(self, data):        c = bytes_to_long(data)        m = pow(c, self.d, self.n)        return long_to_bytes(m)def handle_client():    if not os.path.exists(&quot;flag.txt&quot;):        with open(&quot;flag.txt&quot;, &quot;wb&quot;) as f:            f.write()    p = getPrime(1024)    q = getPrime(1024)    e = 65537    crypt = RsaCrypt(p, q, e)    while 1 :        cmd = input(&quot;cmd &gt;&quot;).strip()        data = input(&quot;data &gt;&quot;).strip()        # 补齐奇数长度的字符串以满足 bytes.fromhex 要求 (例如 &#39;2&#39; -&gt; &#39;02&#39;)        if len(data) % 2 != 0:            data = &quot;0&quot; + data        if cmd == &quot;enc&quot;:            try:                data = bytes.fromhex(data)            except ValueError:                print(&quot;invalid hex data&quot;)                continue            if b&quot;flag&quot; in data:                print(&quot;data can&#39;t contain \&#39;flag\&#39;&quot;)            else:                res = crypt.enc(data)                if res:                    print(res.hex())                else:                    print(&quot;args wrong&quot;)        elif cmd == &quot;dec&quot;:            try:                data = bytes.fromhex(data)            except ValueError:                print(&quot;invalid hex data&quot;)                continue            res = crypt.dec(data)            if res:                if b&quot;flag&quot; in res:                    print(&quot;you can&#39;t decrypt flag&quot;)                else:                    print(res.hex())            else:                print(&quot;args wrong&quot;)        elif cmd == &quot;get_flag&quot;:            with open(&quot;flag.txt&quot;, &quot;rb&quot;) as file:                flag = file.read()            res = crypt.enc(flag)            print(res.hex())if __name__ == &quot;__main__&quot;:    handle_client()</code></pre><p>首先我们可以通过选择明文攻击来获取到模数 $n$ 的值了，接下来我们就可以通过选择密文攻击来获取到明文的值了。</p><p>解题代码示例：</p><pre><code class="lang-python">from Crypto.Util.number import *# get_flagc = 0x246ed3b729330b0799ec47da886dfb2b6276bcffc66c0ede83fd7e8e1d36a82f0e51e1ba5f95253c04213c3236cf4e102cd3a1578a66ea1a1f03c2e488302da49a76d18aacab364ae1447f4d71f75658eaa3bc8fa1fc1cc9871fc7dccc8ba6f03a5d9343a1aab2404c579823e971448b7ee74bfa2ec0adb70e8fde943687e86036abe5bdf9fba8ecbfaf2bf2d6806ee6d62f62e6e344b0050840a7d167f61aa99606e5cbc51758bbb8633bc16294fcbe286ad592ea3c4d1209dea83cae7a2a99865bc13422a246e624b21da6481b8362226ece44ad12180b5631ced203a877e3e5028917824c19e377c00c5ecee6cc054cbeab9fa168a8bebe6c57684cbb3f3# enc 2c_2 = 0x114a75c97c0334cc5148eedb4b9f66a9f4d94c66d571ca1e2f69a6ee72c9cd2a5de3e3c8b93e8bb9328a36d2e6eec828c3315705cc1e54cf8f65a777a7aae5134f395769c38448c26572a53c901879f7245a6ea689e149bfba84035a645530f7ac998fd2c48ae8f41b3fed7e2837911565d5aa69f712fc78c20c47a6742da9804737a9d30edbfdcbe7c94d38201625c3fa39234d19b75088dfd98eae4ff145e8c689e8a54c2d717d24d4829d95d7a5de3d31e04ec298038c3c85b18600db770fd4931434f5d222b55187ff1309ef91f646fc4f2f431c86ce71cbace8ad14d82266bf5f3cb8220645156226d609a409068f31587998e0c727ec91163c06f7822c# enc 4c_4 = 0x4c1fb7110722eaf6ea9a51f8c0f68d2de3e90a86408afa76cef8d0bd255d2b9e1f581c56c5478726082f23e066e60be1086e6d14b23d8b401cfcb57c3eb65ee4c39a820341858cc9ba32f9e9a2aa71cbb6ef03edd6b54ba1eb8db5d327e2ad5722cfabc8311cf26607ef146138ea14d310c10859c3686abd4b55dff7a54aba1bc57e494681ca5edde24264de16c72ee8855630038299cb2702c2149d09cecb6ddccdfe813d7697745551c5ec1d9b066d4e13f082b44094b0607721b544aed6b65d5a338917babb6142772b3b72a3b8c289adcc7649adb2cbd13d5cc0032fd36bfbd2934377f86acb8d9b97b4d7821832f79af9d5a0d80df3aa54ed9e067c3ee# enc 8c_8 = 0x418fdbe7174db8c25395457fae06ce57f1761b4e3b73ae26150d1b951813d41646d8c93315b19c47cc65294d5ea0cebba6663fe750d741eae1d8fd19d7da87324c7df04fff9125b34e821cac0e7c1eabdf1d3224529029e0d91545db97eb314bc0040e0ff225f8a42f1b0fc1dd5a51bf05b8f70d9b6b3992ac7a290496768cdab6d136c3566a0ea9623dfa40fa29dd684629ddf5fc447682c6c3c656ec30d2cdc5f51bc4dba4e87b9323b47a296f2326ad6014f905b70b928478273478dff01b71dffd6d54d6382ddb8d4a6e636148ee3b314a442eecaea2582df53549e5fb975c679c6d56367641c1860889edf73dfc002a7f0f214e45d41082cc8b6a92c9a0n = GCD(c_2**2 - c_4, c_2**3 - c_8)while n % 2 == 0:    n //= 2print(f&quot;n: {n}&quot;)X = 2Y = (c * c_2) % nprint(f&quot;Y: {hex(Y)}&quot;)# dec YYd = 92654804595522157479766957049954882296889731182656163601277452832538918832378m = (Yd * inverse(X, n)) % nprint(long_to_bytes(m))# b&#39;flag{u_know_what_the_oracle_did}&#39;</code></pre><h2><span id="奇偶预言机">奇偶预言机 😎</span></h2><p>有一个 oracle，它会对一个给定的密文进行解密，并且会检查解密的明文的奇偶性，并根据奇偶性返回相应的值，比如 1 表示奇数，0 表示偶数。</p><p>这一种预言机我们可以通过构造不断放缩确定密文的范围，最终确定明文的值了。我们有 $c \equiv m^e \mod n$。</p><p>我们可以将 $c \cdot 2^e \equiv (2 \cdot m)^e \mod n$ 作为输入，那么预言机就会计算出 $2m \mod n$。</p><ul><li><p>如果服务器返回 $1$，说明 $2m \mod n$ 是奇数，则说明 $2m$ 大于 $n$，且减去了奇数个 $n$，又因为 $2m &lt; 2n$，那么</p><script type="math/tex; mode=display">\frac{n}{2} \leq m < n</script></li><li><p>如果服务器返回 $0$，说明 $2m \mod n$ 是偶数，则说明 $2m$ 小于 $n$，又因为 $m &lt; n$，那么</p><script type="math/tex; mode=display">0 \leq m < \frac{n}{2}</script></li></ul><p>依据这个思路，一直放缩 $i$ 次，有</p><script type="math/tex; mode=display">\frac{xn}{2^i} \leq m < \frac{(x+1)n}{2^i}</script><p>那么第 $i + 1$ 次放缩时，输入 $c \cdot 2^{(i+1)e} \mod n$，得到</p><script type="math/tex; mode=display">\frac{kn}{2^i} \leq m < \frac{(k+1)n}{2^i}</script><p>根据第 $i$ 次放缩的结果，有</p><script type="math/tex; mode=display">\frac{2xn}{2^{i+1}} \leq m < \frac{2(x+1)n}{2^{i+1}}</script><ul><li>如果服务器返回 $1$，则 $k$ 必然是奇数，设 $k = 2y + 1$，则<script type="math/tex; mode=display">\frac{(2y + 1)n}{2^{i+1}} \leq m < \frac{(2y + 2)n}{2^{i+1}}</script></li></ul><p>与此同时，由于 $m$ 必然存在，所以第 $i+1$ 次得到的这个范围和第 $i$ 次得到的范围必然存在交集。所以 $y$ 必然与 $x$ 相等。</p><ul><li>服务器返回偶数，则 $k$ 必然是一个偶数，$k = 2y$，此时 $y$ 必然也与 $x$ 相等。</li></ul><p>如此反复放缩，最终就可以确定 $m$ 的值了。</p><h3><span id="奇偶预言机例题">奇偶预言机例题</span></h3><p><a href="https://files.catbox.moe/2zkzsx.zip">奇偶预言机例题下载</a></p><pre><code class="lang-python">import socketimport threadingimport osfrom Crypto.Util.number import getPrime, bytes_to_longdef generate_key(bits=512):    p = getPrime(bits)    q = getPrime(bits)    n = p * q    e = 65537    d = pow(e, -1, (p-1)*(q-1))    return (n, e), ddef handle_client(c, pub, priv, flag_c):    n, e = pub    d = priv    c.sendall(f&quot;n = {n}\ne = {e}\nflag_c = {flag_c}\n&quot;.encode())    while True:        try:            c.sendall(b&quot;ct (hex): &quot;)            data = c.recv(1024).decode().strip()            if not data: break            ct = int(data, 16)            pt = pow(ct, d, n)            c.sendall(f&quot;{pt % 2}\n&quot;.encode())        except Exception as err:            break    c.close()pub, priv = generate_key(512)with open(&quot;flag.txt&quot;, &quot;rb&quot;) as f:    flag = f.read()flag_c = pow(bytes_to_long(flag), pub[1], pub[0])s = socket.socket()s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)s.bind((&#39;127.0.0.1&#39;, 8888))s.listen(5)print(&quot;Oracle Server listening on 127.0.0.1:8888&quot;)while True:    conn, addr = s.accept()    threading.Thread(target=handle_client, args=(conn, pub, priv, flag_c)).start()</code></pre><p>按照上面的思路，我们就可以通过不断放缩来确定明文的值了。</p><pre><code class="lang-bash"># 记得结束后杀掉服务器进程sudo kill -9 $(sudo lsof -t -i:8888)</code></pre><p>解题代码示例：</p><pre><code class="lang-python">from pwn import *from Crypto.Util.number import long_to_bytesfrom fractions import Fractionimport sys# 设置大整数转换限制，防止溢出或报错sys.set_int_max_str_digits(10000)context.log_level = &#39;error&#39; # 关闭多余的 pwntools 输出r = remote(&#39;127.0.0.1&#39;, 8888)r.recvuntil(b&#39;n = &#39;)n = int(r.recvline().strip())r.recvuntil(b&#39;e = &#39;)e = int(r.recvline().strip())r.recvuntil(b&#39;flag_c = &#39;)flag_c = int(r.recvline().strip())print(f&quot;n = {n}\ne = {e}\nc = {flag_c}&quot;)print(&quot;开始执行奇偶预言机攻击...&quot;)# 我们想要解密明文，依据题意：输入 c * 2^e mod n 来获取 2m mod n 的奇偶性multiplier = pow(2, e, n)ct = flag_clow = Fraction(0)high = Fraction(n)# 需要的交互次数为 n 的比特长度（二分查找的次数）bits = n.bit_length()for i in range(1, bits + 2):    ct = (ct * multiplier) % n    r.recvuntil(b&quot;ct (hex): &quot;)    r.sendline(hex(ct)[2:].encode())    try:        res = r.recvline()        parity = int(res.strip())    except EOFError:        break    except ValueError:        print(&quot;无效返回:&quot;, res)        break    mid = (low + high) / 2    if parity == 1:        low = mid    else:        high = mid    if i % 50 == 0:        print(f&quot;[*] 已推进 {i}/{bits} 步...&quot;)m = int(high)print()print(&quot;攻击完成，解密得到的明文十六进制为:&quot;, hex(m))print(&quot;转换为 ASCII:&quot;, long_to_bytes(m).decode(errors=&#39;replace&#39;))r.close()# flag{you_have_gaind_a_deep_insight_into_the_rsa_parity_oracle!!!}</code></pre><h2><span id="字节预言机">字节预言机 🥰</span></h2><p>有一个 oracle，它会对一个给定的密文进行解密，并且会给出明文的最后一个字节。这一种可以看作上一种预言机的扩展，一样地放缩来确定明文的范围，最终确定明文的值了。</p><p>我们有 $c \equiv m^e \mod n$，构造 $c \cdot 256^e \equiv (256 \cdot m)^e \mod n$ 作为输入，那么预言机就会计算出 $256m \mod n$ 的值。</p><p>由于 $m$ 一般是小于 $n$ 的，所以 $256m \mod n = 256m - kn$，其中 $k &lt; 256$。</p><p>而且对于两个不同的 $k_1$ 和 $k_2$，$256m - k_1n$ 和 $256m - k_2n$ 的最后一个字节是不同的。</p><p>$256m - kn$ 的最后一个字节其实就是 $-kn$ 在模 $256$ 的情况下获取的。那么其实我们可以首先枚举出0~255情况下的最后一个字节，构造一个 $k$ 和最后一个字节的映射表 $map$。</p><p>当服务器返回最后一个字节，那么我们可以根据上述构造的映射表得知 $k$，即减去了$k$ 个 $n$，即</p><script type="math/tex; mode=display">kn < 256m < (k+1)n</script><p>一样地放缩 $i$ 次，有</p><script type="math/tex; mode=display">\frac{xn}{256^i} < m < \frac{(x+1)n}{256^i}</script><p>第 $i+1$ 次放缩时，输入 $c \cdot 256^{(i+1)e} \mod n$，得到</p><script type="math/tex; mode=display">\begin{align*}{256}^{i+1}m \mod n = {256}^{i+1}m - kn \\0 \leq {256}^{i+1}m - kn < n \\\frac{kn}{256^i} < m < \frac{(k+1)n}{256^i}\end{align*}</script><p>根据第 $i$ 次放缩的结果，有</p><script type="math/tex; mode=display">\frac{256xn}{256^{i+1}} < m < \frac{256(x+1)n}{256^{i+1}}</script><p>这里可以假设 $k = 256y + t$，而这里的 $t$ 就是我们可以通过映射表获得的。</p><script type="math/tex; mode=display">\frac{n(256y + t)}{256^{i+1}} < m < \frac{n(256y + t + 1)}{256^{i+1}}</script><p>与此同时，由于 $m$ 必然存在，所以第 $i+1$ 次得到的这个范围和第 $i$ 次得到的范围必然存在交集。所以 $y$ 必然与 $x$ 相等。如此反复放缩，最终就可以确定 $m$ 的值了。</p><h3><span id="字节预言机例题">字节预言机例题</span></h3><p><a href="https://files.catbox.moe/rdkvar.zip">字节预言机例题下载</a></p><pre><code class="lang-python">from Crypto.Util.number import getPrime, bytes_to_longimport osdef gen_keys(bits=1024):    p = getPrime(bits // 2)    q = getPrime(bits // 2)    n = p * q    e = 65537    d = pow(e, -1, (p-1)*(q-1))    return (n, e), dclass Oracle:    def __init__(self, n, d):        self.n = n        self.d = d    def decrypt_last_byte(self, c):        m = pow(c, self.d, self.n)        return m % 256pub, priv = gen_keys(1024)n, e = pubd = privwith open(&quot;flag.txt&quot;, &quot;rb&quot;) as f:    flag = f.read()m = bytes_to_long(flag)c = pow(m, e, n)print(f&quot;n = {n}&quot;)print(f&quot;e = {e}&quot;)print(f&quot;c = {c}&quot;)oracle = Oracle(n, d)while True:    try:        query = int(input(&quot;c&gt; &quot;))        last_byte = oracle.decrypt_last_byte(query)        print(f&quot;Last byte: {last_byte}&quot;)    except:        break</code></pre><p>这个题的解法和前面的思路是一样的，只不过这里我们需要构造一个 $k$ 和最后一个字节的映射表来获取 $k$ 的值了。</p><p>解题代码示例：</p><pre><code class="lang-python">import mathfrom pwn import *from Crypto.Util.number import long_to_bytesp = process([&#39;python3&#39;, &#39;server.py&#39;])p.recvuntil(b&quot;n = &quot;)n = int(p.recvline().strip())p.recvuntil(b&quot;e = &quot;)e = int(p.recvline().strip())p.recvuntil(b&quot;c = &quot;)c = int(p.recvline().strip())def query(c_query):    p.sendlineafter(b&quot;c&gt; &quot;, str(c_query).encode())    p.recvuntil(b&quot;Last byte: &quot;)    return int(p.recvline().strip())# 提前计算映射表，实际上就是求 (-n) 模 256 的逆元# 满足 equation: (k * (-n)) % 256 = b  =&gt;  k = (b * inv_n) % 256inv_n = pow(-n, -1, 256)x = 0# 总查询次数取决于位数，一次查询泄露一个字节(8位)total_queries = math.ceil(n.bit_length() / 8.0)# 为减少日志输出，我们每10次打印一次进度for i in range(1, total_queries + 1):    c_query = (c * pow(pow(256, i, n), e, n)) % n    b = query(c_query)    # 我们已知 256^i * m % n = 256^i * m - K * n    # 当前迭代的余数贡献主要来自于 (-k * n) 模 256    # 通过预先计算好的逆元，可以直接求出减去的个数值 k    k = (b * inv_n) % 256    x = x * 256 + k    if i % 10 == 0:        print(f&quot;[*] 已完成 {i}/{total_queries} 次查询&quot;)print(&quot;[*] 所有查询已完成。&quot;)# 根据所有迭代的 x 值，m 的取值范围最终被限缩到以下闭区间内# [x * n / 256^total_queries, (x+1) * n / 256^total_queries)lower = x * n // (256**total_queries)upper = (x + 1) * n // (256**total_queries)print(f&quot;预期下界: {lower}&quot;)print(f&quot;预期上界: {upper}&quot;)if lower == upper:    m = lower    print(f&quot;明文 m: {m}&quot;)    print(f&quot;Flag : {long_to_bytes(m)}&quot;)else:    # 为了容灾计算的精度误差，我们在上下界附近测试真正的明文值    for m in range(lower - 2, upper + 2):        if pow(m, e, n) == c:            print(f&quot;明文 m: {m}&quot;)            print(f&quot;Flag : {long_to_bytes(m)}&quot;)            breakp.close()</code></pre><p>上一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/4006.html">RSA加解密专题 - Coppersmith 相关攻击二</a> 👈<br>下一章：<br>回到开始：<a href="http://lr2006-robot.github.io/myblog.github.io/about/">关于我</a> 👈</p><p>相关链接：</p><p>参考：<br><a href="https://ctf-wiki.org/crypto/asymmetric/rsa/rsa_chosen_plain_cipher/">CTF Wiki - RSA 选择明密文攻击</a> 👈</p>]]>
    </content>
    <id>https://lr2006-robot.github.io/myblog.github.io/post/4007.html</id>
    <link href="https://lr2006-robot.github.io/myblog.github.io/post/4007.html"/>
    <published>2026-03-13T04:33:00.000Z</published>
    <summary>
      <![CDATA[<p>在前面几章中，我们已经介绍了RSA加解密的基本原理和一些相关攻击方法。在本章中，我们将继续深入探讨RSA加解密中的预言机相关攻击。这些攻击利用了预言机（Oracle）的功能来推断出明文或私钥，从而威胁到RSA的安全性。</p>
<h2><span id="选择明密文相关攻击]]>
    </summary>
    <title>RSA加解密专题 - 预言机相关攻击</title>
    <updated>2026-03-16T07:31:15.310Z</updated>
  </entry>
  <entry>
    <author>
      <name>Dorange</name>
    </author>
    <category term="RSA加解密专题" scheme="https://lr2006-robot.github.io/myblog.github.io/categories/RSA%E5%8A%A0%E8%A7%A3%E5%AF%86%E4%B8%93%E9%A2%98/"/>
    <category term="密码学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="前言" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%89%8D%E8%A8%80/"/>
    <category term="RSA" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/RSA/"/>
    <content>
      <![CDATA[<p>上一章我们介绍了 Coppersmith 相关攻击的基本原理和实现方法，帮助大家了解了如何利用 Coppersmith 方法来攻击 RSA 加密算法。接下来，我们将继续深入探讨 Coppersmith 相关攻击的应用。</p><p>欢迎来到《密码学核心算法实战》的 Coppersmith 相关攻击二专题！这里没有纸上谈兵的理论空谈（真的不画大饼😉），只有一把把能直接撬动数据安全的精密齿轮⚙️。</p><h2><span id="d_q-的低位泄露攻击">$d_q$ 的低位泄露攻击 🙃</span></h2><p>这种情况多见于破损pem文件，或者某些特定的实现中，攻击者可能会获得 $d_q$ 的部分低位信息。我们可以利用这些泄露的低位信息来恢复完整的 $d_q$，进而推导出私钥。</p><p>和前一篇文章中的私钥低位泄露一样，我们认识到低位，可以理解成原来的方程在取模时的值，然后我们就可以求解另外的参数的低位，最后通过 Coppersmith 方法求解高位。</p><h3><span id="d_q-的低位泄露攻击例题">$d_q$ 的低位泄露攻击例题</span></h3><p><a href="https://files.catbox.moe/30439o.zip">$d_q$ 的低位泄露攻击例题下载</a></p><pre><code class="lang-python">&#39;&#39;&#39;破损私钥以及密文-----BEGIN RSA PRIVATE KEY-----MIICXgIBAAKBgQDXFSUGqpzsBeUzXWtG9UkUB8MZn9UQkfH2Aw03YrngP0nJ3NwHUFTgzBSLl0tBhUvZO07haiqHbuYgBegO+Aa3qjtksb+bH6dz41PQzbn/l4Pd1fXmdJmtEPNh6TjQC4KmpMQqBTXF52cheY6GtFzUuNA7DX51wr6HZqHoQ73GQQIDAQAB​​​​​​​​yQvOzxy6szWFheigQdGxAkEA4wFss2CcHWQ8FnQ5w7k4uIH0I38khg07HLhaYm1czUcmlk4PgnDWxN+ev+vMU45O5eGntzaO3lHsaukX9461mA==-----END RSA PRIVATE KEY-----如何进行手工分离参数就不一一分析了，详情看之前的文章，这里直接给出数据&#39;&#39;&#39;n = 0xd7152506aa9cec05e5335d6b46f5491407c3199fd51091f1f6030d3762b9e03f49c9dcdc075054e0cc148b974b41854bd93b4ee16a2a876ee62005e80ef806b7aa3b64b1bf9b1fa773e353d0cdb9ff9783ddd5f5e67499ad10f361e938d00b82a6a4c42a0535c5e76721798e86b45cd4b8d03b0d7e75c2be8766a1e843bdc641e = 0x10001dq_l = 0xc90bcecf1cbab3358585e8a041d1b1q_inv_p = 0xe3016cb3609c1d643c167439c3b938b881f4237f24860d3b1cb85a626d5ccd4726964e0f8270d6c4df9ebfebcc538e4ee5e1a7b7368ede51ec6ae917f78eb598c = 30867633715813868869594516898484466949832562855682390289015078636128522331216753026144388726648565996116979180534633656943032219373784742974695394149452376092090200793002116674808387426569939440254310919848751355794417768901965964656988835770326844588984815500667657575205646452291264006781430216086103523765</code></pre><p>直接取模，然后构建出方程：</p><script type="math/tex; mode=display">e \cdot d_q - 1 \equiv k(q - 1) \mod 2^{know\_bits}</script><p>因为 $d_q &lt; q - 1$，所以可以知道 $k$ 的范围是 $[1, e]$，我们可以枚举 $k$ 来求解 $q$ 的低位。</p><p>小技巧：因为求解 $q$时用到了 $k^{-1}$，即 $k$ 是奇数（要和模互质），所以我们可以直接跳过偶数的 $k$ 来加速枚举。</p><p>注意，我们还有一个额外的参数，它是 $q^{-1} \mod p$，我们由关系 $q \cdot q^{-1} \equiv 1 \mod p$ 可以知道</p><script type="math/tex; mode=display">q^{-1} \cdot q - 1 \equiv sp</script><p>两边同时乘 $q$，就可以得到</p><script type="math/tex; mode=display">q^{-1} \cdot q^2 - q \equiv sn</script><p>所以，在模 $n$ 的情况下，我们构造出 Coppersmith 来解出高位的方程是</p><script type="math/tex; mode=display">q^{-1} \cdot q^2 - q \equiv 0 \mod n</script><p>解题代码示例：</p><pre><code class="lang-python">from Crypto.Util.number import *from sage.all import *n = 0xd7152506aa9cec05e5335d6b46f5491407c3199fd51091f1f6030d3762b9e03f49c9dcdc075054e0cc148b974b41854bd93b4ee16a2a876ee62005e80ef806b7aa3b64b1bf9b1fa773e353d0cdb9ff9783ddd5f5e67499ad10f361e938d00b82a6a4c42a0535c5e76721798e86b45cd4b8d03b0d7e75c2be8766a1e843bdc641e = 0x10001dq_l = 0xc90bcecf1cbab3358585e8a041d1b1q_inv_p = 0xe3016cb3609c1d643c167439c3b938b881f4237f24860d3b1cb85a626d5ccd4726964e0f8270d6c4df9ebfebcc538e4ee5e1a7b7368ede51ec6ae917f78eb598c = 30867633715813868869594516898484466949832562855682390289015078636128522331216753026144388726648565996116979180534633656943032219373784742974695394149452376092090200793002116674808387426569939440254310919848751355794417768901965964656988835770326844588984815500667657575205646452291264006781430216086103523765nbits = n.nbits()konw_bits = dq_l.nbits()mod = 2**konw_bitsP.&lt;x&gt; = PolynomialRing(Zmod(n))print(&quot;Start solving...&quot;)for k in range(1, e + 1):    if k % 2 == 0:         continue    q_l = ((e * dq_l - 1) * inverse_mod(k, mod) + 1) % mod    # 构造二次多项式    f = q_inv_p * (x * mod + q_l)**2 - (x * mod + q_l)    f = f.monic()    roots = f.small_roots(X=2**(nbits // 2 - konw_bits + 1), beta=1)    if roots:        x_val = int(roots[0])        q = x_val * mod + q_l        if n % q == 0:            print(f&quot;[*] Found q with k = {k}&quot;)            p = n // q            d = inverse_mod(e, (p-1)*(q-1))            m = pow(c, d, n)            print(&quot;Message:&quot;, long_to_bytes(int(m)))            break# b&#39;flag{the_broken_pem_is_dangerous!}&#39;</code></pre><h2><span id="双元变量-coppersmith-攻击">双元变量 Coppersmith 攻击 😅</span></h2><p>在某些特定的攻击场景中，我们可能会同时获得两个相关参数的部分信息。这时，我们可以构造一个包含两个变量的多项式方程，利用 Coppersmith 方法来同时求解这两个未知数。</p><p>但是，相较于单元变量的 Coppersmith 攻击，双元变量需要自己构造格，并且需要自己实现 LLL 算法来进行格的约简，因此难度较大。不过这里我会给出一种简单的构造方式。</p><p>对于双元变量方程：</p><script type="math/tex; mode=display">xy + a x^2 + b y^2 + c x + d y + e \equiv 0 \mod P</script><p>我们可以构造出如下的格：</p><script type="math/tex; mode=display">\begin{bmatrix}P & 0 & 0 & 0 & 0 & 0\\0 & P \cdot X & 0 & 0 & 0 & 0\\0 & 0 & P \cdot Y & 0 & 0 & 0\\0 & 0 & 0 & P \cdot X^2 & 0 & 0\\0 & 0 & 0 & 0 & P \cdot Y^2 & 0\\e & c & d & a & b & X \cdot Y\end{bmatrix}</script><p>其中 $X$ 和 $Y$ 是我们对未知数 $x$ 和 $y$ 的范围的估计。通过对这个格进行 LLL 约简，我们可以得到一个新的基，其中包含一个短向量，这个短向量对应的多项式在 $x$ 和 $y$ 的范围内有一个小根，这个小根就是我们要找的解。</p><h3><span id="双元变量-coppersmith-攻击例题">双元变量 Coppersmith 攻击例题</span></h3><p><a href="https://files.catbox.moe/3z59gd.zip">双元变量 Coppersmith 攻击例题下载</a></p><pre><code class="lang-python">from sage.all import *from Crypto.Util.number import *from secret import mp = getPrime(1024)k = getPrime(64)r1 = getPrime(512)r2 = getPrime(512)t1 = (k * inverse(m + r1, p)) % pt2 = (k * inverse(m + r2, p)) % pleak1 = t1 &gt;&gt; 244leak2 = t2 &gt;&gt; 244print(&quot;leak1 =&quot;,leak1)print(&quot;leak2 =&quot;,leak2)print(&quot;p =&quot;,p)print(&quot;k =&quot;,k)print(&quot;r1 =&quot;,r1)print(&quot;r2 =&quot;,r2)# leak1 = 1098816764286426157385797801363396675371118108280094592042050672573359525317258628504654733753781201133132612254789332026374788761747254155970613812606464205839077461898387247572628046476143045468089557793517353783804524800718752833296# leak2 = 1049656482213011765520700817730759174299375637725089075248860293784388137259519537644863360085943551872969496676976510844694589456499043770509884151671385719255903566416708982265930991647265599750836450978226653691655928529034866663445# p = 108209265779528487066534499136599909709853973107950537298744390757900138945182229902606206423050930011269860879007733713146197683046339710813223062191831446119265107116553831108599559354822644205393315657677544783720147837009193921493082268867053418119324279998822097576668591119657920848005423817059278923289# k = 12365281680381288671# r1 = 9144410517756126435013029016083002712735801054642387622098751193487101216215562293703971866241356055783669611204550875272227458679304511201946361648448397# r2 = 12776175140844604979090713660094736561506184755499536907757209332776967059175177777412075292688365522364612020362287897485306481341531072303954683485324863</code></pre><p>在这个例题中，我们同时获得了两个相关参数的部分信息，即 $t1$ 和 $t2$ 的高位泄露。我们可以利用这些泄露的信息来构造一个包含两个变量的多项式方程，并使用 Coppersmith 方法来求解这两个未知数 $x$ 和 $y$。</p><p>设 $x$ 和 $y$ 分别是我们要求解的未知数，$A$ 和 $B$ 是已知的高位泄露，那么我们可以构造出如下的方程：</p><script type="math/tex; mode=display">t_1 \cdot (m + r1) \equiv t_2 \cdot (m + r2) \mod p</script><p>由 $t_1 \cdot (m + r1) \equiv k \mod p$ 可以得到：</p><script type="math/tex; mode=display">m \equiv k \cdot t_1^{-1} - r1 \mod p</script><p>带入上面式子，我们可以得到一个关于 $t_1$ 和 $t_2$ 的二次多项式方程：</p><script type="math/tex; mode=display">k (t_1 - t_2) + t_1 t_2 (r1 - r2) \equiv 0 \mod p</script><p>带入 $t_1 = A + x$ 和 $t_2 = B + y$，进而化简成可以使用格基方法求解的方程：</p><script type="math/tex; mode=display">f(x, y) = xy + C_x \cdot x + C_y \cdot y + C_0 \equiv 0 \mod p</script><p>解题代码示例：</p><pre><code class="lang-python">from sage.all import *from Crypto.Util.number import *p = 108209265779528487066534499136599909709853973107950537298744390757900138945182229902606206423050930011269860879007733713146197683046339710813223062191831446119265107116553831108599559354822644205393315657677544783720147837009193921493082268867053418119324279998822097576668591119657920848005423817059278923289k = 12365281680381288671r1 = 9144410517756126435013029016083002712735801054642387622098751193487101216215562293703971866241356055783669611204550875272227458679304511201946361648448397r2 = 12776175140844604979090713660094736561506184755499536907757209332776967059175177777412075292688365522364612020362287897485306481341531072303954683485324863leak1 = 1098816764286426157385797801363396675371118108280094592042050672573359525317258628504654733753781201133132612254789332026374788761747254155970613812606464205839077461898387247572628046476143045468089557793517353783804524800718752833296leak2 = 1049656482213011765520700817730759174299375637725089075248860293784388137259519537644863360085943551872969496676976510844694589456499043770509884151671385719255903566416708982265930991647265599750836450978226653691655928529034866663445A = leak1 &lt;&lt; 244B = leak2 &lt;&lt; 244D = (r1 - r2) % pX_bound = 2**244Y_bound = 2**244# 构造二次多项式的系数c_xy = Dc_x = (D * B + k) % pc_y = (D * A - k) % pc_0 = (D * A * B - k * (B - A)) % p# 归一化多项式，使得最高次项系数为1inv_D = inverse_mod(D, p)C_x = (c_x * inv_D) % pC_y = (c_y * inv_D) % pC_0 = (c_0 * inv_D) % p# 构造格M = Matrix(ZZ,[    [p, 0, 0, 0],    [0, p * X_bound, 0, 0],    [0, 0, p * Y_bound, 0],    [C_0, C_x * X_bound, C_y * Y_bound, X_bound * Y_bound]])L = M.LLL()PR.&lt;x,y&gt; = PolynomialRing(ZZ)polys = []for row in L : # 恢复多项式    ploy = (row[0]) + (row[1] // X_bound) * x + (row[2] // Y_bound) * y + (row[3] // (X_bound * Y_bound)) * x * y    if not ploy.is_constant():        polys.append(ploy)    if len(polys) &gt;= 2: # 由于我们构造的格中有两个短向量，所以我们只需要前两个非零多项式来求解        breakx_root = Nonef1 = polys[0]f2 = polys[1]res = f1.resultant(f2, y) # 消去 y，得到关于 x 的单变量多项式if not res.is_constant():    roots = res.univariate_polynomial().roots() # 求解 x 的根    for r, _ in roots:        if 0 &lt;= r &lt; X_bound:            f1_x = f1.subs(x=r) # 将 x 的值代入 f1，得到关于 y 的单变量多项式            y_roots = f1_x.univariate_polynomial().roots()            for ry, _ in y_roots: # 验证 (x, y) 是否满足原方程                if (r * ry + C_x * r + C_y * ry + C_0) % p == 0:                    x_root = r                    breakt1 = A + Integer(x_root)m = (k * inverse(t1, p) - r1) % pflag = long_to_bytes(int(m))print(flag)# b&#39;flag{u_know_the_bivariate_coppersmith!!!}&#39;</code></pre><h2><span id="boneh-durfee-攻击">Boneh Durfee 攻击 😝</span></h2><p>Boneh Durfee 攻击是一种针对 RSA 加密算法的攻击方法，主要用于攻击 RSA 的私钥 $d$ 的构造不当的情况。具体来说，当 RSA 的私钥 $d$ 满足 $d &lt; N^{0.292}$ 时，攻击者可以利用 Boneh Durfee 攻击来恢复私钥 $d$。这种方法是优于 Wiener 攻击的，因为它可以攻击更大的私钥范围。</p><p>从 $e \cdot d = k \cdot \phi(n) + 1$ 开始，直接取模 $e$，我们得到：</p><script type="math/tex; mode=display">k \cdot \phi(n) + 1 \equiv 0 \mod e</script><p>由 $n = p \cdot q$ 和 $\phi(n) = (p-1)(q-1)$，我们可以将 $\phi(n)$ 表示为 $n - (p + q) + 1$。因此，我们可以将上面的方程改写为：</p><script type="math/tex; mode=display">k \cdot (n - (p + q) + 1) + 1 \equiv 0 \mod e</script><p>转化一下，我们可以得到：</p><script type="math/tex; mode=display">2 k (\frac{n + 1}{2} - \frac{p + q}{2}) + 1 \equiv 0 \mod e</script><p>设 $A = \frac{n + 1}{2}$，$x = 2 k$，$y = \frac{p + q}{2}$，我们可以将上面的方程改写为：</p><script type="math/tex; mode=display">f(x, y) = x \cdot (A - y) + 1 \mod e</script><p>我们可以构造多项式 $f(x,y)$，要求找到它在模 $e$ 下  的小根。为了通过LLL算法求解，我们需<br>要构造一个格，其基向量由 $f(x,y)$ 在模 $e^m$ 下的多个平移（shifts）构成。</p><p>我们定义两类平移多项式:<br>x-shifts：</p><script type="math/tex; mode=display">g_{k,i}(x,y) = x^і f(x,y)^k e^{m-k}, 其中 k \in [0,m],i \in [0,m-k]</script><p>y-shifts：</p><script type="math/tex; mode=display">h_{k,j}(x,y) = y^j f(x,y)^k e^{m-k}, 其中 k \in [0,m],j \in [1,m-k]</script><p>然后，我们将 $x$ 替换为 $xX$，$y$ 替换为 $yY$ 以便在格中编码目标上界，根据这些多项式的系数向量构造出一个方阵(Lattice Matrix)。通过运行LLL算法对格进行格基规约，即可在规约后的基中找到范数足够低的向量，这些向量对应于在 $\mathbb{Z}$ 上有相同小根的多项式。</p><p>设 $m$ 为格的阶，$t$ 为位移次数，以两者取 $1$ 为例构造格：</p><script type="math/tex; mode=display">\begin{bmatrix}e & 0 & 0 & 0 & 0 \\0 & eX & 0 & 0 & 0 \\0 & 0 & Y & A \cdot Y & - X \cdot Y^2 \\1 & A \cdot X & 0 & - X \cdot Y & 0\end{bmatrix}</script><p>在真正的 Boneh-Durfee 攻击脚本中（比如 $m = 4$，$t = 2$ 时），矩阵维度会到达数十维。如果我们巧妙地排序（通常按照 $x$ 的次数和组合），矩阵会呈现出分块对角或下三角的形状：</p><script type="math/tex; mode=display">M = \begin{bmatrix}M_x & M_{xy} \\0 & M_y\end{bmatrix}</script><p>其中：</p><ul><li>左上角的块 $M_x$ 对应于 x-shifts，在合理排序后，这部分恰好是一个关于单项式的下三角矩阵，主对角线上的元素都是 $e^{m-k}X^i Y^j$ 形式的对角元。</li><li>右下角的块 $M_y$，对应于 y-shifts。这些多项式会在矩阵的右侧带来额外的列（代表高阶的 $y$ 和 $xy$ 单项式），需要将这部分一并丢入LLL 算法进行规约。</li></ul><p>LLL格基规约的作用就是通过行变换，使得底部的向量（即矩阵的行）变得尽可能短。由于所有行对应的多项式在模 $e^m$ 的意义下根不变，规约后得到的最最短向量对应的那个多项式如果在全空间上的值绝对值小于 $e^m$，就可以直接在整数环 $\mathbb{Z}$ 上求解其根 $x_0$，$y_0$ 了。</p><h3><span id="boneh-durfee-攻击例题">Boneh Durfee 攻击例题</span></h3><p><a href="https://files.catbox.moe/ucqb7l.zip">Boneh Durfee 攻击例题下载</a></p><pre><code class="lang-python">from Crypto.Util.number import *from secret import mp = getPrime(512)q = getPrime(512)n = p * qphi = (p - 1) * (q - 1)print(&quot;n =&quot;, hex(n))d = getPrime(int(1024 * 0.270))e = inverse(d,phi)print(&quot;e =&quot;, hex(e))c = pow(m, e, n)print(&quot;c =&quot;, hex(c))# n = 0xe788a2054a29f6769c2586a6faec7b2b5880317d5ca6a4595e9f07e164c00e34a0e469ef5127a46d1a4a0e9957189031d735d81bd4a8c80ce4b14b9c76aaa4863b629536ad6d7095a2358375d87155a65f981151b5e0d3591636da13ec004f1b545999ee1de21c790c73fd2ed7338b325ad5df231a193398cb1905a81f499bf1# e = 0x3d2638e4f1975d3e30470f9b99c799229016f24a74560c92f83a5157a1276bb25eae05ca6a5f9f345d4333602d6c3af54ad4d433eaab55d4f457fbbd78807e85d7f6c3266655dc9cf86d6dcf1d06c5a2c630a4b5ef10a67bb6eacb2e188c33399b1a636ad0bd8b912cb42ec54f73ec965c1ecca7df8d74923b98e93b175bfaf7# c = 0x694e83ff962e9e98e3aeea50eac9c97f22ce06bff699819c68c6b91c71ad49fad7b2f2bf6eb8dd92aee3234229ce8f8bb52e5894f5551bde341a5e18682776d33353a571d883da4ab40d55efdcc0517d4a8c16a63b01926bfbc6594c9123fc8b19b1cc8996f6466f4363f83e3541929c7c1b73221bf2127b1165d027e925f55d</code></pre><p>在这个例题中，我们构造了一个 RSA 加密系统，其中私钥 $d$ 满足 Boneh Durfee 攻击的条件。我们使用 SageMath 来实现 Boneh Durfee 攻击，通过构造格并进行 LLL 规约来恢复私钥 $d$，最终解密出消息 $m$。</p><pre><code class="lang-python">from sage.all import *from Crypto.Util.number import *n = 0xe788a2054a29f6769c2586a6faec7b2b5880317d5ca6a4595e9f07e164c00e34a0e469ef5127a46d1a4a0e9957189031d735d81bd4a8c80ce4b14b9c76aaa4863b629536ad6d7095a2358375d87155a65f981151b5e0d3591636da13ec004f1b545999ee1de21c790c73fd2ed7338b325ad5df231a193398cb1905a81f499bf1e = 0x3d2638e4f1975d3e30470f9b99c799229016f24a74560c92f83a5157a1276bb25eae05ca6a5f9f345d4333602d6c3af54ad4d433eaab55d4f457fbbd78807e85d7f6c3266655dc9cf86d6dcf1d06c5a2c630a4b5ef10a67bb6eacb2e188c33399b1a636ad0bd8b912cb42ec54f73ec965c1ecca7df8d74923b98e93b175bfaf7c = 0x694e83ff962e9e98e3aeea50eac9c97f22ce06bff699819c68c6b91c71ad49fad7b2f2bf6eb8dd92aee3234229ce8f8bb52e5894f5551bde341a5e18682776d33353a571d883da4ab40d55efdcc0517d4a8c16a63b01926bfbc6594c9123fc8b19b1cc8996f6466f4363f83e3541929c7c1b73221bf2127b1165d027e925f55ddef boneh_durfee_attack(N, e, m, t, X, Y):    &quot;&quot;&quot;    Boneh-Durfee 攻击主函数    N, e: RSA 公钥    m, t: 决定格大小的超参数    X, Y: 根的上界    &quot;&quot;&quot;    PR.&lt;x,y&gt; = PolynomialRing(ZZ)    A = (N + 1) // 2    f = x*(A - y) + 1    shifts = []    # x-shifts    for k in range(m + 1):        for i in range(m - k + 1):            shifts.append(x^i * f^k * e^(m - k))    # y-shifts    for k in range(m + 1):        for j in range(1, t + 1):            shifts.append(y^j * f^k * e^(m - k))    # 提取所有的单项式 (x^i * y^j)    monomials = set()    for shift in shifts:        monomials.update(shift.monomials())    monomials = sorted(list(monomials)) # 固定单项式顺序，即矩阵的列    # 构建格矩阵    dim = len(shifts)    M = Matrix(ZZ, dim, dim)    for i, shift in enumerate(shifts):        shift_scaled = shift(X*x, Y*y)        for j, mon in enumerate(monomials):            M[i, j] = shift_scaled.monomial_coefficient(mon)    print(f&quot;[+] 格矩阵构造完成，维度: {dim}x{dim}&quot;)    print(&quot;[+] 正在执行 LLL 格基规约...&quot;)    M_lll = M.LLL()    # 还原多项式，寻找代数无关解    PR2.&lt;x, y&gt; = PolynomialRing(ZZ)    polys = []    for i in range(M_lll.nrows()):        if M_lll[i].is_zero():            continue        poly = 0        for j, mon in enumerate(monomials):            val = M_lll[i, j] // mon(X, Y)            poly += val * mon(x, y)        if poly != 0:            polys.append(poly)    print(f&quot;[+] 获取到了 {len(polys)} 个短多项式，开始求解公根...&quot;)    # 因为计算 Resultant 非常慢，这里我们只取前三个最短的非零多项式进行两两组合求解    for i in range(min(3, len(polys))):        for j in range(i+1, min(4, len(polys))):            p1 = polys[i]            p2 = polys[j]            try:                # 使用结式消元                res = p1.resultant(p2, x)                if res.is_zero():                    continue                res_y = res.univariate_polynomial()                roots = res_y.roots()                if roots:                    for y0, _ in roots:                        p1_univariate = p1.subs(y=y0).univariate_polynomial()                        if p1_univariate.is_zero():                            p1_univariate = p2.subs(y=y0).univariate_polynomial()                        if not p1_univariate.is_zero():                            x_roots = p1_univariate.roots()                            if x_roots:                                x0 = x_roots[0][0]                                if (x0 * (A - y0) + 1) % e == 0:                                    return x0, y0            except Exception as ex:                continue    return None, Nonedelta = 0.27bits = 1024X = floor(2 * e**delta)Y = floor(n**0.5)m = 7t = int((m + 1) * (1 - 2*delta)) + 1x0, y0 = boneh_durfee_attack(n, e, m, t, X, Y)if x0 is not None:    print(f&quot;[!] 找到小根 x = {x0}, y = {y0}&quot;)        # 根据我们设定的 y = (p+q)/2    half_sum = y0    sum_pq = 2 * half_sum    # 构造二次方程 Z^2 - (p+q)Z + N = 0 求解 p, q    R.&lt;Z&gt; = PolynomialRing(ZZ)    f = Z^2 - sum_pq * Z + n    pq_roots = f.roots()    if pq_roots:        p = pq_roots[0][0]        q = pq_roots[1][0]        phi = (p - 1) * (q - 1)        d = inverse_mod(e, phi)        m = pow(c, d, n)        flag = long_to_bytes(m)        print(f&quot;Flag: {flag}&quot;)# Flag: b&#39;flag{boneh_durfee_attack_is_fun!}&#39;</code></pre><p>上一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/4005.html">RSA加解密专题 - Coppersmith 相关攻击一</a> 👈<br>下一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/4007.html">RSA加解密专题 - 预言机相关攻击</a> 👈<br>回到开始：<a href="http://lr2006-robot.github.io/myblog.github.io/about/">关于我</a> 👈</p><p>相关链接：<br><a href="http://lr2006-robot.github.io/myblog.github.io/post/4004.html">RSA加解密专题 - 公私钥格式相关攻击</a> 👈</p>]]>
    </content>
    <id>https://lr2006-robot.github.io/myblog.github.io/post/4006.html</id>
    <link href="https://lr2006-robot.github.io/myblog.github.io/post/4006.html"/>
    <published>2026-03-10T14:33:00.000Z</published>
    <summary>
      <![CDATA[<p>上一章我们介绍了 Coppersmith 相关攻击的基本原理和实现方法，帮助大家了解了如何利用 Coppersmith 方法来攻击 RSA 加密算法。接下来，我们将继续深入探讨 Coppersmith 相关攻击的应用。</p>
<p>欢迎来到《密码学核心算法实战》的 Cop]]>
    </summary>
    <title>RSA加解密专题 - Coppersmith 相关攻击二</title>
    <updated>2026-03-15T01:59:08.779Z</updated>
  </entry>
  <entry>
    <author>
      <name>Dorange</name>
    </author>
    <category term="密码学算法" scheme="https://lr2006-robot.github.io/myblog.github.io/categories/%E5%AF%86%E7%A0%81%E5%AD%A6%E7%AE%97%E6%B3%95/"/>
    <category term="密码学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="数学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E6%95%B0%E5%AD%A6/"/>
    <category term="算法" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<p>上一章我们介绍了 BSGS（Baby-Step Giant-Step）算法的基本原理和实现方法，帮助大家快速求解离散对数问题。接下来，我们将继续深入探讨 BSGS 算法的另一种应用。除了在离散对数问题中，BSGS 算法也可以用于求解 ECC（Elliptic Curve Cryptography）中的离散对数问题，这在现代密码学中具有重要的实际意义。</p><p>欢迎来到《密码学核心算法实战》的 BSGS 算法二专题！这里没有纸上谈兵的理论空谈（真的不画大饼😉），只有一把把能直接撬动数据安全的精密齿轮⚙️。</p><h2><span id="ecc中的bsgs算法">ECC中的BSGS算法 🌟</span></h2><p>在 ECC 中，离散对数问题的形式是：给定一个椭圆曲线 $E$ 上的点 $P$ 和 $Q$，找到一个整数 $k$ 使得 $Q = kP$。这个问题被称为椭圆曲线离散对数问题（ECDLP），是 ECC 加密算法安全性的基础。而 BSGS 算法可以用来求解 ECDLP，从而在某些情况下破解 ECC 加密。</p><h2><span id="ecc中的bsgs算法的原理">ECC中的BSGS算法的原理 😵</span></h2><p>既然有前一章的铺垫，那么原理就直接简单讲解了。</p><p>求解 $x$ 使得 $Q = xP$，我们可以将 $x$ 表示为 $x = i \cdot m - j$，其中 $m = \lceil \sqrt{n} \rceil$，$i \in [0, m)$ （大步），$j \in [0, m)$ （小步）。这样我们可以将 ECDLP 转化为以下形式：</p><script type="math/tex; mode=display">i \cdot mP = Q + jP</script><p>小步（Baby Step）：计算 $Q + jP$，对于 $j \in [0, m)$，将结果存入哈希表（键为点的坐标，值为 $j$）。每次都是加 $P$，所以叫做小步。</p><p>大步（Giant Step）：计算 $mP$，对于 $i \in [0, m)$，计算 $i \cdot mP$，检查是否在哈希表中存在对应的点，如果存在，则可以得到 $x = i \cdot m - j$。每次都是加 $mP$，所以叫做大步。</p><h2><span id="ecc中的bsgs算法的实现">ECC中的BSGS算法的实现 🙃</span></h2><pre><code class="lang-python">import mathclass EllipticCurve:    def __init__(self, a, b, p):        self.a = a        self.b = b        self.p = p    # 求逆元 (Fermat&#39;s little theorem)    def inverse(self, val):        return pow(val, self.p - 2, self.p)    # 点加运算 P + Q    def add(self, P, Q):        if P is None: return Q        if Q is None: return P        x1, y1 = P        x2, y2 = Q        if x1 == x2 and (y1 + y2) % self.p == 0:            return None # 也就是无穷远点 O        if P == Q:            # 斜率 k = (3x^2 + a) / 2y            lam = (3 * x1 * x1 + self.a) * self.inverse(2 * y1) % self.p        else:            if x1 == x2: return None # 垂直切线            # 斜率 k = (y2 - y1) / (x2 - x1)            lam = (y2 - y1) * self.inverse(x2 - x1) % self.p        x3 = (lam * lam - x1 - x2) % self.p        y3 = (lam * (x1 - x3) - y1) % self.p        return (x3, y3)    # 标量乘法 k * P (Double-and-Add 算法)    def multiply(self, k, P):        res = None        current = P        while k &gt; 0:            if k % 2 == 1:                res = self.add(res, current)            current = self.add(current, current)            k //= 2        return resdef bsgs_ecc(curve, P, Q, order):    &quot;&quot;&quot;    ECC 上的 BSGS 算法    求解 x 使得 Q = xP    原理: x = i * m - j =&gt; i * mP = Q + jP    :param curve: 椭圆曲线对象    :param P: 基点 (Generator)    :param Q: 目标点 (Public Key)    :param order: 群的阶 n (或者一个足够大的数)    :return: 离散对数 x    &quot;&quot;&quot;    # 1. 计算步长 m = ceil(sqrt(n))    m = math.ceil(math.sqrt(order))    # 2. 小步 (Baby Step)    # 计算 Q + jP，其中 j ∈ [0, m)    # 我们构建一个哈希表: { Point: j }    step_dict = dict()    current_point = Q # 初始值为 Q + 0P    for j in range(m):        # 将点作为 key 存入 (注意点需要是可哈希的类型，元组是可以的)        if current_point not in step_dict:             step_dict[current_point] = j        # 下一步：Q + (j+1)P = (Q + jP) + P        current_point = curve.add(current_point, P)    # 3. 大步 (Giant Step)    # 计算 i * (mP)，其中 i ∈ [1, m] (通常遍历到 m+1 保证覆盖)    # 先算出 mP    mP = curve.multiply(m, P)    current_giant = mP # 初始值为 1 * mP    for i in range(1, m + 1):        # 检查 i * mP 是否在 Baby Steps 中        if current_giant in step_dict:            j = step_dict[current_giant]            # 找到碰撞：i * mP = Q + jP            # Q = i * mP - jP = (i * m - j) P            x = i * m - j            return x        # 下一步：(i+1)mP = imP + mP        current_giant = curve.add(current_giant, mP)    return None</code></pre><h2><span id="sagemath-偷懒">SageMath 偷懒 🤓👆</span></h2><p>有同学说：“博主，博主，你的算法确实很厉害，但是还是太吃操作了，有没有更加简单无脑的用法？”👻<br>有的，兄弟有的，这样的算法在 SageMath 中早就已经被封装好了🤫，我们直接调用就行了：</p><pre><code class="lang-python">from sage.all import discrete_log# 1. 定义椭圆曲线 E: y² = x³ + ax + b mod pp = pE = EllipticCurve(GF(p), [a, b])  # 格式：EllipticCurve(有限域, [a, b])，对应 y²=x³+ax+b# 2. 定义基点 P 和公钥 QP = E(x_p, y_p)  # 基点 Q = E(x_q, y_q)  # 公钥 n = P.order()# 3. 求解离散对数：找 x 使得 x*P = Q（底层用BSGS/Pollard&#39;s Rho等算法，更加完善）private_key = discrete_log(Q, P, n, operation=&#39;+&#39;)print(f&quot;私钥 x = {private_key}&quot;)</code></pre><p>怎么样，是不是非常简单？🤣👉🤡</p><p>上一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/3008.html">密码学算法 - BSGS一 算法</a> 👈<br>下一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/3010.html">密码学算法 - AMM 算法</a> 👈<br>回到开始：<a href="http://lr2006-robot.github.io/myblog.github.io/about/">关于我</a> 👈</p><p>相关链接：<br><a href="http://lr2006-robot.github.io/myblog.github.io/post/2006.html">加解密模式分析 - ECC 加密算法</a> 👈</p>]]>
    </content>
    <id>https://lr2006-robot.github.io/myblog.github.io/post/3009.html</id>
    <link href="https://lr2006-robot.github.io/myblog.github.io/post/3009.html"/>
    <published>2026-03-09T11:03:00.000Z</published>
    <summary>
      <![CDATA[<p>上一章我们介绍了 BSGS（Baby-Step Giant-Step）算法的基本原理和实现方法，帮助大家快速求解离散对数问题。接下来，我们将继续深入探讨 BSGS 算法的另一种应用。除了在离散对数问题中，BSGS 算法也可以用于求解 ECC（Elliptic Curve C]]>
    </summary>
    <title>密码学算法 - BSGS 算法二</title>
    <updated>2026-03-14T07:16:20.547Z</updated>
  </entry>
  <entry>
    <author>
      <name>Dorange</name>
    </author>
    <category term="RSA加解密专题" scheme="https://lr2006-robot.github.io/myblog.github.io/categories/RSA%E5%8A%A0%E8%A7%A3%E5%AF%86%E4%B8%93%E9%A2%98/"/>
    <category term="密码学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="前言" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%89%8D%E8%A8%80/"/>
    <category term="RSA" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/RSA/"/>
    <content>
      <![CDATA[<p>Coppersmith 攻击是一种针对 RSA 加密算法的攻击方法，主要用于在特定条件下恢复 RSA 私钥或解密 RSA 密文。该攻击方法由 Don Coppersmith 在 1996 年提出，利用了 RSA 加密算法中的数学结构和性质。</p><p>这里我们并不会深入到 Coppersmith 攻击的数学细节，但我们会介绍一些相关的攻击场景和原理，以帮助你理解 Coppersmith 攻击的基本思想和应用。</p><h2><span id="部分明文泄露攻击">部分明文泄露攻击 😋</span></h2><p>单一变量的情况下，我们主要关注构建一个多项式方程，并使用 Coppersmith 方法来寻找小根。这种方法可以在某些特定条件下成功，例如当明文的某些部分已知或满足特定的数学关系时。</p><p>最简单的情况莫过于明文的某些部分已知，不管是哪一种泄露直接套用加密的方程就好：</p><script type="math/tex; mode=display">\begin{align*}(m_h + x)^e &\equiv c \pmod{n} \\(m_h + x \ll m_l.\text{nbits}() + m_l)^e &\equiv c \pmod{n} \\(x \ll m_l.\text{nbits}() + m_l)^e &\equiv c \pmod{n}\end{align*}</script><p>其中 $m_h$ 是已知的明文部分，$x$ 是未知的部分，$e$ 是公钥指数，$c$ 是密文，$n$ 是模数。</p><h3><span id="部分明文泄露攻击例题">部分明文泄露攻击例题</span></h3><p><a href="https://files.catbox.moe/up8r2d.zip">部分明文泄露攻击例题下载</a></p><pre><code class="lang-python">from Crypto.Util.number import *from secret import mp = getPrime(512)q = getPrime(512)n = p*qe = 5m_h = (m &gt;&gt; 72) &lt;&lt; 72c = pow(m, e, n)print(&quot;n =&quot;, n)print(&quot;c =&quot;, c)print(&quot;e =&quot;, e)print(&quot;m_h =&quot;, m_h)# n = 71406081470098943664147354595360130965949415477183415925937691459300487381042569174133696785917044437192425109528474145856370063591801161214117594482262437036039582313557538014983339960352144743243869648717280156439092931956094810765560689842129272908038382363737252851872602935800129772310097494871743999683# c = 60258767266725462475089309010639723372675479396874103345307979963872676339512613518477686690014996946520051358953694535853808486956187104018894409809787475031000910824770481120529345447944422364229572306876886435743357596246183561495740412854973420619617616182147277760953828917472063677119186309853762339354# e = 5# m_h = 777244835068427274667156821981506353228924473235252582307290753380891138229664743424</code></pre><p>在这个例题中，我们生成了一个 RSA 密钥对，并加密了一个明文消息。我们知道明文的高位部分 $m_h$，并且我们有密文 $c$ 和公钥指数 $e$。使用 Coppersmith 方法，我们可以尝试找到未知的部分 $x$，从而恢复完整的明文消息。</p><p>解题代码示例：</p><pre><code class="lang-python">from sage.all import *n = 71406081470098943664147354595360130965949415477183415925937691459300487381042569174133696785917044437192425109528474145856370063591801161214117594482262437036039582313557538014983339960352144743243869648717280156439092931956094810765560689842129272908038382363737252851872602935800129772310097494871743999683c = 60258767266725462475089309010639723372675479396874103345307979963872676339512613518477686690014996946520051358953694535853808486956187104018894409809787475031000910824770481120529345447944422364229572306876886435743357596246183561495740412854973420619617616182147277760953828917472063677119186309853762339354e = 5m_h = 777244835068427274667156821981506353228924473235252582307290753380891138229664743424P.&lt;x&gt; = PolynomialRing(Zmod(n))f = (x + m_h)^e - croots = f.small_roots(bound=2^72, epsilon=1/32)for r in roots:    flag = long_to_bytes(int(r + m_h))    print(flag)# b&#39;flag{the_leaked_flag_is_not_secure}&#39;</code></pre><h2><span id="模数部分泄露攻击">模数部分泄露攻击 😎</span></h2><p>在某些情况下，攻击者可能会获得 RSA 模数 $p$ 的部分信息，例如知道 $p$ 的高位或低位。这种信息泄露可以被利用来构建多项式方程，并使用 Coppersmith 方法来寻找小根，从而恢复完整的模数 $p$。</p><p>模数相关的方程构建直接使用 $n$ 的定义就好，高位泄露和低位泄露都是一样的思路：</p><script type="math/tex; mode=display">\begin{aligned}(p_h + x)q &\equiv 0 \mod n \\(x \ll p_h.nbits() + p_h)q &\equiv 0 \mod n\end{aligned}</script><p>其中 $p_h$ 是已知的模数部分，$x$ 是未知的部分，$q$ 是另一个素数。</p><h3><span id="模数部分泄露攻击例题">模数部分泄露攻击例题</span></h3><p><a href="https://files.catbox.moe/x0zzy2.zip">模数部分泄露攻击例题下载</a></p><pre><code class="lang-python">from Crypto.Util.number import *from secret import mp = getPrime(512)q = getPrime(512)n = p*qe = 65537p_h = (p &gt;&gt; 128) &lt;&lt; 128c = pow(m, e, n)print(&quot;n =&quot;, n)print(&quot;c =&quot;, c)print(&quot;e =&quot;, e)print(&quot;p_h =&quot;, p_h)# n = 148343204993341213021619313428058654106890149696397492706333896400186219477227728301676148709241129304306574087724304991489318859287047163276539541481697268017678181261322191026527154404707813733872358303403566566327058403912886903786387007581372068922650740322771090553555674369191355108765322667860494558677# c = 39156544505474625229023477922212602423468384619737085007608097248472070125608788562256041364458195493803172264223273233547146479148821267828148585094956004078892557930333392606045896106072959524844360325819706205800762438141913680222858798397571618769159479716837722530469152413770655154791443613805271743833# e = 65537# p_h = 12734678186795088622000398950939657451514087254410587977928786315334611555210018653020273683790430319996027334767103524767798553242990607416419371588780032</code></pre><p>在这个例题中，我们生成了一个 RSA 密钥对，并加密了一个明文消息。我们知道模数 $p$ 的高位部分 $p_h$，并且我们有密文 $c$ 和公钥指数 $e$。使用 Coppersmith 方法，我们可以尝试找到未知的部分 $x$，从而恢复完整的模数 $p$，进而解密消息。</p><p>解题代码示例：</p><pre><code class="lang-python">from sage.all import *n = 148343204993341213021619313428058654106890149696397492706333896400186219477227728301676148709241129304306574087724304991489318859287047163276539541481697268017678181261322191026527154404707813733872358303403566566327058403912886903786387007581372068922650740322771090553555674369191355108765322667860494558677c = 39156544505474625229023477922212602423468384619737085007608097248472070125608788562256041364458195493803172264223273233547146479148821267828148585094956004078892557930333392606045896106072959524844360325819706205800762438141913680222858798397571618769159479716837722530469152413770655154791443613805271743833e = 65537p_h = 12734678186795088622000398950939657451514087254410587977928786315334611555210018653020273683790430319996027334767103524767798553242990607416419371588780032P.&lt;x&gt; = PolynomialRing(Zmod(n))f = x + p_hroots = f.small_roots(bound=2^128, beta = 0.4, epsilon=1/32)for r in roots:    p = int(r + p_h)    q = n // p    phi = (p-1)*(q-1)    d = inverse_mod(e, phi)    m = pow(c, d, n)    flag = long_to_bytes(m)    print(flag)# b&#39;flag{the_leaked_p_is_not_secure}&#39;</code></pre><h2><span id="私钥低位泄露攻击">私钥低位泄露攻击 🧐</span></h2><p>在某些情况下，攻击者可能会获得 RSA 私钥 $d$ 的部分信息，例如知道 $d$ 的低位。这种信息泄露可以被利用来构建多项式方程，并使用 Coppersmith 方法来寻找小根，从而恢复完整的私钥 $d$。</p><p>这个时候就好灵活构建方程了，要先认识到低位，可以理解成原来的方程在取模时的值，这样在取模后就可以达到一个未知数暂时不参与的效果。</p><h3><span id="私钥低位泄露攻击例题">私钥低位泄露攻击例题</span></h3><p><a href="https://files.catbox.moe/u1a3st.zip">私钥低位泄露攻击例题下载</a></p><pre><code class="lang-python">from Crypto.Util.number import *from secret import mp = getPrime(512)q = getPrime(512)n = p*qe = 3d = inverse_mod(e, (p-1)*(q-1))d_l = d &amp; ((1 &lt;&lt; 512) - 1)c = pow(m, e, n)print(&quot;n = &quot;, n)print(&quot;e = &quot;, e)print(&quot;c = &quot;, c)print(&quot;d_l = &quot;, d_l)# n = 78036226711261137562635523171733270722300007283095085193438779642343110620250804479566251132258821498672359932995596424916036883417080280241580905118033281785063140297467453715900194715947245388137276189087436097121831057248623061596822311980543096721479575328572293908180835278420031510114830258210990389017# e = 3# c = 4458558515804294040125006969671194268520221962979792020774793426952905371959925004280044612155954386479615226026601709155668384798849357146766830652052906333248686079022711702984275745125# d_l = 1548125634003834517457946342794474179624692403859149914470848791910513316602949323478233517628159099938386644246361533546700081991087641483936928476761795</code></pre><p>根据RSA相关参数我们可以知道的是 $e \cdot d = k \cdot \phi(n) + 1$，其中 $k$ 是一个整数。我们设 $s = p + q$，则有 $e \cdot d = k \cdot (n - s + 1) + 1$。</p><p>重点来了，已知 $d$ 的低位部分 $d_l$，我们可以直接将方程取模 $2^{512}$，得到：</p><script type="math/tex; mode=display">\begin{aligned}e \cdot d_l = k \cdot (n - s + 1) + 1 \mod 2^{512} \\s \equiv n + 1 - (e \cdot d_l - 1) \cdot k^{-1} \mod 2^{512}\end{aligned}</script><p>然后联系根为 $p$ 和 $q$ 方程 $x^2 - s \cdot x + n = 0$，我们就可以构建一个关于 $x$ 的多项式方程，并使用贪心加剪枝的方法求解 $512$ 位的 $p$ 和 $q$。如果 $p$ 和 $q$ 都大于 $512$ 位，那么我们可以通过 Coppersmith 方法来寻找小根。</p><p>解题代码示例：</p><pre><code class="lang-python">from sage.all import *from Crypto.Util.number import *n = 78036226711261137562635523171733270722300007283095085193438779642343110620250804479566251132258821498672359932995596424916036883417080280241580905118033281785063140297467453715900194715947245388137276189087436097121831057248623061596822311980543096721479575328572293908180835278420031510114830258210990389017e = 3c = 4458558515804294040125006969671194268520221962979792020774793426952905371959925004280044612155954386479615226026601709155668384798849357146766830652052906333248686079022711702984275745125d_l = 1548125634003834517457946342794474179624692403859149914470848791910513316602949323478233517628159099938386644246361533546700081991087641483936928476761795def recover_p_general(d_l, e, n):    d_l_sage = Integer(d_l)  # 转换为Sage的Integer类型    known_bits = d_l_sage.nbits()  # 现在可以正常调用nbits()    for k in range(1, e + 1):        A = e * d_l - 1        if A % k != 0: continue        # 1. 也是算出只模已知位数的 s_mod        s_mod = (n + 1 - (A // k)) % (2**known_bits)        # 2. 也是逐位解 x^2 - s_mod*x + n = 0        res = [1]        for i in range(1, known_bits):            next_res = []            for r in res:                for b in (0, 1):                    x = r | (b &lt;&lt; i)                    if (x**2 - s_mod*x + n) % (2**(i+1)) == 0:                        next_res.append(x)            res = next_res        # 3. 对每一个得到的部分低位 p_low 使用 small_roots 求高位        for p_low in res:            # 如果碰巧拿到全量位了，直接除除看            if n % p_low == 0 and p_low != 1 and p_low != n:                return p_low            # 如果没拿到全量，用 small_roots 把剩下高位求出来            PR.&lt;x&gt; = PolynomialRing(Zmod(n))            f = x * (2**known_bits) + p_low    # p = x * 2^m + p_low            f = f.monic()            # X是预估未知高位的值域上限，假设 p 大约在靠近 sqrt(n) 附近            n_sage = Integer(n)            unknown_bits = (n_sage.nbits() // 2) - known_bits            X_bound = 2**(unknown_bits + 5)   # +5 留一点余量            if X_bound &gt; 2: # 也就是确实有未知位需要求                try:                    # beta=0.4 反映了我们要找的 p 的位数约占总位数的比例（略小于一半）                    roots = f.small_roots(X=X_bound, beta=0.4)                    if roots:                        p = int(roots[0]) * (2**known_bits) + p_low                        if n % p == 0:                            return p                except Exception as e:                    pass    return Nonep = recover_p_general(d_l, e, n)q = n // pphi = (p-1)*(q-1)d = inverse_mod(e, phi)m = pow(c, d, n)print(long_to_bytes(m))# b&#39;flag{the_leak_of_d_is_bad}&#39;</code></pre><h2><span id="håstad-广播攻击扩展">Håstad 广播攻击扩展 🤠</span></h2><p>前面我们提到过 Håstad 广播攻击，而 Coppersmith 方法也可以被用来扩展 Håstad 广播攻击的适用范围。在 Coppersmith 方法的帮助下，我们可以将问题推广到更一般的情况，就是加密的不再是相同的 $m$，而是具有线性关系的 $m_i$。</p><h3><span id="håstad-广播攻击扩展例题">Håstad 广播攻击扩展例题</span></h3><p><a href="https://files.catbox.moe/n3p7fp.zip">Håstad 广播攻击扩展例题下载</a></p><pre><code class="lang-python">from Crypto.Util.number import *from secret import mp1 = getPrime(256)p2 = getPrime(256)p3 = getPrime(256)q1 = getPrime(256)q2 = getPrime(256)q3 = getPrime(256)n1 = p1*q1n2 = p2*q2n3 = p3*q3e = 3c1 = pow(m, e, n1)c2 = pow(m+1, e, n2)c3 = pow(2*m+2, e, n3)print(&quot;n1 = &quot;, n1)print(&quot;c1 = &quot;, c1)print(&quot;n2 = &quot;, n2)print(&quot;c2 = &quot;, c2)print(&quot;n3 = &quot;, n3)print(&quot;c3 = &quot;, c3)# n1 =  7436297924479111288858365740455970275623094012645820223363041858682843243234345296777579027762343770914910949000282160537982015904948267903325514335621283# c1 =  4325495502980350621916618330673099516626441599498586125877854511173538779941000124091552361831740416431308367276617945025243861814840900084833354971434577# n2 =  6456104243856281808527299663033415352202343985414289970319782054731415622452561418810434572150687245284010827848197758737921807475656221001397521660559891# c2 =  2003003821228318250607284803400253958526269528049733743569235891789477388365756120493530569516748649272916121719028297249295064499105886124376055740719578# n3 =  8769049710633699648972640916530598241721246817703490720809168840216439564713379108405895103749642237199313984669343902262587767037986004793227502470940611# c3 =  7583851145248429122395092819244845051341655099143487890806191663124326571012675198159624097396942593587956402263246628310823739943969343448935180194204</code></pre><p>这一题我们使用 CRT 构造一组标准基向量，通过线性组合正确合并三个多项式：</p><script type="math/tex; mode=display">l_i \equiv \begin{cases} 1 \pmod{n_i} \\ 0 \pmod{n_j} (j \neq i) \end{cases}</script><p>后面用 $F = l_1f_1 + l_2f_2 + l_3f_3$ 就可以把三个方程合成一个方程，使得满足 $F \equiv 0 \pmod{n_i}$。剩下的就是求这个方程的整数根了。</p><p>解题代码示例：</p><pre><code class="lang-python">from Crypto.Util.number import *from sage.all import *n1 =  7436297924479111288858365740455970275623094012645820223363041858682843243234345296777579027762343770914910949000282160537982015904948267903325514335621283c1 =  4325495502980350621916618330673099516626441599498586125877854511173538779941000124091552361831740416431308367276617945025243861814840900084833354971434577n2 =  6456104243856281808527299663033415352202343985414289970319782054731415622452561418810434572150687245284010827848197758737921807475656221001397521660559891c2 =  2003003821228318250607284803400253958526269528049733743569235891789477388365756120493530569516748649272916121719028297249295064499105886124376055740719578n3 =  8769049710633699648972640916530598241721246817703490720809168840216439564713379108405895103749642237199313984669343902262587767037986004793227502470940611c3 =  7583851145248429122395092819244845051341655099143487890806191663124326571012675198159624097396942593587956402263246628310823739943969343448935180194204N = n1*n2*n3R_mod.&lt;m&gt; = PolynomialRing(Zmod(N))f1 = m**3 - c1f2 = (m+1)**3 - c2f3 = ((2*m+2)**3 - c3) * inverse_mod(8, N)c_1 = crt([1, 0, 0], [n1, n2, n3])c_2 = crt([0, 1, 0], [n1, n2, n3])c_3 = crt([0, 0, 1], [n1, n2, n3])F_mod = c_1 * f1 + c_2 * f2 + c_3 * f3F_mod = F_mod.monic()X = 2**256roots = F_mod.small_roots(X=X, beta=1.0)flag = long_to_bytes(int(roots[0]))print(flag)# b&#39;flag{coppersmith_is_very_useful}&#39;</code></pre><p>上一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/4004.html">RSA加解密专题 - 公私钥格式相关攻击</a> 👈<br>下一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/4006.html">RSA加解密专题 - Coppersmith 相关攻击二</a> 👈<br>回到开始：<a href="http://lr2006-robot.github.io/myblog.github.io/about/">关于我</a> 👈</p><p>相关链接：<br><a href="http://lr2006-robot.github.io/myblog.github.io/post/3002.html">密码学算法 - 中国剩余定理CRT</a> 👈</p>]]>
    </content>
    <id>https://lr2006-robot.github.io/myblog.github.io/post/4005.html</id>
    <link href="https://lr2006-robot.github.io/myblog.github.io/post/4005.html"/>
    <published>2026-03-04T11:33:00.000Z</published>
    <summary>
      <![CDATA[<p>Coppersmith 攻击是一种针对 RSA 加密算法的攻击方法，主要用于在特定条件下恢复 RSA 私钥或解密 RSA 密文。该攻击方法由 Don Coppersmith 在 1996 年提出，利用了 RSA 加密算法中的数学结构和性质。</p>
<p>这里我们并不会深入]]>
    </summary>
    <title>RSA加解密专题 - Coppersmith 相关攻击一</title>
    <updated>2026-03-15T12:12:29.535Z</updated>
  </entry>
  <entry>
    <author>
      <name>Dorange</name>
    </author>
    <category term="RSA加解密专题" scheme="https://lr2006-robot.github.io/myblog.github.io/categories/RSA%E5%8A%A0%E8%A7%A3%E5%AF%86%E4%B8%93%E9%A2%98/"/>
    <category term="密码学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="前言" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%89%8D%E8%A8%80/"/>
    <category term="RSA" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/RSA/"/>
    <content>
      <![CDATA[<p>pem 格式的文件通常用于数字证书认证机构(Certificate Authorities，CA)，其文件形式主要为 base64 编码的文件，头尾有类似于——-BEGIN PUBLIC KEY——-和——-END PUBLIC KEY——-的头尾标记</p><p>本文的目的就是如何从 .pem 格式的证书中，获取公钥、私钥信息。在 RSA 加密中，实际上公钥和私钥都是由一系列数字组成的，这些数字在.pem格式的证书中以特定的格式存储。通过解析这些数字，我们可以获取到公钥和私钥的相关信息，从而进行加密和解密操作。</p><h2><span id="pem文件生成与使用">PEM文件生成与使用 🥳</span></h2><p>在python中，可以通过 <code>from Crypto.PublicKey import RSA</code> 生成想要的公私钥文件。</p><p>生成公钥文件的代码如下：</p><pre><code class="lang-python">from Crypto.PublicKey import RSAfrom Crypto.Util.number import *p,q = getPrime(512),getPrime(512)n = p * qe = 0x10001pub = RSA.construct((n,e))with open(&#39;out.pem&#39;,&#39;wb&#39;) as f:    f.write(pub.exportKey(&#39;PEM&#39;))with open(&#39;out.pem&#39;,&#39;rb&#39;) as f:    print(f.read().decode())&#39;&#39;&#39;-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCuRPouMRTcLWPBEUlhjCrZ6MNQrSy29BrHjH4+lGMykB23azPtT9fk7IsEFXoodm6tsPL8kheJ6cP+0WPldlQOw/K3c9LUGzeCCAhNJuBjUoeW32ruE2HS7RoIF6vkP36zLs167ZZMK7Fg0cqW6VNXoJHTzaKdqysBe+3W1VHl6QIDAQAB-----END PUBLIC KEY-----&#39;&#39;&#39;</code></pre><p>生成私钥文件的代码如下：</p><pre><code class="lang-python">from Crypto.PublicKey import RSAfrom Crypto.Util.number import *p,q = getPrime(512),getPrime(512)n = p * qe = 0x10001d = inverse(e,(p - 1) * (q - 1))pub = RSA.construct((n,e,d,p,q))with open(&#39;out.pem&#39;,&#39;wb&#39;) as f:    f.write(pub.exportKey(&#39;PEM&#39;))with open(&#39;out.pem&#39;,&#39;rb&#39;) as f:    print(f.read().decode())&#39;&#39;&#39;-----BEGIN RSA PRIVATE KEY-----MIICXAIBAAKBgQCnHIvLP0IERPVRaED+71dlCRBcm3be4jlHgqVqIIXyIvrzc8ZCIIbIDqlBybNgq32i6PVlzBCsWiiTfBYS6J24qCjYVTywKk+yieNshieNNohmvQRFbZOZITJiP99URkhtGWo3trQfoAZEQ7NfMoS1N3PDvPet1lMfFK81AyWt1wIDAQABAoGAIq74DK0KZJxzVfwPUVoXh27EKJRTrZrCTKc+8bHiWwkLkK+8vEjH8Imqc28LfcrZ/o/fLsuVwk/MECA27KG+6hiRPJDWZmFgCInCABuhd+xitSBciMSGrO5ITjoqYdgMsR5xJTI8vhXIJ1iCkkCz6fD6Di7s+3n/+Ti3iuK87jECQQDOY/pLyH0ppOk049pieQRshckQMXqsKhahEDZmWMu70JqDLF0U3aIfup1R87uogB8hJj8+K5RQavG44l0W5ghTAkEAz0eTNEWTe2iJ/Dq+8JKTN/MA/a8MiG7wvAkXqjx+B+Eow77IdoN8D5ehD0x6ou73yaorTddidDAplNmvyq0D7QJAJbcPXhndBWclVozss2H59Prdqx/fkuZ+DCCyUDGZyVBta9sHh3CY18N6TCeF+1yuU5hxpiLAj5F7apWy/SQ8EQJBAMtvAxGda6cGLc8o1PeF1AlobUONxy4sPAdAoUJKRqNzH7AmEdcHKv6eoctDE2XQRc9ePUwTpSRFlLnrgLXZYu0CQBKKT+oY4ssnwKDQqGPsh9MtPyilySL9sWJilk1cSXmQXm7gjDq5S/x4k1gJyQFXbUnxTfsbLWs6BljHHlKSRes=-----END RSA PRIVATE KEY-----&#39;&#39;&#39;</code></pre><p>如果是生成一组随机的公私钥文件，可以直接直接使用 RSA.generate(bits)，其中的 bits 参数为生成的公钥文件中n的二进制位数，且 bits&gt;=1024，默认为 1024。</p><pre><code class="lang-python">from Crypto.PublicKey import RSAfrom Crypto.Util.number import *key = RSA.generate(1024)print(key.publickey)with open(&#39;out.pem&#39;,&#39;wb&#39;) as f:    f.write(key.exportKey(&#39;PEM&#39;))with open(&#39;out.pem&#39;,&#39;rb&#39;) as f:    print(f.read().decode())&#39;&#39;&#39;&lt;bound method RsaKey.public_key of RsaKey(n=99929675131146107818047790971049703803161677332600984800831117021637597471437271599719384035180982326470249457001083005224626356572009901255841391966616564984242588913592789383087326511196585443834216472384770224810255153922375944881215835774119177009570247468911673932973239275741131654695253944455708641727, e=65537, d=27501740780061900509028777360107084282858928440567277451824014559115713670042546183199393779857817534085793587304881725189343016621314014739780326627796196251470626677550096120689732366473717193895036548926100990548453802714993577259273117223098888966957225092204230317866243049867167880710514827901086931493, p=9959145719455053414348729655771312861460726396890001110536788452571591154277537325754067618769136991544158484849077735057792084414015625760536769751415507, q=10033960536990122517580769502852456118641921945920255759007869111269242067226468943041901092934902353013133963062909073461567977701122665256982872188799461, u=2111771661302217183343493746406805348965531581492223651147849337466575464897027597797323383307376291792334461891498947953036261381547003770335520720819326)&gt;-----BEGIN RSA PRIVATE KEY-----MIICXQIBAAKBgQCOTfkFWAc5UsCT07Y4bU9jKfpT5pt+ScHFIk/39jsE8yDHSFAopcQMlJ6GKRYfU7F/3xbY2udbvXBj9jJVLBmh3ORbZT06xwYDeqhvE2kJB+QfGk+JPOqzjCkFiDorxMUzWEq4us8nXmkv6WsrJMGSQZ0SQ7c29N9M0/8K622JvwIDAQABAoGAJyntaVuXLV8JcgW3piLrUNLKOpICZEi/Q9ZUJN2G069n64CK0wz//ihe0nR3SqrZdGQ84PSp7LUfu9sTch5fdSQLH/OAQtVf/rwWuwulm8wS0njrU9xxQQWrwZoeL9W7DlBWf5+PbmYdgyoLIwL3+wskvxiWswlvVcSR7RkXGiUCQQC+J0pN+1W2U3qea8rhAwX7XMSXoblFuqfBfH/duLZvseEEjtuTcj0ISlJgts2aZT1J8yHnllUoiqAuWQPUK/rTAkEAv5T6OwPHj+4J/4WzQgJTN89Xj9ihR45wpr8Rr6WPXUCgXTe82UFzny68nIzALvKrLTf58yu8/E5wy2+SejBJ5QJASNUnwsK3y8QhvTgwVwsfaW3Y5vNM0YZy5stW9offaNzLAUHunIUvF1PQRbb+/Vo1pXN40wljyMmAHQB/VO8bfQJBAIGQnVKAEdyzHavjngHMVL9vyEYOObSNDn6Wxb1GeJiWdl3UrjE35JwJHaG6Rtb5Yu7n5nCgaeUwn3PV9vgP5EkCQQCWIiD5b8Qb5Mma8/iDNhchmRkosUug/iCBeqNh3nJctxZnUy1fjc0toXxJY3ZBzTl3TNsk8p8x4H9y/NXoDtvu-----END RSA PRIVATE KEY-----&#39;&#39;&#39;</code></pre><p>在 python 中，利用公私钥文件，通过 PKCS1_OAEP 算法填充可以实现加解密，例如：</p><pre><code class="lang-python">from Crypto.PublicKey import RSAfrom Crypto.Cipher import PKCS1_OAEPfrom Crypto.Util.number import *flag = b&#39;This_is_a_message_qwer&#39;​key = RSA.generate(1024)pub = key.publickey().exportKey()public_key = RSA.importKey(pub)pk = PKCS1_OAEP.new(public_key)enc = pk.encrypt(flag)print(enc)​priv = key.exportKey()priv_key = RSA.importKey(priv)sk = PKCS1_OAEP.new(priv_key)msg = sk.decrypt(enc)print(msg)# b&#39;\xd1/Ou\xae\xba]z\xc0\t\xc6\xd7\xc4f\x13\x96\x9a\xeb5\xdb8\x92\xc7\x19\x12y\x9c\x18\xf7A\x9d\xe9\n&amp;=&lt;\x16\x07\xefz\xad2-\x983\xec\x932\xcb\xf0\x87~\xdf\xc1\xd2\x9f\xd7@\\H\x1f\x87#\xf3\x84\xa0\xfc\xd9\xcfV$&gt;\xd7Of\xe6G\x06\xcb\x91\xa1\xcc\x0c\xad\xc2\x9a\xad\xe46\x91x\xad\xa51\xbb\xfb\xc1E\x93~e%\xd1~\xf8l\x19n\x88\xff\xac^\xca\x8fs*}\xb9c0\xc0N\xf2\xfa\xa4\xd8\x18g&#39;# b&#39;This_is_a_message_qwer&#39;</code></pre><h2><span id="pem文件解析">PEM文件解析 🤠</span></h2><p>在文件进行 base64 解码后，可以看到 pem 文件的结构（专门用于 RSA 私钥）如下：</p><pre><code class="lang-text">RSAPrivateKey ::= SEQUENCE {    version           Version,     0表示两个素数的RSA  1表示多素数的RSA    modulus           INTEGER,  -- n    publicExponent    INTEGER,  -- e    privateExponent   INTEGER,  -- d    prime1            INTEGER,  -- p    prime2            INTEGER,  -- q    exponent1         INTEGER,  -- d mod (p-1) dp    exponent2         INTEGER,  -- d mod (q-1) dq    coefficient       INTEGER,  -- (inverse of q) mod p    otherPrimeInfos   OtherPrimeInfos OPTIONAL}十六进制格式如下：表示头 30表示长度 82 xx xx版本信息 02 01 00n 02 xx xxe 02 xxd 02 xx xxp 02 xxq 02 xxdp 02 xxdq 02 xxinv 02 xx如果数据长度超过 128 字节，则长度部分会多出两个字节表示实际长度</code></pre><p>对于完整的 pem 格式文件，我们可以使用 openssl 工具进行解析，例如：</p><pre><code class="lang-bash">读公钥 openssl rsa -pubin -text -modulus -in 1.pem读私钥 openssl rsa -in 1.pem -text</code></pre><p>当然，对于残缺的 pem 格式文件，我们也可以 base64 解码后，通过分析上述结构，手动解析出其中的数字信息，这也是我们重点讲解的内容。</p><h2><span id="残缺私钥解析">残缺私钥解析 👻</span></h2><p>我们从上面的结构知道，我们要的数据都是以 02 开头的整数类型，后面跟着长度和数据内容。对于残缺的 pem 格式文件，我们可以通过分析这些整数类型的数据，来获取我们需要的信息。</p><h3><span id="残缺私钥解析例题">残缺私钥解析例题</span></h3><p><a href="https://files.catbox.moe/8052x0.zip">残缺私钥解析例题下载</a></p><pre><code class="lang-text">Ciphertext = 0x6d482cbbc60b146ce064754910f5ce845a6eedeff2a20589b97c342677219c324debe4fc7324106bbeccc633ec819383f019856e700f027a39987500873a5700491f2828df4d5c01d179febac4f7589ed6846701e4d78daea6265df0fe1853e43984a3760b5257144842f9f4d316c3d314dfedb2ded08b90b957bf040258400a-----BEGIN RSA PRIVATE KEY-----MIICewIBAAKBgQCdA0+pqynKvc3/BH5ojnUABMdWy9Lzi9TwSAOgiJ6ky//QBrWGCNAn98CYNcoKuA2yOtHKIjAUrPh+LdgjmW+/pAWPJyrnoQw5SqD+FvqNTjG7AkvA+TUAyMflL9qodrWBEbl/AmN01Qbivo36+U1mFDB+6LENk/3BwWAHVj0DvQIhAL7tVc3/3jEFe+pAWKNoTV++2B8D1T+ii7ZYsy3kU1yNAoGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkCQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJAJrKFhI5pl/oBN2BZqLTf+NAcGqTFrzmbi7RVFAN43kAYAu11urAyLTncfJABpRhtGFdsL31jiswhiYRp9yjT+wJBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA-----END RSA PRIVATE KEY-----</code></pre><p>在这个例题中，我们可以看到，私钥文件中的一些数据残缺了，不过我们可以通过解码后的 02 头直接解析，因为数据的顺序都是严格按照上面的结构来的。</p><p>解析代码示例：</p><pre><code class="lang-python">from base64 import b64decodeimport binasciis = &quot;&quot;&quot;-----BEGIN RSA PRIVATE KEY-----MIICewIBAAKBgQCdA0+pqynKvc3/BH5ojnUABMdWy9Lzi9TwSAOgiJ6ky//QBrWGCNAn98CYNcoKuA2yOtHKIjAUrPh+LdgjmW+/pAWPJyrnoQw5SqD+FvqNTjG7AkvA+TUAyMflL9qodrWBEbl/AmN01Qbivo36+U1mFDB+6LENk/3BwWAHVj0DvQIhAL7tVc3/3jEFe+pAWKNoTV++2B8D1T+ii7ZYsy3kU1yNAoGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkCQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJAJrKFhI5pl/oBN2BZqLTf+NAcGqTFrzmbi7RVFAN43kAYAu11urAyLTncfJABpRhtGFdsL31jiswhiYRp9yjT+wJBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA-----END RSA PRIVATE KEY-----&quot;&quot;&quot;s = b64decode(s)print(binascii.hexlify(s))&quot;&quot;&quot;手动分行解析，注意02后面的长度部分，这是我们确定数据长度的方式，如果长度超过128字节，则会多出两个字节表示实际长度3082027b020100028181 #n009d034fa9ab29cabdcdff047e688e751704c756cbd2f38bd4f049a3a0889ea4cbffd006b58608d6a7f7c09835ca0abb1db23ad1ca223c54acf87e2dd823996fbfa5a58f272ae7a10c394aa0fe16fa8d4e31bb024bf1f93517c8c7e52fdaa876b58111b97fc66374d506e2be8dfaf94d6614307ee8b10d93fdc1c165c7563d03bd0221   #e00beed55cdffde31057bea5a58a3684d5fbed81f03d53fa28bb658b32de4535c8d028180 #d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090241   #p00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000241   #q00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030240   #dp26b285848e6997fa01376059a8b4dff8d69c1aa4c5af399b8bb45515a378de45d85eed75bab5f22d39dc7c95c1a5186d18576c2f7d638acc21898469f728d3fb0241   #dq00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030241   #inv0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000&quot;&quot;&quot;</code></pre><p>通过上述代码，我们可以成功解析出私钥中的各个参数，我们发现就是一个 $d_p$ 泄露的情况，利用这个参数，我们可以通过以下代码计算出 $p$ 和 $q$ 的值：</p><pre><code class="lang-python">from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse, GCDfrom secret import mn_hex = &quot;0x9d034fa9ab29cabdcdff047e688e751704c756cbd2f38bd4f049a3a0889ea4cbffd006b58608d6a7f7c09835ca0abb1db23ad1ca223c54acf87e2dd823996fbfa5a58f272ae7a10c394aa0fe16fa8d4e31bb024bf1f93517c8c7e52fdaa876b58111b97fc66374d506e2be8dfaf94d6614307ee8b10d93fdc1c165c7563d03bd&quot;n = int(n_hex,16)e_hex = &quot;0xbeed55cdffde31057bea5a58a3684d5fbed81f03d53fa28bb658b32de4535c8d&quot;e = int(e_hex,16)c_hex = &quot;0x6d482cbbc60b146ce064754910f5ce845a6eedeff2a20589b97c342677219c324debe4fc7324106bbeccc633ec819383f019856e700f027a39987500873a5700491f2828df4d5c01d179febac4f7589ed6846701e4d78daea6265df0fe1853e43984a3760b5257144842f9f4d316c3d314dfedb2ded08b90b957bf040258400a&quot;c = int(c_hex,16)dp_hex = &quot;0x26b285848e6997fa01376059a8b4dff8d69c1aa4c5af399b8bb45515a378de45d85eed75bab5f22d39dc7c95c1a5186d18576c2f7d638acc21898469f728d3fb&quot;dp = int(dp_hex,16)p = GCD((pow(2,e*dp,n) -2) ,n)q = n // pif n == p * q and p &gt; 1 and q &gt; 1:    print(&quot;成功分解n:&quot;)    print(&quot;p =&quot;, p)    print(&quot;q =&quot;, q)    phi = (p - 1) * (q - 1)    d = inverse(e, phi)    m = pow(c, d, n)    print(long_to_bytes(m))# b&#39;flag{u_know_the_pem_structure}&#39;</code></pre><p>上一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/4003.html">RSA加解密专题 - 私钥 d 相关攻击</a> 👈<br>下一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/4005.html">RSA加解密专题 - Coppersmith 相关攻击一</a> 👈<br>回到开始：<a href="http://lr2006-robot.github.io/myblog.github.io/about/">关于我</a> 👈</p>]]>
    </content>
    <id>https://lr2006-robot.github.io/myblog.github.io/post/4004.html</id>
    <link href="https://lr2006-robot.github.io/myblog.github.io/post/4004.html"/>
    <published>2026-03-03T11:33:00.000Z</published>
    <summary>
      <![CDATA[<p>pem 格式的文件通常用于数字证书认证机构(Certificate Authorities，CA)，其文件形式主要为 base64 编码的文件，头尾有类似于——-BEGIN PUBLIC KEY——-和——-END PUBLIC KEY——-的头尾标记</p>
<p>本文的]]>
    </summary>
    <title>RSA加解密专题 - 公私钥格式相关攻击</title>
    <updated>2026-03-14T12:27:09.189Z</updated>
  </entry>
  <entry>
    <author>
      <name>Dorange</name>
    </author>
    <category term="密码学算法" scheme="https://lr2006-robot.github.io/myblog.github.io/categories/%E5%AF%86%E7%A0%81%E5%AD%A6%E7%AE%97%E6%B3%95/"/>
    <category term="密码学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="数学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E6%95%B0%E5%AD%A6/"/>
    <category term="算法" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<p>当你在处理离散对数问题时，发现暴力破解的方法效率太低了😩，这时候 BSGS（Baby-Step Giant-Step）算法 就像一把神奇的钥匙🔑，能帮你快速找到离散对数的解。这个算法也别戏称为“北上广深”算法。🤐</p><p>欢迎来到《密码学核心算法实战》的 BSGS 专题！这里没有纸上谈兵的理论空谈（真的不画大饼😉），只有一把把能直接撬动数据安全的精密齿轮⚙️。</p><h2><span id="bsgs算法">BSGS算法 🌟</span></h2><p>BSGS（Baby-Step Giant-Step）算法是一种用于求解离散对数问题的算法。给定质数 $p$，整数 $a$ 和 $b$，我们想要找到一个整数 $x$，使得 $a^x \equiv b \pmod{p}$（其中 $a$ 与 $p$ 互质）。</p><h2><span id="bsgs算法的原理">BSGS算法的原理 😵</span></h2><p>根据群中生成元的性质，我们可以知道元素 $a$ 的阶（即 $a$ 的幂等于 1 的最小正整数）是 $p-1$，然后产生一种循环的效果。因此，离散对数问题的解 $x$ 必须满足 $0 \leq x \leq p-1$。</p><p>我们考虑先求出一部分 $a$ 的幂次模 $p$ 意义下的值，将它们存起来，然后使得剩下没有求值的部分能够想个办法利用已求值直接查出来。这就让我们联想到根号。</p><p>将 $x$ 表示为 $x = im - j$，其中 $m = \lceil \sqrt{p} \rceil$，$i$ 和 $j$ 是非负整数。这样我们可以将离散对数问题转化为以下形式：</p><script type="math/tex; mode=display">a^{i \cdot m - j} \equiv b \pmod{p} \Rightarrow (a^i)^m \equiv b \cdot a^j \pmod{p}</script><p>小步（Baby Step）：与计算所有 $b \cdot a^j \pmod{p}$ $(j \in [0,m))$，存入哈希表（键为值，值为 $j$）。每次都是乘 $a$，所以叫做小步。</p><p>大步（Giant Step）：计算 $a^m \pmod{p}$，遍历 $i \in [1,m]$，计算 $(a^m)^i \pmod{p}$，检查是否在哈希表中存在对应的值，如果存在，则可以得到 $x = im - j$。每次都是乘 $a^m$，所及叫做大步。</p><p>$j$ 是小步的步数，范围为 $0 \leq j &lt; m$，因为 $j$ 是拆分后的余项，必须小于块大小 $m$ （类似除法中的余数）。当然，如果 $j \geq m$，可以通过 $i$ 来调整，本质是上是多余的。</p><p>$i$ 是大步的步数，范围为 $1 \leq i \leq m$，我们分两步证明：</p><p>先证明：所有 $x \in [0, p-1]$ 都可以表示为 $x = im - j$，其中 $i \in [1, m]$，$j \in [0, m)$。</p><p>由于 $m = \lceil \sqrt{p} \rceil$，我们有 $m^2 \geq p$。对于任意 $x \in [0, p-1]$，我们可以将其除以 $m$ 得到商 $i$ 和余数 $j$，即 $x = im + j$。由于 $x &lt; p \leq m^2$，我们有 $i &lt; m$ 和 $j &lt; m$。因此，所有的 $x$ 都可以表示为 $x = im - j$。</p><p>再证明： $i$ 不用超过 $m$。</p><p>若 $i &gt; m$，则 $x = im - j &gt; m^2 - j \geq m^2 - (m-1) = m^2 - m + 1$。由于 $m^2 \geq p$，我们有 $x &gt; p - m + 1$。因此，如果 $i &gt; m$，则 $x$ 将超过 $p-1$，这与 $x \in [0, p-1]$ 矛盾。因此，$i$ 不用超过 $m$。</p><h2><span id="bsgs算法的实现">BSGS算法的实现 🙃</span></h2><p>下面是 BSGS 算法的一个简单实现：</p><pre><code class="lang-python">import mathdef bsgs(a, b, mod):    # 前提断言：a 与 mod 必须互质，否则直接报错    assert math.gcd(a, mod) == 1, &quot;a 与 mod 不互质，基础 BSGS 无法求解&quot;    a = a % mod  # 先取模，防止底数过大    b = b % mod  # 先取模，防止结果过大    # 特殊情况：b ≡ 1 mod mod，直接返回 x=0    if b == 1:        return 0    k = math.ceil(math.sqrt(mod))    step_dict = dict()    # 小步 (Baby Step)     # 计算: b * a^q mod mod，q ∈ [0, k)    right_val = b  # 初始值: b * a^0    for q in range(k):        # 只存第一次出现的q（保证q最小，从而解x最小）        if right_val not in step_dict:            step_dict[right_val] = q        right_val = right_val * a % mod    # 大步 (Giant Step)     # 计算: (a^k)^p mod mod，p ∈ [1, k]    ak = pow(a, k, mod)    left_val = 1  # 初始值: (a^k)^0    for p in range(1, k + 1):        # 用递推代替重复幂运算        left_val = left_val * ak % mod        if left_val in step_dict:            q = step_dict[left_val]            x = k * p - q            # 验证解的有效性（防止哈希碰撞或理论误差）            if pow(a, x, mod) == b:                return x    return None  # 遍历结束未找到，无解</code></pre><h2><span id="扩展bsgs算法">扩展BSGS算法 🥰</span></h2><p>当 $a$ 和 $mod$ 不互质时，基础的 BSGS 算法无法直接求解。这时候我们可以使用扩展 BSGS 算法来处理这种情况。</p><p>我们把题目所给的这个式子搬出来：</p><script type="math/tex; mode=display">a^x \equiv b \pmod{p}</script><p>我们不妨设 $gcd(a, p) = d$，如果 $d$ 不整除 $b$，则方程无解；如果 $d$ 整除 $b$，我们可以将方程两边同时除以 $d$，得到：</p><script type="math/tex; mode=display">\left(\frac{a}{d}\right) \cdot a^{x-1} \equiv \frac{b}{d} \pmod{\frac{p}{d}}</script><p>但是即使这样，我们也不能直接使用 BSGS 算法来求解，因为 $\frac{a}{d}$ 和 $\frac{p}{d}$ 可能仍然不互质。我们需要继续进行约简，直到 $\frac{a}{d}$ 和 $\frac{p}{d}$ 互质为止，即：</p><script type="math/tex; mode=display">\frac{a}{\prod_k g_i} \cdot a^{x-k} \equiv \frac{b}{\prod_k g_i} \pmod{\frac{p}{\prod_k g_i}}</script><p>这个时候，我们再处理一下：</p><script type="math/tex; mode=display">a^{x-k} \equiv \frac{b}{\prod_k g_i} \cdot \left(\frac{a}{\prod_k g_i}\right)^{-1} \pmod{\frac{p}{\prod_k g_i}}</script><p>这样我们就得到了一个新的离散对数问题，其中底数和模数已经互质了，我们就可以使用 BSGS 算法来求解了。</p><h2><span id="扩展bsgs算法的实现">扩展BSGS算法的实现 🤪</span></h2><p>下面是这个算法的python简单实现：</p><pre><code class="lang-python">def extended_bsgs(a, b, mod):    # 保存原始值（关键！用于小解验证）    orig_a = a    orig_b = b    orig_mod = mod    a = a % mod    b = b % mod    # 特殊情况：b ≡ 1 mod mod，x=0    if b == 1:        return 0    # 特殊情况：mod=1，所有 x 都满足，返回最小非负解 0    if mod == 1:        return 0    k = 0    C = 1  # 记录左边累积的系数    while True:        d = math.gcd(a, mod)        if d == 1:            break  # 已经互质，停止提取        # 如果 b 不是 d 的倍数，无解        if b % d != 0:            return None        # 提取公因数（注意：a 不除 d！之前的 a//=d 是错误的）        # 正确逻辑：a 保持原值，只除 mod 和 b        b = b // d        mod = mod // d        k += 1        # 累积系数：C = C * (a//d) mod mod         C = (C * (orig_a // d)) % mod    # 现在 a（orig_a）与 mod 互质，转化方程：C * orig_a^(x-k) ≡ b (mod mod)    # 求 C 的逆元    try:        inv_C = pow(C, -1, mod)    except ValueError:        return None  # 逆元不存在，理论上不会发生    B = (b * inv_C) % mod    # 调用 BSGS 时用原始 a，不是提取后的 a    y = bsgs(orig_a, B, mod)    if y is None:        return None    x = y + k    # 验证小解：x &lt; k 的情况（用原始值验证）    for i in range(k):        if pow(orig_a, i, orig_mod) == orig_b:            return i    # 验证最终解是否满足原方程    if pow(orig_a, x, orig_mod) == orig_b:        return x    return None</code></pre><h2><span id="sagemath-偷懒">SageMath 偷懒 🤓👆</span></h2><p>有同学说：“博主，博主，你的算法确实很厉害，但是还是太吃操作了，有没有更加简单无脑的用法？”👻<br>有的，兄弟有的，这样的算法在 SageMath 中早就已经被封装好了🤫，我们直接调用就行了：</p><pre><code class="lang-python">from sage.all import discrete_log# discrete_log中内置了BSGS算法，当然更加完善r = discrete_log(b, a, mod)# 但是需要注意：如果 a 和 mod 不互质，discrete_log 可能会抛出异常，这时候我们需要先处理一下，或者直接使用 extended_bsgs 来求解。</code></pre><p>怎么样，是不是非常简单？🤣👉🤡</p><p>上一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/3007.html">密码学算法 - Miller-Rabin素数检验</a> 👈<br>下一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/3009.html">密码学算法 - BSGS算法二</a> 👈<br>回到开始：<a href="http://lr2006-robot.github.io/myblog.github.io/about/">关于我</a> 👈</p><p>相关链接：<br><a href="https://lr2006-robot.github.io/myblog.github.io/post/1007.html">密码学数学基础 - 群论基础</a> 👈</p>]]>
    </content>
    <id>https://lr2006-robot.github.io/myblog.github.io/post/3008.html</id>
    <link href="https://lr2006-robot.github.io/myblog.github.io/post/3008.html"/>
    <published>2026-03-02T15:58:00.000Z</published>
    <summary>
      <![CDATA[<p>当你在处理离散对数问题时，发现暴力破解的方法效率太低了😩，这时候 BSGS（Baby-Step Giant-Step）算法 就像一把神奇的钥匙🔑，能帮你快速找到离散对数的解。这个算法也别戏称为“北上广深”算法。🤐</p>
<p>欢迎来到《密码学核心算法实战》的 BSG]]>
    </summary>
    <title>密码学算法 - BSGS 算法一</title>
    <updated>2026-03-12T08:37:46.141Z</updated>
  </entry>
  <entry>
    <author>
      <name>Dorange</name>
    </author>
    <category term="RSA加解密专题" scheme="https://lr2006-robot.github.io/myblog.github.io/categories/RSA%E5%8A%A0%E8%A7%A3%E5%AF%86%E4%B8%93%E9%A2%98/"/>
    <category term="密码学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="前言" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%89%8D%E8%A8%80/"/>
    <category term="RSA" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/RSA/"/>
    <content>
      <![CDATA[<p>在RSA加解密算法中，私钥是一个非常重要的参数，它与公钥指数和模数之间存在着密切的关系。如果私钥的选择不当，或者在某些特定情况下，攻击者可能会利用这些关系来破解RSA加密算法，从而获取到原始消息或者分解出模数。</p><p>本文将介绍一些与RSA私钥 $d$ 相关的攻击方法，包括常见的攻击手段如维纳攻击、$d_p$ 泄露攻击等。</p><h2><span id="d_p-泄露攻击">$d_p$ 泄露攻击 😸</span></h2><p>在RSA算法中，私钥 $d$ 可以通过模数 $n$ 和公钥指数 $e$ 来计算得到。然而，如果攻击者能够获取到私钥的某些部分信息，例如 $d_p$（即 $d \mod (p-1)$），就可能会利用这些信息来破解RSA加密算法。</p><p>我们可以使用小费马定理：<br>如果p是一个质数，整数a不是p的倍数，那么 $a^{p-1} \equiv 1 \mod p$</p><p>根据 $d_p$ 的定义，我们有 $d \equiv d_p \mod (p-1)$，因此存在一个整数 $k$ 使得 $d = d_p + k(p-1)$。</p><p>带入RSA的加密解密公式，我们可以得到：</p><script type="math/tex; mode=display">a^{e×dp} \equiv a \cdot (a^{p-1})^k</script><p>带入小费马定理得：</p><script type="math/tex; mode=display">a^{e×dp} \equiv a \cdot 1^k \equiv a \mod p</script><p>令a = 2，显然与p互质</p><script type="math/tex; mode=display">2^{e×dp} \equiv 2 \mod p</script><p>故找到 $2^{e×dp} - 2$ 和 $n$ 的最大公约数即可得到 $p$。</p><h3><span id="d_p-泄露攻击例题">$d_p$ 泄露攻击例题</span></h3><p><a href="https://files.catbox.moe/rzxfk9.zip">$d_p$ 泄露攻击例题下载</a></p><pre><code class="lang-python">from Crypto.Util.number import long_to_bytes, bytes_to_longfrom secret import mn = 110258232065996936040119382717057533219407439132082940253584584843182789072223349906230791856973287972123865907521188214279276036006315106449207819895384374593369624196851954653849023080919521562472625060120241327872006503587923172763038424169766164805827768917668909645409183750383690108690297577306064749501e = 86358776187347673493647406489815610508873901999008033525516761692023880375437dp = 2026744663215773394057368328828715466612074334163244052273782180970389150112352609040984669970258057823988294286780317419979188752707883050227364941845499c = pow(bytes_to_long(m.encode()), e, n)print(&quot;c =&quot;, c)#c = 57824787462538624115079284407485175696376125079926154812508611561859936644607308884917900487579845271493649664537572020799918933437291303394878105158410745800945703317112722575982308950734393152733789992168072260114287421251670816067142160465467661371596611685239421550264474850090840444467287904457322154326</code></pre><p>在这个例题中，攻击者可以通过获取到 $d_p$ 的值来计算出 $p$，从而进一步分解出模数 $n$，最终获取到原始消息 $m$。</p><p>解题代码示例：</p><pre><code class="lang-python">from Crypto.Util.number import long_to_bytes, inverse, GCDn = 110258232065996936040119382717057533219407439132082940253584584843182789072223349906230791856973287972123865907521188214279276036006315106449207819895384374593369624196851954653849023080919521562472625060120241327872006503587923172763038424169766164805827768917668909645409183750383690108690297577306064749501e = 86358776187347673493647406489815610508873901999008033525516761692023880375437dp = 2026744663215773394057368328828715466612074334163244052273782180970389150112352609040984669970258057823988294286780317419979188752707883050227364941845499c = 57824787462538624115079284407485175696376125079926154812508611561859936644607308884917900487579845271493649664537572020799918933437291303394878105158410745800945703317112722575982308950734393152733789992168072260114287421251670816067142160465467661371596611685239421550264474850090840444467287904457322154326p = GCD(pow(2, e*dp, n) - 2, n)q = n // pphi = (p-1) * (q-1)d = inverse(e, phi)m = pow(c, d, n)print(long_to_bytes(m))# b&#39;flag{dp_leak_is_very_dangerous}&#39;</code></pre><h2><span id="d-泄露攻击">$d$ 泄露攻击 😎</span></h2><p>如果攻击者同时知道了公钥指数 $e$ 和私钥指数 $d$，并且我们发现模数的大小与 $e \cdot d$ 之间差值符合爆破范围。</p><p>我们由密钥关系推导$e × d = 1 + k × \phi(n)$。<br>确定 $\phi(n)$ 的范围(用n近似)和 $e \cdot d$ 范围进行爆破 $k$。</p><script type="math/tex; mode=display">\frac{e \cdot d - 1}{\phi(n)} = k</script><p>当然，我们可以使用 $d$ 泄露攻击来分解模数，获取到 $p$ 和 $q$。这种情况一般会出现在系列题目中，分解模数代表完全掌握了RSA的加密解密过程，后续的题目可能会在此基础上进行一些变形或者增加一些限制条件来增加难度。</p><p>首先，已知RSA的公钥 $(e, n)$ 和私钥 $d$ 满足以下关系：</p><script type="math/tex; mode=display">e \cdot d \equiv 1 \mod \phi(n)</script><p>所以，存在整数 $k$，使得</p><script type="math/tex; mode=display">e \cdot d - 1 = k \cdot \phi(n)</script><p>又 $ \forall a \in Z_n^*$，满足 $a^{ed - 1} \equiv 1 \mod n$，令</p><script type="math/tex; mode=display">ed - 1 \equiv 2^{s}t</script><p>其中，$t$ 为奇数，可以证明至少一半的 $a$ 满足，存在 $i \in [1, s]$，使得</p><script type="math/tex; mode=display">a^{2^{i-1}t} \not \equiv \pm 1 \mod n, a^{2^{i}t} \equiv 1 \mod n</script><p>如果 $a,i$ 满足上述条件，则 $gcd(a^{2^{i-1}t} - 1, n)$ 为 $n$ 的非平凡因子。</p><p>代码演示：</p><pre><code class="lang-python">def factor(e, d, n):    k = e * d - 1    if k % 2 == 1:        return None    r = 0    t = k    while t % 2 == 0:        t //= 2        r += 1    for i in range(100):        g = random.randint(2, n - 1)        y = pow(g, t, n)        if y == 1 or y == n - 1:            continue        for j in range(r):            x = pow(y, 2, n)            if x == 1:                p = GCD(y - 1, n)                q = GCD(y + 1, n)                if p != n and q != n:                    return p, q            if x == n - 1:                break            y = x    return None</code></pre><h3><span id="d-泄露攻击例题">$d$ 泄露攻击例题</span></h3><p><a href="https://files.catbox.moe/resbn6.zip">$d$ 泄露攻击例题下载</a></p><pre><code class="lang-python">from Crypto.Util.number import *from secret import mdef nextPrime(n):    n += 2 if n &amp; 1 else 1    while not isPrime(n):        n += 2    return np = getPrime(1024)q = nextPrime(p)n = p*qe = 65537d = inverse(e, (p-1)*(q-1))c = pow(bytes_to_long(m.encode()), e, n)print(f&quot;c = {c}&quot;)print(f&quot;d = {d}&quot;)# c = 2351513466711699961275642938145920567717064187415670287856662165691734583511498511849556884324985836944759340430602518816433675154658136461956198063979927374588752761285276022407348897862403266636524918180127033165885832562693771076754684722731688431429937047605808304439473588318383001177577056016593797108647431975635650094036889708447244444614378357243277541322659755051936643582756858815668485878335755801145194320825814381728758757706480457190526292370291648284303227561156220999669622281957677370592060075572209068255726871622636790395044724399719011386821145933178352805650027741623500937510313518945469155302# d = 19148105720056666554199884501635824534269326983585168429616104055501403262584188036915335459586648180711891951597484047750673362212665198365799020084440783235574443041732573269843811986291526627281503946572630916067824489440197669541850813141337499242644990995736824092775241919506559628875472249640582214556197841501452934104799319015596870245520721236032106961363621543195345434346863657436265360961414326566644881685137687179094740959374192583443543425968907126818396159939075323461512947503693730501859850872858036346011436175151526444655727243816528687577922841002832868639902639633674684653878564522063533978273</code></pre><p>在这个例题中，攻击者可以通过已知的 $e$ 和 $d$ 来计算出 $\phi(n)$，从而获取到 $p$ 和 $q$，最终获取到原始消息 $m$。<br>重点在于通过bit位成功确定爆破范围，成功爆破出 $k$。</p><p>解题代码示例：</p><pre><code class="lang-python">from Crypto.Util.number import *import sympyimport gmpy2 as gpe = 65537c = 2351513466711699961275642938145920567717064187415670287856662165691734583511498511849556884324985836944759340430602518816433675154658136461956198063979927374588752761285276022407348897862403266636524918180127033165885832562693771076754684722731688431429937047605808304439473588318383001177577056016593797108647431975635650094036889708447244444614378357243277541322659755051936643582756858815668485878335755801145194320825814381728758757706480457190526292370291648284303227561156220999669622281957677370592060075572209068255726871622636790395044724399719011386821145933178352805650027741623500937510313518945469155302d = 19148105720056666554199884501635824534269326983585168429616104055501403262584188036915335459586648180711891951597484047750673362212665198365799020084440783235574443041732573269843811986291526627281503946572630916067824489440197669541850813141337499242644990995736824092775241919506559628875472249640582214556197841501452934104799319015596870245520721236032106961363621543195345434346863657436265360961414326566644881685137687179094740959374192583443543425968907126818396159939075323461512947503693730501859850872858036346011436175151526444655727243816528687577922841002832868639902639633674684653878564522063533978273ed = bin(e*d - 1) [2:]print(&quot;ed =&quot;, ed) # 位数2064# 已知n是2048位的数，e*d-1是2048位的数，所以k的位数为16for k in range(pow(2,15),pow(2,16)):    if (e*d - 1 ) % k == 0:        phi = (e*d - 1) // k        p = sympy.prevprime(gp.iroot(phi,2) [0])        q = sympy.nextprime(p)        if (p - 1) * (q - 1) == phi:            breakn = p * qm = pow(c, d, n)print(long_to_bytes(m))# b&#39;flag{u_can_catch_flag_without_n}&#39;</code></pre><h2><span id="wiener攻击">wiener攻击 👻</span></h2><p>当d比较小（$d&lt;\frac{1}{3}N^\frac{1}{4}$）时,可以使用 Wiener 算法来获取私钥 $d$。</p><p>通常出题人为了要使得生成的私钥 $d$ 比较小，通常会先生成一个比较小的 $d$，然后再去求 $e$，从而使得 $e$ 的取值范围位于（$1,\phi(n)$）之间，会导致 $e$ 很大，基本上可以和模数 $n$ 大小相似。</p><p>从两条关系出发</p><script type="math/tex; mode=display">\begin{align*}\phi(n) = (p-1) \cdot (q-1) &= n - (p + q) + 1 \\e \cdot d &= 1 + k \cdot \phi(n)\end{align*}</script><p>带入有</p><script type="math/tex; mode=display">e \cdot d = 1 + k \cdot [ n - (p + q) +1]</script><p>两边同时除以 $nd$，得到</p><script type="math/tex; mode=display">\frac{e}{n} = \frac{k}{d}(1 - \delta)</script><p>式子左边均已知，右边均未知，右边和左边非常接近。<br>这种情况可以使用连分数来将左边的比值展开，在连分数的展开式中，很大概率存在 $k$ 和 $d$，从而可以求出私钥 $d$，进行解密。</p><p>当然，这个算法也不一定是用来求解 $d$，实际上任何满足的近似值都可以用这个算法直接计算出我们要的解。</p><h3><span id="wiener攻击例题一">wiener攻击例题一</span></h3><p><a href="https://files.catbox.moe/bmsjro.zip">wiener攻击例题下载</a></p><pre><code class="lang-python">from Crypto.Util.number import *from secret import mn = 101991809777553253470276751399264740131157682329252673501792154507006158434432009141995367241962525705950046253400188884658262496534706438791515071885860897552736656899566915731297225817250639873643376310103992170646906557242832893914902053581087502512787303322747780420210884852166586717636559058152544979471e = 46731919563265721307105180410302518676676135509737992912625092976849075262192092549323082367518264378630543338219025744820916471913696072050291990620486581719410354385121760761374229374847695148230596005409978383369740305816082770283909611956355972181848077519920922059268376958811713365106925235218265173085c = pow(bytes_to_long(m.encode()), e, n)print(&quot;c =&quot;,c)# c = 80101582494604437223741383588717025600300047023426616620891241783981638998150472682835072801153995869502522450301302385220129781465036908562359475679205173120186042784572146009306633860957439559980202876647026507563827608212430617525606857990848945461179334863717971248735308788929007826292228060661672434321</code></pre><p>在这个例题中，肉眼可见模数 $n$ 和公钥指数 $e$ 大小相似，说明私钥 $d$ 比较小，那么我们就可以使用 Wiener 攻击来求解出私钥 $d$，从而获取到原始消息 $m$。</p><p>解题代码示例：</p><pre><code class="lang-python">from Crypto.Util.number import *from sage.all import *n = 101991809777553253470276751399264740131157682329252673501792154507006158434432009141995367241962525705950046253400188884658262496534706438791515071885860897552736656899566915731297225817250639873643376310103992170646906557242832893914902053581087502512787303322747780420210884852166586717636559058152544979471e = 46731919563265721307105180410302518676676135509737992912625092976849075262192092549323082367518264378630543338219025744820916471913696072050291990620486581719410354385121760761374229374847695148230596005409978383369740305816082770283909611956355972181848077519920922059268376958811713365106925235218265173085c = 80101582494604437223741383588717025600300047023426616620891241783981638998150472682835072801153995869502522450301302385220129781465036908562359475679205173120186042784572146009306633860957439559980202876647026507563827608212430617525606857990848945461179334863717971248735308788929007826292228060661672434321e_sage = Integer(e)n_sage = Integer(n)cf = continued_fraction(e_sage / n_sage).convergents()for k_frac in cf[1:]:    d = k_frac.denominator()    k = k_frac.numerator()    if k == 0 or d == 0:        continue    if (e * d - 1) % k != 0:        continue    m = long_to_bytes(pow(c, d, n))    print(m)# b&#39;flag{the_big_e_is_dangerous}&#39;</code></pre><h3><span id="wiener攻击例题二">wiener攻击例题二</span></h3><p><a href="https://files.catbox.moe/fzjjt2.zip">wiener攻击例题下载</a></p><pre><code class="lang-python">import sympyfrom Crypto.Util.number import *from secret import flagflag1 = flag[:19].encode()flag2 = flag[19:].encode()assert(len(flag) == 38)P1 = getPrime(1038)P2 = sympy.nextprime(P1)assert(P2 - P1 &lt; 1000)Q1 = getPrime(512)Q2 = sympy.nextprime(Q1)N1 = P1 * P1 * Q1N2 = P2 * P2 * Q2E1 = getPrime(1024)E2 = sympy.nextprime(E1)m1 = bytes_to_long(flag1)m2 = bytes_to_long(flag2)c1 = pow(m1, E1, N1)c2 = pow(m2, E2, N2)print(&quot;N1=&quot;,N1)print(&quot;E1=&quot;,E1)print(&quot;c1=&quot;,c1)print(&quot;N2=&quot;,N2)print(&quot;E2=&quot;,E2)print(&quot;c2=&quot;,c2)# N1=60143104944034567859993561862949071559877219267755259679749062284763163484947626697494729046430386559610613113754453726683312513915610558734802079868190554644983911078936369464590301246394586190666760362763580192139772729890492729488892169933099057105842090125200369295070365451134781912223048179092058016446222199742919885472867511334714233086339832790286482634562102936600597781342756061479024744312357407750731307860842457299116947352106025529309727703385914891200109853084742321655388368371397596144557614128458065859276522963419738435137978069417053712567764148183279165963454266011754149684758060746773409666706463583389316772088889398359242197165140562147489286818190852679930372669254697353483887004105934649944725189954685412228899457155711301864163839538810653626724347# E1=125932919717342481428108392434488550259190856475011752106073050593074410065655587870702051419898088541590032209854048032649625269856337901048406066968337289491951404384300466543616578679539808215698754491076340386697518948419895268049696498272031094236309803803729823608854215226233796069683774155739820423103# N2=60143104944034567859993561862949071559877219267755259679749062284763163484947626697494729046430386559610613113754453726683312513915610558734802079868195633647431732875392121458684331843306730889424418620069322578265236351407591029338519809538995249896905137642342435659572917714183543305243715664380787797562011006398730320980994747939791561885622949912698246701769321430325902912003041678774440704056597862093530981040696872522868921139041247362592257285423948870944137019745161211585845927019259709501237550818918272189606436413992759328318871765171844153527424347985462767028135376552302463861324408178183842139330244906606776359050482977256728910278687996106152971028878653123533559760167711270265171441623056873903669918694259043580017081671349232051870716493557434517579121# E2=125932919717342481428108392434488550259190856475011752106073050593074410065655587870702051419898088541590032209854048032649625269856337901048406066968337289491951404384300466543616578679539808215698754491076340386697518948419895268049696498272031094236309803803729823608854215226233796069683774155739820425393# c1 = 14073319586163781679481641179662582612304692176580961784953477838609276616064589846809270198729540715245691991740464261550360788284937834670050775858366101085348280669179408813113362405053335009206731776590481373734764383540364750799676070346518475581682516426563732231616057180451762625401173375609189648138854979940971607994443347332491396247789654295359301772332945336628799244150289351676680721435465563648104813778503434793931626482475134001659540217915771568233343281055830752661539150794979736067742502306279761313481724154724055466231123359431082147305556479579346284553457892479595918187550211451065607322614000006859723843438948537608221042231108152696040678683554497540495126798195723931414990643263282854475908809930686235855491686777629698984201000658669936961615186# c2 = 7741133573061898991718841569604686293847712651459642535825877765243067060193308133385557355381962779171678392611432953529802863894965082406081770549435804237257142410673170863585027671844837301406995739036818126803525925392123724367462856577481458451934142005470762416077608148210778025980305611228050923521365912250581454107545415887539097404367263553527441676600625832138948968108210407415663388184702852913387236486545458819686141479064069733023490788623703473254743320501358595518028968805977839380155887651172992548200623712353654087865742820046863844682793639042663279707824154464544589525016155769813415003482652443230965612123925078843715142264156868627471931778131372410481557419293475673728501696136293542695689649954773759684775991749503582720389352345218448368591510</code></pre><p>在这个例题中，我们发现 $P1$ 和 $P2$ 大小接近，$Q1$ 和 $Q2$ 大小接近，那么我们可以认为 $\frac{N1}{N2} = \frac{P1^2 \cdot Q1}{P2^2 \cdot Q2} \approx \frac{Q1}{Q2}$，从而可以通过连分数来求解出 $Q1$ 和 $Q2$，再解出 $P1$ 和 $P2$，最终获取到原始消息 $m$。</p><p>解题代码示例：</p><pre><code class="lang-python">from sage.all import *import gmpy2 as gpfrom Crypto.Util.number import *N1=60143104944034567859993561862949071559877219267755259679749062284763163484947626697494729046430386559610613113754453726683312513915610558734802079868190554644983911078936369464590301246394586190666760362763580192139772729890492729488892169933099057105842090125200369295070365451134781912223048179092058016446222199742919885472867511334714233086339832790286482634562102936600597781342756061479024744312357407750731307860842457299116947352106025529309727703385914891200109853084742321655388368371397596144557614128458065859276522963419738435137978069417053712567764148183279165963454266011754149684758060746773409666706463583389316772088889398359242197165140562147489286818190852679930372669254697353483887004105934649944725189954685412228899457155711301864163839538810653626724347E1=125932919717342481428108392434488550259190856475011752106073050593074410065655587870702051419898088541590032209854048032649625269856337901048406066968337289491951404384300466543616578679539808215698754491076340386697518948419895268049696498272031094236309803803729823608854215226233796069683774155739820423103N2=60143104944034567859993561862949071559877219267755259679749062284763163484947626697494729046430386559610613113754453726683312513915610558734802079868195633647431732875392121458684331843306730889424418620069322578265236351407591029338519809538995249896905137642342435659572917714183543305243715664380787797562011006398730320980994747939791561885622949912698246701769321430325902912003041678774440704056597862093530981040696872522868921139041247362592257285423948870944137019745161211585845927019259709501237550818918272189606436413992759328318871765171844153527424347985462767028135376552302463861324408178183842139330244906606776359050482977256728910278687996106152971028878653123533559760167711270265171441623056873903669918694259043580017081671349232051870716493557434517579121E2=125932919717342481428108392434488550259190856475011752106073050593074410065655587870702051419898088541590032209854048032649625269856337901048406066968337289491951404384300466543616578679539808215698754491076340386697518948419895268049696498272031094236309803803729823608854215226233796069683774155739820425393c1 = 14073319586163781679481641179662582612304692176580961784953477838609276616064589846809270198729540715245691991740464261550360788284937834670050775858366101085348280669179408813113362405053335009206731776590481373734764383540364750799676070346518475581682516426563732231616057180451762625401173375609189648138854979940971607994443347332491396247789654295359301772332945336628799244150289351676680721435465563648104813778503434793931626482475134001659540217915771568233343281055830752661539150794979736067742502306279761313481724154724055466231123359431082147305556479579346284553457892479595918187550211451065607322614000006859723843438948537608221042231108152696040678683554497540495126798195723931414990643263282854475908809930686235855491686777629698984201000658669936961615186c2 = 7741133573061898991718841569604686293847712651459642535825877765243067060193308133385557355381962779171678392611432953529802863894965082406081770549435804237257142410673170863585027671844837301406995739036818126803525925392123724367462856577481458451934142005470762416077608148210778025980305611228050923521365912250581454107545415887539097404367263553527441676600625832138948968108210407415663388184702852913387236486545458819686141479064069733023490788623703473254743320501358595518028968805977839380155887651172992548200623712353654087865742820046863844682793639042663279707824154464544589525016155769813415003482652443230965612123925078843715142264156868627471931778131372410481557419293475673728501696136293542695689649954773759684775991749503582720389352345218448368591510N1 = Integer(N1)N2 = Integer(N2)c1 = Integer(c1)c2 = Integer(c2)E1 = Integer(E1)E2 = Integer(E2)cf = continued_fraction(N1 / N2).convergents()for x_frac in cf[1:]:    Q1 = x_frac.numerator()    Q2 = x_frac.denominator()    if N1 % Q1 == 0 and N2 % Q2 == 0:        P1, ok1 = gp.iroot(N1 // Q1,2)        P2, ok2 = gp.iroot(N2 // Q2,2)        if ok1 and ok2:            if P1 * P1 * Q1 == N1 and P2 * P2 * Q2 == N2:                phi1 = (P1 - 1) * (P1) * (Q1 - 1)                phi2 = (P2 - 1) * (P2) * (Q2 - 1)                if (phi1 != 0):                    d1 = inverse(E1, phi1)                    m1 = pow(c1, d1, N1)                    flag1 = long_to_bytes(m1)                if (phi2 != 0):                    d2 = inverse(E2, phi2)                    m2 = pow(c2, d2, N2)                    flag2 = long_to_bytes(m2)                    print(flag1 + flag2)print(&quot;Done&quot;)</code></pre><h2><span id="共私钥攻击">共私钥攻击</span></h2><p>设同一个 RSA 加密系统中有 $r$ 个 RSA 加密共用同一个私钥 $d$，这些加密的公钥分别为 $(e_1,N_1), \cdots ,(e_r,N_r)$。设 $N_i, \cdots ,N_r$ 依次增大，并且由于是同一个系统中的加密，故认为 $N_1, \cdots ,N_r$ 具有近似相同的比特长度，于是有 $N_i &lt; N_2 &lt; \cdots &lt; N_r &lt; 2N_1$。</p><p>那我们可以得到一串式子：</p><script type="math/tex; mode=display">\begin{cases}e_1d=1+k_1\varphi(N_1) \newline e_2d=1+k_2\varphi(N_2) \newline {\vdots} \newline e_rd=1+k_r\varphi(N_r)\end{cases}</script><p>其中，$N_1 \lt N_2 \lt \cdots \lt N_r \lt 2N_1$。</p><p>构造格：</p><p>$\mathcal{B}<em>r = \begin{bmatrix}{M}&amp;{e_1}&amp;{e_2}&amp;{\cdots}&amp;{e</em>{r}}\newline<br>{0}&amp;{-N_1}&amp;{0}&amp;{\cdots}&amp;{0}\newline{0}&amp;{0}&amp;{-N_2}&amp;{\cdots}&amp;{0}\newline{\vdots}&amp;{\vdots}&amp;{\vdots}&amp;{\ddots}&amp;{\vdots}\newline{0}&amp;{0}&amp;{0}&amp;{\cdots}&amp;{-N_r}\newline\end{bmatrix}$</p><p>其中 $M=\lfloor N_r^{\frac{1}{2}} \rfloor$。</p><p>则有 $x_r \mathcal{B}_r = v_r$。注意到$\mathcal{B}_r$的行向量组生成了一个 $r+1$ 维的格，而 $v_r$ 是格中的一个向量。</p><p>当不等式 $|v_r| &lt; (\det(\mathcal{B}_r))^{1/(r+1)}$ 成立时，攻击者只要解出格 $\mathcal{L}$ 上的 $SVP$ 问题即可解出$v_r$，从而得到$v_r$ 第一个分量 $dM$。$M$ 已知，因此攻击者可求出$d$，从而攻破这些加密。</p><h3><span id="共私钥攻击例题">共私钥攻击例题</span></h3><p><a href="https://files.catbox.moe/qlu1x0.zip">共私钥攻击例题下载</a></p><pre><code class="lang-python">from Crypto.Util.number import *from random import randintfrom secret import mflag = bytes_to_long(m)d = getPrime(randint(370, 390))def enc(i):    print()    p = getPrime(512)    q = getPrime(512)    n = p * q    phi = (p - 1) * (q - 1)    e = inverse(d, phi)    c = pow(flag, e, n)    print(f&#39;e_{i} =&#39;, e)    print(f&#39;n_{i} =&#39;, n)    print(f&#39;c_{i} =&#39;, c)if __name__ == &#39;__main__&#39;:    for i in range(3):        enc(i)# e_0 = 65273226782938365337746916273730336693400864987226527768629555154328678878476135267049386675622260483462220524488000354329161815186124642838496578365092125560635683645652071790968480727817124418736448310995681377289580390945033014829116595493634248909648715641159852982292802459935880193569900887152429411847# n_0 = 68273324976926984651373562317651913900179693126653185371759797219277881642295726703952462364177190633417720186224603741096752660090376579503727572378024303201815180519953050892027877371422177476434135471869132077433509929481856863664414488106874978031117040155569240599125372440850335176938915089228631971431# c_0 = 16463194277385538006493566728001486227786740657948067325252587295940494532065739047726911635855222798083564798297575745875829939089155104617971353187283800644588271503369243499488369408980732160648403981697621858247469275317713246150930367274375918283239982926149575828206494609703891781265641859935914641074# e_1 = 53183464145552802360531688680606198853528727337569529128017476892680955108627614437658132384170885260306496461692012374021037184681776910963904643763330840441120876734164247981352314520467566316707955441110480325303720433156607378590168931447833282902975068598066007058786049095275193605661581759764776542387# n_1 = 90833211551873588270723217667845249495678823850090802136881865517157637454704099665876807553866070458443591809771270518186644833084553243489440868805023263983493252562278743540874001449613551511883028751718014192333516201168480774970739015419610164062537605779410456513031793199999777014894788382706200020757# c_1 = 22230407808384569931627303117203531383384351503543847426279650122016349747727471353664420880494793722905388983532487901293245030301420692356478218700042566146940345598488036019164944136859836172333034426475299360977773550746037781230355523685359896051245838911971087469266586191931907778679857463565972955053# e_2 = 79529912185727957854696598539340243519754783720481234589808002665134740533984846798408244547517529674239903959400216641821295959596895413579323465593465830465480139546555530097368569245288033553150779307023833488210096219439360271707710591508276783814809197507754362030663819911878417169437723245134949158879# n_2 = 129884915453563769925617783280424054377155108084275359132533095588688005688782127459458645185506079867020438362987468403256647102770553654162232890781552673147712836927912298560902655711987881754839768794787168125599139190546712270501013360266296615603202216855466243614243328596099717542864006193566337698107# c_2 = 45046520188230821139461613627667404949754182395531112466748475103653526026321780076835632789482008756547877684013753034233663379312517859344427404926219930531928639530079463417386563408726593602509407278834104957286532673590748489716544044445125128760577108367123150435970486466443524388142767285555053802498</code></pre><p>这个题目中，攻击者可以通过已知的 $e_i$ 和 $N_i$ 来构造格 $\mathcal{B}_r$，然后直接使用 LLL 算法来求解出格 $\mathcal{L}$ 上的 $SVP$ 问题，从而得到$v_r$，最终获取到原始消息 $m$。</p><p>解题代码示例：</p><pre><code class="lang-python">from Crypto.Util.number import *from sage.all import *e_0 = 65273226782938365337746916273730336693400864987226527768629555154328678878476135267049386675622260483462220524488000354329161815186124642838496578365092125560635683645652071790968480727817124418736448310995681377289580390945033014829116595493634248909648715641159852982292802459935880193569900887152429411847n_0 = 68273324976926984651373562317651913900179693126653185371759797219277881642295726703952462364177190633417720186224603741096752660090376579503727572378024303201815180519953050892027877371422177476434135471869132077433509929481856863664414488106874978031117040155569240599125372440850335176938915089228631971431c_0 = 16463194277385538006493566728001486227786740657948067325252587295940494532065739047726911635855222798083564798297575745875829939089155104617971353187283800644588271503369243499488369408980732160648403981697621858247469275317713246150930367274375918283239982926149575828206494609703891781265641859935914641074e_1 = 53183464145552802360531688680606198853528727337569529128017476892680955108627614437658132384170885260306496461692012374021037184681776910963904643763330840441120876734164247981352314520467566316707955441110480325303720433156607378590168931447833282902975068598066007058786049095275193605661581759764776542387n_1 = 90833211551873588270723217667845249495678823850090802136881865517157637454704099665876807553866070458443591809771270518186644833084553243489440868805023263983493252562278743540874001449613551511883028751718014192333516201168480774970739015419610164062537605779410456513031793199999777014894788382706200020757c_1 = 22230407808384569931627303117203531383384351503543847426279650122016349747727471353664420880494793722905388983532487901293245030301420692356478218700042566146940345598488036019164944136859836172333034426475299360977773550746037781230355523685359896051245838911971087469266586191931907778679857463565972955053e_2 = 79529912185727957854696598539340243519754783720481234589808002665134740533984846798408244547517529674239903959400216641821295959596895413579323465593465830465480139546555530097368569245288033553150779307023833488210096219439360271707710591508276783814809197507754362030663819911878417169437723245134949158879n_2 = 129884915453563769925617783280424054377155108084275359132533095588688005688782127459458645185506079867020438362987468403256647102770553654162232890781552673147712836927912298560902655711987881754839768794787168125599139190546712270501013360266296615603202216855466243614243328596099717542864006193566337698107c_2 = 45046520188230821139461613627667404949754182395531112466748475103653526026321780076835632789482008756547877684013753034233663379312517859344427404926219930531928639530079463417386563408726593602509407278834104957286532673590748489716544044445125128760577108367123150435970486466443524388142767285555053802498M = floor(isqrt(n_2))Mat = Matrix(ZZ,[    [M,e_0,e_1,e_2],    [0,-n_0,0,0],    [0,0,-n_1,0],    [0,0,0,-n_2]])Mat_LLL=Mat.LLL()d = int(abs(Mat_LLL[0][0])/M)m = pow(c_0, d, n_0)print(long_to_bytes(m))# b&#39;flag{u_gain_deep_insights_into_LLL!!!}&#39;</code></pre><p>上一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/4002.html">RSA加解密专题 - 公钥指数相关攻击</a> 👈<br>下一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/4004.html">RSA加解密专题 - 公私钥格式相关攻击</a> 👈<br>回到开始：<a href="http://lr2006-robot.github.io/myblog.github.io/about/">关于我</a> 👈</p><p>相关链接：<br><a href="https://lr2006-robot.github.io/myblog.github.io/post/1003.html">密码学数学基础 - 二次剩余</a> 👈<br><a href="http://lr2006-robot.github.io/myblog.github.io/post/3006.html">密码学算法 - Wiener算法</a> 👈</p>]]>
    </content>
    <id>https://lr2006-robot.github.io/myblog.github.io/post/4003.html</id>
    <link href="https://lr2006-robot.github.io/myblog.github.io/post/4003.html"/>
    <published>2026-03-02T13:58:00.000Z</published>
    <summary>
      <![CDATA[<p>在RSA加解密算法中，私钥是一个非常重要的参数，它与公钥指数和模数之间存在着密切的关系。如果私钥的选择不当，或者在某些特定情况下，攻击者可能会利用这些关系来破解RSA加密算法，从而获取到原始消息或者分解出模数。</p>
<p>本文将介绍一些与RSA私钥 $d$ 相关的攻击方]]>
    </summary>
    <title>RSA加解密专题 - 私钥 d 相关攻击</title>
    <updated>2026-03-16T07:30:16.623Z</updated>
  </entry>
  <entry>
    <author>
      <name>Dorange</name>
    </author>
    <category term="RSA加解密专题" scheme="https://lr2006-robot.github.io/myblog.github.io/categories/RSA%E5%8A%A0%E8%A7%A3%E5%AF%86%E4%B8%93%E9%A2%98/"/>
    <category term="密码学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="前言" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%89%8D%E8%A8%80/"/>
    <category term="RSA" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/RSA/"/>
    <content>
      <![CDATA[<p>在实际应用中，攻击者可能会利用一些特定的攻击方法来破解RSA加密，尤其是当公钥指数选择不当时。</p><p>本文将介绍一些与 RSA 公钥指数相关的攻击方法，包括常见的攻击手段如小公钥攻击、共模攻击等。</p><h2><span id="共模攻击">共模攻击 😝</span></h2><p>对于同一个模数 $n$，用于加密同一个 $m$，但是用不同 $e$ 加密两次</p><script type="math/tex; mode=display">\begin{align*}m^{e_1} \equiv c_1 \mod n \\m^{e_2} \equiv c_2 \mod n\end{align*}</script><p>使用裴蜀定理可以破解,构造</p><script type="math/tex; mode=display">r e_1 + s e_2 = gcd(e_1,e_2)</script><p>则由</p><script type="math/tex; mode=display">c_{1}^r \cdot c_{2}^s \equiv m^{er_1 + se_2} \mod n</script><p>从而绕过私钥d解密</p><h3><span id="共模攻击例题">共模攻击例题</span></h3><p><a href="https://files.catbox.moe/4oo3fy.zip">共模攻击例题下载</a></p><pre><code class="lang-python">from Crypto.Util.number import *from secret import mn=22708078815885011462462049064339185898712439277226831073457888403129378547350292420267016551819052430779004755846649044001024141485283286483130702616057274698473611149508798869706347501931583117632710700787228016480127677393649929530416598686027354216422565934459015161927613607902831542857977859612596282353679327773303727004407262197231586324599181983572622404590354084541788062262164510140605868122410388090174420147752408554129789760902300898046273909007852818474030770699647647363015102118956737673941354217692696044969695308506436573142565573487583507037356944848039864382339216266670673567488871508925311154801e1=11187289e2=9647291c1=pow(bytes_to_long(m.encode()),e1,n)c2=pow(bytes_to_long(m.encode()),e2,n)print(&quot;c1=&quot;,c1)print(&quot;c2=&quot;,c2)# c1= 790366317276917716093037100691893212049197503039073730961711955448039864232657167573766790435902489218987416033345432295535434340907830576059653129588389135233836704959763784732603854307280897825796829293821089699244606589550689905999360752865586633248545571069424718536350480397506708904105353777714019847026603704906940973023655873113053577161389905208578179532007836298832828616932855035573669965730367452024031424244549158099754154915714597044930116500701707880040556392334392040167123504591683227247869389138456426790241330370554371099743080796833926937422959498423437908845573739316285898375909700396674766321# c2= 4478228437147112241330288727207043414653440426045870497003424494438532257351016896075282127540792673410751595389118015588892772311255950462483402965089986383679582710285977427903307418218183817805264826906460262820854269348325404657900131315817318564877009830557176260073108040099242211702768455039629577625595469833810558303709929093235956775183074705448641710348260784158331497639337250366911970600020203747263235354753699686026666826062329129036821935202924925003686947876422542100428131746969352587613145920594297604370505030268652906467071393237088678927582520826567309207877633272146060026832148078270532393467</code></pre><p>在这个例题中，两个加密使用了同一个模数 $n$，但是使用了不同的公钥指数 $e_1$ 和 $e_2$，攻击者可以通过构造裴蜀定理中的线性组合来破解，获取到原始消息 $m$。</p><p>解题代码示例：</p><pre><code class="lang-python">from Crypto.Util.number import *import gmpy2 as gpn=22708078815885011462462049064339185898712439277226831073457888403129378547350292420267016551819052430779004755846649044001024141485283286483130702616057274698473611149508798869706347501931583117632710700787228016480127677393649929530416598686027354216422565934459015161927613607902831542857977859612596282353679327773303727004407262197231586324599181983572622404590354084541788062262164510140605868122410388090174420147752408554129789760902300898046273909007852818474030770699647647363015102118956737673941354217692696044969695308506436573142565573487583507037356944848039864382339216266670673567488871508925311154801e1=11187289e2=9647291c1= 790366317276917716093037100691893212049197503039073730961711955448039864232657167573766790435902489218987416033345432295535434340907830576059653129588389135233836704959763784732603854307280897825796829293821089699244606589550689905999360752865586633248545571069424718536350480397506708904105353777714019847026603704906940973023655873113053577161389905208578179532007836298832828616932855035573669965730367452024031424244549158099754154915714597044930116500701707880040556392334392040167123504591683227247869389138456426790241330370554371099743080796833926937422959498423437908845573739316285898375909700396674766321c2= 4478228437147112241330288727207043414653440426045870497003424494438532257351016896075282127540792673410751595389118015588892772311255950462483402965089986383679582710285977427903307418218183817805264826906460262820854269348325404657900131315817318564877009830557176260073108040099242211702768455039629577625595469833810558303709929093235956775183074705448641710348260784158331497639337250366911970600020203747263235354753699686026666826062329129036821935202924925003686947876422542100428131746969352587613145920594297604370505030268652906467071393237088678927582520826567309207877633272146060026832148078270532393467g,r,s = gp.gcdext(e1, e2)m_g = pow(c1, r, n) * pow(c2, s, n) % nm, ok = gp.iroot(m_g,g)assert okprint(long_to_bytes(m))# flag{use_the_same_n_is_dangerous}</code></pre><h2><span id="小公钥攻击">小公钥攻击 🤠</span></h2><p>如果公钥指数 $e$ 选择得过小（如 $e=3$），攻击者可以直接爆破出正确的结果，获取到原始消息 $m$。</p><script type="math/tex; mode=display">C \equiv M^e \mod n</script><p>如果 $e$ 较小，则 $C = M^e$，攻击者可以直接计算</p><script type="math/tex; mode=display">M = \sqrt[e]{C + k n}</script><p>来获取到原始消息 $m$。</p><h3><span id="小公钥攻击例题">小公钥攻击例题</span></h3><p><a href="https://files.catbox.moe/n52clp.zip">小公钥攻击例题下载</a></p><pre><code class="lang-python">from Crypto.Util.number import getPrime, bytes_to_longfrom secret import mp = getPrime(512)q = getPrime(512)e = 5n = p * qprint(&quot;n = &quot;,n)c = pow(bytes_to_long(m.encode()), e, n)print(&quot;c = &quot;,c)# n =  151884658354743631395724585360507748159987284748681837528758493598171633984152168593482231461426893458183687924535172757062309062690802891891728554675548497092710048532926098530490127945685611807130560943577105993562003866576646552381530174936590879602799062588680515873294508600878401809375729119964496068873# c =  68358389983146861908514174327099913729389440802950325638324116448687159665872062912200362098390297202380605609237336130498725419673621377108679738418963796009518478977346741380680301178771968725758445389936586626869076720616127612057058701</code></pre><p>在这个例题中，公钥指数 $e$ 选择得过小（$e=5$），攻击者可以直接计算 $M = \sqrt[5]{C + k n}$ 来获取到原始消息 $m$。</p><p>解题代码示例：</p><pre><code class="lang-python">from Crypto.Util.number import  long_to_bytesimport gmpy2n =  151884658354743631395724585360507748159987284748681837528758493598171633984152168593482231461426893458183687924535172757062309062690802891891728554675548497092710048532926098530490127945685611807130560943577105993562003866576646552381530174936590879602799062588680515873294508600878401809375729119964496068873c =  68358389983146861908514174327099913729389440802950325638324116448687159665872062912200362098390297202380605609237336130498725419673621377108679738418963796009518478977346741380680301178771968725758445389936586626869076720616127612057058701e = 5for k in range(0,10000000):    temp = c + k * n    m, ok = gmpy2.iroot(temp, e)    print(f&quot;Trying k={k}...&quot;, end=&quot;\r&quot;)    if ok:        print(long_to_bytes(m))        break# flag{small_e_is_bad}</code></pre><p>实际上，对于这种小根攻击，我们使用 Coppersmith 攻击会更快一些，这里我们只是为了演示小公钥攻击的原理，所以使用了暴力破解的方法。当然，仅限于这个明文较小的例题，如果明文较大，破解的方法就不太适用了。<br>这里我附上一个使用Coppersmith攻击的解题代码示例，供大家参考：</p><pre><code class="lang-python">from sage.all import *from Crypto.Util.number import *n =  151884658354743631395724585360507748159987284748681837528758493598171633984152168593482231461426893458183687924535172757062309062690802891891728554675548497092710048532926098530490127945685611807130560943577105993562003866576646552381530174936590879602799062588680515873294508600878401809375729119964496068873c =  68358389983146861908514174327099913729389440802950325638324116448687159665872062912200362098390297202380605609237336130498725419673621377108679738418963796009518478977346741380680301178771968725758445389936586626869076720616127612057058701e = 5R = Zmod(n)P.&lt;x&gt; = PolynomialRing(R)f = x^e - croots = f.small_roots(bound=2**512,epsilon=1/32)for m in roots:    flag = long_to_bytes(int(m))    print(flag)</code></pre><h2><span id="håstad-广播攻击">Håstad 广播攻击 🧐</span></h2><p>如果同一个消息 $M$ 被加密成多个密文 $C_i$，并且使用了相同的公钥指数 $e$，但是不同的模数 $n_i$，攻击者可以通过中国剩余定理（CRT）来破解，获取到原始消息 $M$。</p><p>我们以 $e=3$ 为例，同时，我们假设 $m &lt; n_i$，对于每个 $n_i$，我们都有 $m &lt; n_i, \quad 1 \leq i \leq 3$。如果这个条件不满足的话，就会使得情况变得比较复杂，这里我们暂不讨论。</p><script type="math/tex; mode=display">\begin{align*}C_1 &\equiv M^3 \mod n_1 \\C_2 &\equiv M^3 \mod n_2 \\C_3 &\equiv M^3 \mod n_3\end{align*}</script><p>既然他们互素，那么我们可以根据中国剩余定理，可得</p><script type="math/tex; mode=display">C \equiv M^3 \mod n_1 n_2 n_3</script><p>此外，既然 $m &lt; n_i, \quad 1 \leq i \leq 3$，那么我们知道 $m^3 &lt; n_1 n_2 n_3$，因此 $C &lt; m^3 &lt; n_1 n_2 n_3$，所以 $C = M^3$，我们可以直接开三次根来获取到原始消息 $M$。</p><p>对于较大的 $e$ 来说，我们只是需要更多的明密文对。</p><h3><span id="håstad-广播攻击例题">Håstad 广播攻击例题</span></h3><p><a href="https://files.catbox.moe/9h8l7c.zip">Håstad 广播攻击例题下载</a></p><pre><code class="lang-python">from Crypto.Util.number import getPrime, bytes_to_longfrom secret import mp1 = getPrime(1024)p2 = getPrime(1024)p3 = getPrime(1024)q1 = getPrime(1024)q2 = getPrime(1024)q3 = getPrime(1024)n1 = p1 * q1n2 = p2 * q2n3 = p3 * q3e = 3c1 = pow(bytes_to_long(m.encode()), e, n1)c2 = pow(bytes_to_long(m.encode()), e, n2)c3 = pow(bytes_to_long(m.encode()), e, n3)print(f&quot;n1 = {n1}&quot;)print(f&quot;n2 = {n2}&quot;)print(f&quot;n3 = {n3}&quot;)print(f&quot;c1 = {c1}&quot;)print(f&quot;c2 = {c2}&quot;)print(f&quot;c3 = {c3}&quot;)# n1 = 21180112366275308792707198865461302488559205693127557021721507567406658951027484893183971746874386346549344017657780496423609870191100125206162930591509951158158895313786384410078334087373942711313704671415169591259711219172542673857746853640129378392407801833076846765791915529766486673221298820251490327058865256315102761890119503763324479969189120347374788296228439421010724923476231379022998720352792854596487066187715900286865855673410192615524429078338954151715726776929439932040633058951481065598837709559391701919303564132940137424657515472160189726163543109080323384612138592271850712993603287137677507275371# n2 = 26298692870046419603854209107106438626626364322119633807665057835205077716901148443766353934040185822955892086743845239542911306720672775951179579233523497436530727494678487971265695182505161985229279355999100651830529513900383767281291762278091913698101895553459847468191806086109066122848275034046679346214591398371960794960928446173802863362768056449148080873128801237014636371466461105802242018480278524831090013751334363547633971239825548849102143384730044030187903043701396605393326607857727105149463605439271849673250355049751794904386124840390758363224179076896253944395507179538229841566514339776966378699671# n3 = 12869245342475181303320296924755202819753541797517717539596064095556108493576385999354161121731642352002540447701060344408122911146469189736765677092104255335613300039538354090648284374737637720058242853559989339600163385950025982705089661378793053705329618987830223564779324941385061454586426419929951975212520146873738260085179715217979995535362995225493010874118526395325025302743041973441408932644128476440939321271157322933582064335494518736514896863501417816715050783307592053908471995029904208083874643331646961543050595330178883620466733870718886642963399595471133909399884799882277656325390344541700992326753# c1 = 27986825396866988547862724357202658730691167333564612684951165687800210126707967163602150013745798355893557986277872052045351674932828898392362316022217536592578822801708792807726846802836702174268667149603691471355321662500021629469435803425125# c2 = 27986825396866988547862724357202658730691167333564612684951165687800210126707967163602150013745798355893557986277872052045351674932828898392362316022217536592578822801708792807726846802836702174268667149603691471355321662500021629469435803425125# c3 = 27986825396866988547862724357202658730691167333564612684951165687800210126707967163602150013745798355893557986277872052045351674932828898392362316022217536592578822801708792807726846802836702174268667149603691471355321662500021629469435803425125</code></pre><p>这个例题中，同一个消息 $M$ 被加密成了三个密文 $C_1$、$C_2$ 和 $C_3$，并且使用了相同的公钥指数 $e=3$，但是不同的模数 $n_1$、$n_2$ 和 $n_3$，攻击者可以通过中国剩余定理来破解，获取到原始消息 $M$。</p><p>解题代码示例：</p><pre><code class="lang-python">from sage.all import *import gmpy2 as gpn1 = 21180112366275308792707198865461302488559205693127557021721507567406658951027484893183971746874386346549344017657780496423609870191100125206162930591509951158158895313786384410078334087373942711313704671415169591259711219172542673857746853640129378392407801833076846765791915529766486673221298820251490327058865256315102761890119503763324479969189120347374788296228439421010724923476231379022998720352792854596487066187715900286865855673410192615524429078338954151715726776929439932040633058951481065598837709559391701919303564132940137424657515472160189726163543109080323384612138592271850712993603287137677507275371n2 = 26298692870046419603854209107106438626626364322119633807665057835205077716901148443766353934040185822955892086743845239542911306720672775951179579233523497436530727494678487971265695182505161985229279355999100651830529513900383767281291762278091913698101895553459847468191806086109066122848275034046679346214591398371960794960928446173802863362768056449148080873128801237014636371466461105802242018480278524831090013751334363547633971239825548849102143384730044030187903043701396605393326607857727105149463605439271849673250355049751794904386124840390758363224179076896253944395507179538229841566514339776966378699671n3 = 12869245342475181303320296924755202819753541797517717539596064095556108493576385999354161121731642352002540447701060344408122911146469189736765677092104255335613300039538354090648284374737637720058242853559989339600163385950025982705089661378793053705329618987830223564779324941385061454586426419929951975212520146873738260085179715217979995535362995225493010874118526395325025302743041973441408932644128476440939321271157322933582064335494518736514896863501417816715050783307592053908471995029904208083874643331646961543050595330178883620466733870718886642963399595471133909399884799882277656325390344541700992326753c1 = 27986825396866988547862724357202658730691167333564612684951165687800210126707967163602150013745798355893557986277872052045351674932828898392362316022217536592578822801708792807726846802836702174268667149603691471355321662500021629469435803425125c2 = 27986825396866988547862724357202658730691167333564612684951165687800210126707967163602150013745798355893557986277872052045351674932828898392362316022217536592578822801708792807726846802836702174268667149603691471355321662500021629469435803425125c3 = 27986825396866988547862724357202658730691167333564612684951165687800210126707967163602150013745798355893557986277872052045351674932828898392362316022217536592578822801708792807726846802836702174268667149603691471355321662500021629469435803425125C = crt([c1, c2, c3], [n1, n2, n3])flag = int(gp.iroot(C, e)[0])print(long_to_bytes(flag).decode())# flag{e_quals_3_has_been_abandoned}</code></pre><h2><span id="rabin-加密算法">Rabin 加密算法 😎</span></h2><p>Rabin 加密算法是 RSA 加密算法的一个变种，它使用了平方运算来加密消息。Rabin 加密算法的安全性基于大数分解的困难性，与 RSA 加密算法类似。可以直接将他看作 RSA 加密算法中 $e=2$ 的特殊情况。</p><h3><span id="rabin-加密算法">Rabin 加密算法 😎</span></h3><p>Rabin 加密算法是 RSA 加密算法的一个变种，它使用了平方运算来加密消息。Rabin 加密算法的安全性基于大数分解的困难性，与 RSA 加密算法类似。可以直接将他看作 RSA 加密算法中 $e=2$ 的特殊情况。</p><p>Rabin 解密过程：</p><p>加密:</p><script type="math/tex; mode=display">C \equiv M^2 \mod n</script><p>解密:<br>计算出模 $p$ 和模 $q$ 下的平方根，得到4个候选值，然后用 CRT 合并这4个候选值，最终还原出正确的明文。</p><script type="math/tex; mode=display">\begin{cases}m_{p_1} \equiv \sqrt{C} \mod p \\m_{p_2} \equiv -\sqrt{C} \mod p \\m_{q_1} \equiv \sqrt{C} \mod q \\m_{q_2} \equiv -\sqrt{C} \mod q \\\end{cases}</script><p>扩展欧几里得算法计算出 $q$ 在模 $p$ 下的逆元 $q^{-1}$ 和 $p$ 在模 $q$ 下的逆元 $p^{-1}$，然后用 CRT 合并这4个候选值，得到4个明文候选值：</p><script type="math/tex; mode=display">\begin{cases}m_1 \equiv m_{p_1} \cdot q \cdot q^{-1} + m_{q_1} \cdot p \cdot p^{-1} \mod n \\m_2 \equiv m_{p_1} \cdot q \cdot q^{-1} + m_{q_2} \cdot p \cdot p^{-1} \mod n \\m_3 \equiv m_{p_2} \cdot q \cdot q^{-1} + m_{q_1} \cdot p \cdot p^{-1} \mod n \\m_4 \equiv m_{p_2} \cdot q \cdot q^{-1} + m_{q_2} \cdot p \cdot p^{-1} \mod n \\\end{cases}</script><p>然后遍历这4个候选值，尝试解码，成功解码的那个就是正确的明文。</p><h3><span id="rabin-加密算法例题">Rabin 加密算法例题</span></h3><p><a href="https://files.catbox.moe/7uk3yj.zip">Rabin 加密算法例题下载</a></p><pre><code class="lang-python">from Crypto.Util.number import getPrime, bytes_to_longfrom secret import mp = getPrime(256)while p % 4 != 3:    p = getPrime(256)q = getPrime(256)while q % 4 != 3 or q == p:    q = getPrime(256)n = p * qe = 2c = pow(bytes_to_long(m.encode()), e, n)print(&quot;n =&quot;, n)print(&quot;c =&quot;, c)# p = 85456561857487541706156138220614376877735385707364349049681146831137409802611# q = 75122157510403898788703795042660745097752300792713827867823026882894316079263# c = 1468508928651076555882997390203921809955789362208256690325971247724971060869686807252565486180911155882249</code></pre><p>这个例题中，模数 $n$ 是由两个满足 $p \equiv 3 \mod 4$ 和 $q \equiv 3 \mod 4$ 的素数 $p$ 和 $q$ 组成的，攻击者可以通过计算模 $p$ 和模 $q$ 下的平方根来获取到原始消息 $m$。</p><p>解题代码示例：</p><pre><code class="lang-python">from Crypto.Util.number import long_to_bytes, inversemp1 = pow(c, (p + 1) // 4, p)mp2 = (p - mp1) % pmq1 = pow(c, (q + 1) // 4, q)mq2 = (q - mq1) % qinv_q_mod_p = inverse(q, p)  # q在模p下的逆元inv_p_mod_q = inverse(p, q)  # p在模q下的逆元candidates = [    (mp1 * q * inv_q_mod_p + mq1 * p * inv_p_mod_q) % n,    (mp1 * q * inv_q_mod_p + mq2 * p * inv_p_mod_q) % n,    (mp2 * q * inv_q_mod_p + mq1 * p * inv_p_mod_q) % n,    (mp2 * q * inv_q_mod_p + mq2 * p * inv_p_mod_q) % n,]# 遍历候选值，还原出正确的明文flag = None  # 初始化flagfor idx, candidate in enumerate(candidates):    try:        # 尝试解码        decoded_str = long_to_bytes(candidate).decode()        # 只有成功解码的才赋值给flag        flag = decoded_str        print(flag)    except (UnicodeDecodeError, ValueError):        continue# flag{u_know_the_rabin}</code></pre><p>Rabin 算法也可以进一步扩展，不一定要 $e = 2$，我们也可以选择 $e = 4$，甚至更大的偶数 $e$，我们的思路也是一样的，计算出模 $p$ 和模 $q$ 下的 $e$ 次根，然后用 CRT 合并这些根，最终还原出正确的明文。不过难度就变成了怎么计算出模 $p$ 和模 $q$ 下的 $e$ 次根了，这个时候我们就可以使用 Adleman-Manders-Miller 算法来计算出模 $p$ 和模 $q$ 下的 $e$ 次根了。</p><h2><span id="相关密文攻击">相关密文攻击 🫠</span></h2><p>相关明文意思是我们用同一个模数 $n$ 和同一个公钥指数 $e$ 加密了两个不同的明文 $m_1$ 和 $m_2$，得到两个密文 $c_1$ 和 $c_2$，而且这两个明文之间存在某种关系，比如 $m_2 = m_1 + k$，其中 $k$ 是一个已知的常数。</p><p>这个时候攻击者就可以利用这个关系来破解，获取到原始消息 $m_1$ 和 $m_2$。当然这种情况一般用于 $e$ 较小的情况，如果 $e$ 较大，这种攻击就不太适用了。</p><h3><span id="相关明文攻击例题">相关明文攻击例题</span></h3><p><a href="https://files.catbox.moe/76h3m0.zip">相关明文攻击例题下载</a></p><pre><code class="lang-python">from Crypto.Util.number import *from secret import me = 5n = 0xf2e5339236455e2bc1b1bd12e45b9341a3b223ddb02dec11c880fa4aa8835df9e463e4c446292cd5a2fe19b10017856654b6d6c3f3a94a95807712329f7dae2e1e6506094d5d2f9c8a05c35cbf3366330996db9bff930fe566016d5e850e232057d419292ce30df9c135d56ef1bb72c38838d4b127aa577ceb4aba94d4e0d55c1 = pow(2*m + 1, e, n)c2 = pow(3*m + 2 , e, n)print(&quot;c1 =&quot;, c1)print(&quot;c2 =&quot;, c2)# c1 = 7641445753073898116827007164793381913392637757517918471003493172096039864745138859395162416166331018059670821487306307100317707328139395788231065689104033478291350670060313374875050515453520864469812221890996287381307066698453475729302664778430397377134780758018837387332598584435841888914421644231089685984# c2 = 167093247921785025551869982469106575631142864056525551417902883782425700355834225006036652059228820657628419198963259510127624586265812555108445743014018325690532163094506166009746619463293864352220499567472463409933060040516110759121216801644457250422763864380123004793281546395406406223721828567329161640</code></pre><p>首先这一题我们可以将已知的信息转换一下，我们有</p><script type="math/tex; mode=display">2m_1 + 1= 3m_2 + 2 \mod n</script><p>再变换一下</p><script type="math/tex; mode=display">m_1 = 3 \cdot 2^{-1} \cdot m_2 + 2^{-1} \mod n</script><p>设 $M1 = x$，那么 $M2 = 3 \cdot 2^{-1} \cdot x + 2^{-1}$<br>如果我们有映射关系 $f(x) = ax + b$,，那么就有 $M_2 = f(M_1)$，因此题面</p><script type="math/tex; mode=display">\begin{align*}(2m + 1)^e - c_1 &= 0 \mod n \\(3m + 2)^e - c_2 &= 0 \mod n\end{align*}</script><p>可以变成求解一个方程组：</p><script type="math/tex; mode=display">\begin{align*}X^e - c_1 = 0 \mod n \ ① \\f(X)^e - c2=0 \mod n \ ②\end{align*}</script><p>显然对于这两个多项式有一个公共解：$M_1$，因此两条式子都可以化简：</p><script type="math/tex; mode=display">\begin{align*}(X - M_1) \cdot K_1 = 0 \mod n \\(X - M_1) \cdot K_2 = 0 \mod n\end{align*}</script><p>$K_i$ 时是多项式方程，由①、②的单调性可知，两式在实数域下仅由唯一解，因此 $K_1$、$K_2$ 两式均不可再因式分解，因此其彼此互素。</p><p>从而我们对这两个多项式求一个 $gcd$ 就可以得到 $(X - M_1)$,但由于在模 $n$ 下,得到的将是 $X+M’$的形式（$M’ = -M_1 \mod n$）,所以最终</p><script type="math/tex; mode=display">M_1 = -M' \mod n</script><p>最后我们就可以通过 $M_1$ 来计算出 $m$ 的值了。最后补充，这种攻击方法也被称为 Franklin-Reiter 攻击。</p><p>解题代码示例：</p><pre><code class="lang-python">from Crypto.Util.number import *from sage.all import *def franklin_reiter(n,e,a,b,c1,c2):    R.&lt;X&gt; = Zmod(n)[]    f1 = X^e - c1    f2 = (a*X + b)^e - c2    m_ = GCD(f1,f2).coefficients()[0] # 返回的是首一多项式，coefficients()返回多项式各项式的系数，项式次数递增，所以第0项是常数    return Integer(n - m_) # 由于tmp其实是 -m % n,所以这里给他转换回去。def GCD(a, b):    if(b == 0):        return a.monic()# 返回首一多项式，即多项式最高次的项式系数为1    else:        return GCD(b, a % b)e = 5n = 0xf2e5339236455e2bc1b1bd12e45b9341a3b223ddb02dec11c880fa4aa8835df9e463e4c446292cd5a2fe19b10017856654b6d6c3f3a94a95807712329f7dae2e1e6506094d5d2f9c8a05c35cbf3366330996db9bff930fe566016d5e850e232057d419292ce30df9c135d56ef1bb72c38838d4b127aa577ceb4aba94d4e0d55b = 1 * pow(2, -1, n)a = 3 * pow(2, -1, n)c1 = 7641445753073898116827007164793381913392637757517918471003493172096039864745138859395162416166331018059670821487306307100317707328139395788231065689104033478291350670060313374875050515453520864469812221890996287381307066698453475729302664778430397377134780758018837387332598584435841888914421644231089685984c2 = 167093247921785025551869982469106575631142864056525551417902883782425700355834225006036652059228820657628419198963259510127624586265812555108445743014018325690532163094506166009746619463293864352220499567472463409933060040516110759121216801644457250422763864380123004793281546395406406223721828567329161640M1 = franklin_reiter(n,e,a,b,c1,c2)print(&quot;M1 =&quot;, M1)m = (M1 - 1) // 2print(&quot;m =&quot;, m)flag = long_to_bytes(int(m))print(flag)# b&#39;flag{u_know_the_franklin_reiter!}&#39;</code></pre><p>我们按照这个思路扩展一下，实际上可以不将其中一条式子归一化处理也行，我们直接对两条式子求 $gcd$ 就可以了，得到的结果也是 $X - M_1$ 的形式，最终我们还是可以通过这个结果来计算出 $m$ 的值的。</p><pre><code class="lang-python">from Crypto.Util.number import *from sage.all import *def franklinReiter(n,e1,e2,c1,c2,noise1,noise2,a,b):    # 在模n的多项式环上定义变量x    R.&lt;x&gt; = PolynomialRing(Zmod(n))    # 构造两个多项式：    # g1 = (a * x + noise1)^e1 - c1    # g2 = (b * x + noise2)^e2 - c2    # 当x = q时，这两个多项式都等于0    g1 = (a * x + noise1)^e1 - c1    g2 = (b * x + noise2)^e2 - c2    def gcd(g1, g2):        # 计算两个多项式的最大公因式        while g2:            g1, g2 = g2, g1 % g2        return g1.monic() # 返回首一多项式    # 最大公因式应该是(x - q)，所以取常数项的相反数就得到q    return -gcd(g1, g2)[0]e = 5n = 0xf2e5339236455e2bc1b1bd12e45b9341a3b223ddb02dec11c880fa4aa8835df9e463e4c446292cd5a2fe19b10017856654b6d6c3f3a94a95807712329f7dae2e1e6506094d5d2f9c8a05c35cbf3366330996db9bff930fe566016d5e850e232057d419292ce30df9c135d56ef1bb72c38838d4b127aa577ceb4aba94d4e0d55b = 3noise2 = 2a = 2noise1 = 1c1 = 7641445753073898116827007164793381913392637757517918471003493172096039864745138859395162416166331018059670821487306307100317707328139395788231065689104033478291350670060313374875050515453520864469812221890996287381307066698453475729302664778430397377134780758018837387332598584435841888914421644231089685984c2 = 167093247921785025551869982469106575631142864056525551417902883782425700355834225006036652059228820657628419198963259510127624586265812555108445743014018325690532163094506166009746619463293864352220499567472463409933060040516110759121216801644457250422763864380123004793281546395406406223721828567329161640M = franklinReiter(n,e,e,c1,c2,noise1,noise2,a,b)print(&quot;M =&quot;, M)flag = long_to_bytes(int(M))print(&quot;Flag =&quot;, flag)# Flag = b&#39;flag{u_know_the_franklin_reiter!}&#39;</code></pre><p>上一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/4001.html">RSA加解密专题 - 模数相关攻击</a> 👈<br>下一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/4003.html">RSA加解密专题 - 私钥 d 相关攻击</a> 👈<br>回到开始：<a href="http://lr2006-robot.github.io/myblog.github.io/about/">关于我</a> 👈</p><p>相关链接：<br><a href="http://lr2006-robot.github.io/myblog.github.io/post/3001.html">密码学算法 - 欧几里得算法</a> 👈<br><a href="http://lr2006-robot.github.io/myblog.github.io/post/2004.html">加解密模式分析 - Rabin 加密算法</a> 👈<br><a href="http://lr2006-robot.github.io/myblog.github.io/post/3010.html">密码学算法 - AMM 算法</a> 👈</p>]]>
    </content>
    <id>https://lr2006-robot.github.io/myblog.github.io/post/4002.html</id>
    <link href="https://lr2006-robot.github.io/myblog.github.io/post/4002.html"/>
    <published>2026-03-02T13:08:00.000Z</published>
    <summary>
      <![CDATA[<p>在实际应用中，攻击者可能会利用一些特定的攻击方法来破解RSA加密，尤其是当公钥指数选择不当时。</p>
<p>本文将介绍一些与 RSA 公钥指数相关的攻击方法，包括常见的攻击手段如小公钥攻击、共模攻击等。</p>
<h2><span id="共模攻击">共模攻击 😝</s]]>
    </summary>
    <title>RSA加解密专题 - 公钥指数相关攻击</title>
    <updated>2026-03-14T12:27:40.039Z</updated>
  </entry>
  <entry>
    <author>
      <name>Dorange</name>
    </author>
    <category term="RSA加解密专题" scheme="https://lr2006-robot.github.io/myblog.github.io/categories/RSA%E5%8A%A0%E8%A7%A3%E5%AF%86%E4%B8%93%E9%A2%98/"/>
    <category term="密码学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="前言" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%89%8D%E8%A8%80/"/>
    <category term="RSA" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/RSA/"/>
    <content>
      <![CDATA[<p>当你在 CTF 赛场遇到 RSA 题型时，模数相关的攻击手法⚔️往往是破解的关键。无论是共模攻击、模重复攻击，还是模数分解的相关漏洞，这些都是 RSA 加解密中常见且高频出现的题型。掌握这些攻击手法，不仅能让你在 CTF 赛场上如鱼得水，还能加深你对 RSA 加解密原理的理解。</p><h2><span id="暴力分解模数">暴力分解模数 🥳</span></h2><p>在 $N$ 的比特位数小于 512 的时候，可以采用大整数分解的策略获取 $p$ 和 $q$。一般来说，使用一些高效的分解工具（如 YAFU、msieve、CADO-NFS 等）可以在合理的时间内完成分解，获取 $p$ 和 $q$。</p><h3><span id="暴力分解模数例题">暴力分解模数例题</span></h3><p><a href="https://files.catbox.moe/sieedy.zip">暴力分解模数例题下载</a></p><pre><code class="lang-python">from Crypto.Util.number import *from secret import Mp = getPrime(56)q = getPrime(56)e = 65537 n = p * qlong_M = long_to_bytes(M.encode())C = pow(long_M, e, n) print(C)print(n)#n: 3211733906383171840011432827424907#C: 1251845031704156126184126788111750</code></pre><p>在这个例题中，模数 $n$ 的比特位数为 112 位，使用暴力分解的方式可以在几秒钟内获取到 $p$ 和 $q$，从而计算出私钥 $d$，最终解密出原始消息 $M$。</p><p>解题代码示例：</p><pre><code class="lang-python">from Crypto.Util.number import *from sage.all import factorn = 3211733906383171840011432827424907C = 1251845031704156126184126788111750result = factor(n)p = result[0][0]q = result[1][0]phi = (p-1)*(q-1)e = 65537d = inverse(e, phi)m = pow(C, d, n)print(long_to_bytes(m))# b&#39;flag{factor}&#39;</code></pre><h2><span id="模数过大攻击">模数过大攻击 😇</span></h2><p>由于 RSA 的安全性依赖于大整数分解的困难性，因此理论上如果模数 $N$ 的比特位数过大，攻击者可能无法在合理的时间内完成分解，从而无法获取到 $p$ 和 $q$，最终无法破解 RSA 加密。</p><p>但是，有时候模数过大，达到因子 $p$ 或者 $q$ 可以直接当加解密的模数时，我们拿到 $p$ 时，是可以直接解密的。</p><h3><span id="模数过大攻击例题">模数过大攻击例题</span></h3><p><a href="https://files.catbox.moe/u63aqy.zip">模数过大攻击例题下载</a></p><pre><code class="lang-python">from Crypto.Util.number import *from secret import flagm=bytes_to_long(flag)p=getPrime(512)q=getPrime(512)print(&#39;p=&#39;,p)print(&#39;q=&#39;,q)n=p*qe=65537c=pow(m,e,n)print(&#39;c=&#39;,c)# p= 12408795636519868275579286477747181009018504169827579387457997229774738126230652970860811085539129972962189443268046963335610845404214331426857155412988073# q= 12190036856294802286447270376342375357864587534233715766210874702670724440751066267168907565322961270655972226761426182258587581206888580394726683112820379# c= 21336045298142901291286840871587672865162787095337602832807363508232388776491914969338774738649382963457992989937263476335226789823301148587701912487928423012379208808206490825708386587184421011578572210993850461087767328588898076582321561621227540126149951300387265955685592427820272752547313291074696393753</code></pre><p>这个题如果直接解密会发现无法解密，因为实际上构造时出了问题， $e$ 和 $q - 1$ 不是互素的，所以无法计算出私钥 $d$，但是我们拿到 $p$ 时，由于这个 $p$ 是远大于明文的，是可以直接解密的。</p><p>解题代码示例：</p><pre><code class="lang-python">from sage.all import *from Crypto.Util.number import *e=65537p= 12408795636519868275579286477747181009018504169827579387457997229774738126230652970860811085539129972962189443268046963335610845404214331426857155412988073q= 12190036856294802286447270376342375357864587534233715766210874702670724440751066267168907565322961270655972226761426182258587581206888580394726683112820379c= 21336045298142901291286840871587672865162787095337602832807363508232388776491914969338774738649382963457992989937263476335226789823301148587701912487928423012379208808206490825708386587184421011578572210993850461087767328588898076582321561621227540126149951300387265955685592427820272752547313291074696393753g1 = gcd(p-1,e) g2 = gcd(q-1,e)print(g1) # 1print(g2) # 65537dp = inverse(e,(p-1))mp = pow(c,dp,p)print(long_to_bytes(mp))# b&#39;flag{too_short_flag_is_not_secure}&#39;</code></pre><h2><span id="p-和-q-选取不当">$p$ 和 $q$ 选取不当 😎</span></h2><p>如果 $p$ 和 $q$ 选取不当，例如它们之间的差值过小，或者它们的比特位数相差过大，那么攻击者可以利用这些弱点进行攻击。</p><p>如果 $p$ 和 $q$ 的差值较小，可以使用 Fermat 分解法快速分解出 $n$，从而获取到 $p$ 和 $q$。</p><p>Fermat 分解法：<br>依据公式</p><script type="math/tex; mode=display">\frac{(p+q)^2}{4} - n = \frac{(p+q)^2}{4} - pq = \frac{(p-q)^2}{4}</script><p>$|p-q|$ 比较小,那么 $\frac{(p-q)^2}{4}$ 也小，所以 $\frac{(p+q)^2}{4}$ 接近于n，可以通过不断增加 $\frac{(p+q)}{2}$ 的值，直到 $\frac{(p-q)^2}{4}$ 为完全平方数。</p><p>设 $x = \frac{(p+q)}{2}$, $y = \frac{(p-q)}{2}$<br>当满足 $x^2 - n = y^2$ 时即可求出方程</p><p>如果 $p$ 和 $q$ 的比特位数相差过大，那么攻击者可以尝试直接分解较小的素数，获取到其中一个素数后，再通过除法计算出另一个素数，从而获取到 $p$ 和 $q$。</p><h3><span id="p-和-q-选取不当例题一">$p$ 和 $q$ 选取不当例题一</span></h3><p><a href="https://files.catbox.moe/valiv4.zip">选取不当例题一下载</a></p><pre><code class="lang-python">from Crypto.Util.number import bytes_to_long, getPrimefrom sercet import mp = getPrime(1024)q = getPrime(25)n = p * qe = 65537c = pow(bytes_to_long(m.encode()), e, n)print(f&quot;n = {n}&quot;)print(f&quot;c = {c}&quot;)# n = 1906011750017368771307796117673461157817901033778025473698320639347279300698085835647703350923834808686618760795173855605538695556283974551407384296775817454538392743419582983483423430619884117509611946945420798868634216459855241900807180705272425022537879022082812277121832800892726814372715231802506905483235703703   # c = 549077691873070738038063799394443169955042904352211214070197952774248103687795272253319938425527517181518258210963581835901145261931143229164695429262477411771050634538709862613793004339395308836706979967103332910059842307878824556049956059889500094079522390808561349617771222185714804173669464216698326999828372795</code></pre><p>在这个例题中，$p$ 和 $q$ 的比特位数相差过大，攻击者可以直接分解较小的素数 $q$，获取到 $q$ 后，再通过除法计算出 $p$，从而获取到 $p$ 和 $q$。</p><p>解题代码示例：</p><pre><code class="lang-python">from Crypto.Util.number import long_to_bytes, inversefor p in range (2**24,2**25):    if n % p == 0:        q = n // p        print(f&quot;Found factors:\np = {p}\nq = {q}&quot;)        breake = 65537m = pow(c, inverse(e, (p-1)*(q-1)), n)print(f&quot;Decrypted message:\n{long_to_bytes(m)}&quot;)# flag{u_had_to_chose_pq_very_carefully}</code></pre><h3><span id="p-和-q-选取不当例题二">$p$ 和 $q$ 选取不当例题二</span></h3><p><a href="https://files.catbox.moe/srjnas.zip">选取不当例题二下载</a></p><pre><code class="lang-python">import gmpy2from Crypto.Util.number import * from secret import mp = getPrime(2048) q = gmpy2.next_prime(p)for i in range(3600):    if i%100 ==0:        print(i)    q = gmpy2.next_prime(q)n = p * qe = 0x10001m = bytes_to_long(m)c = pow(m,e,n)print(c)print(n)# c = 62348462953990088934443336160656676256379196401827670581893451052313356600633156941411475073416038229710206387318746963512184736996831716413075320743814866642932860845362219164316326327314284158359209179674094486144680764515105492217458520575237366185272695957361214915406136484746226387883188256560994222709908095180408898696125963039783417587557949172960800557890764169451380255783986305118036512216986529314746084028860213669584595890037896692321861109269594521684968556512950683264437797627221443003439607555292132467628624371973634298128744397311157396100677819983319642517810760039637209759076927554454366134319010242392632132662642903994785600982132993886312621423872136988427063684972059758439594825762030726134443850618714647064586081678339263001700234301845400889943777913130192236281852096864533037602716295223417658798760710279949225248192831610982416617462182909627962556922338248721205926228772344215985646960602952014798520975237358594200048919941619698843434175749789671602254848991688128862206939872826391163844030010778464360088750386438734967569871044926861579066115140189872433762447450824103620809994330813092392551615553658883561091626338360944606525952944254241058687279915857632310080059282018449507613305332# n = 359336501271521532887656688609295042672434709877081900753462802000755944104448697042366747461395534728915053035475459015263877654779607712559439958640046447246795492613180033737648218742508752449971055824413830268089448680617248179389657713305429663149548581330682010843775095659107771083498691437964118187909080096258126217389687367383301166809926191321584757387431941409060744328371424948988833446651828309230245313487641590365198484255142967088588384318797132203546546629955996480505149111166600546126192029751124771571918693276230154597894534213860659972166774036644473508159923466417979740342453286817394016723536509392328304731594460395586984216646043768390806512181233642534151692135644819066105646943194201485395325911256860832603278670900133367627500162513041761043011626948699652168654976313717938901597808754267996648935745100287531478172712432058146052903388546565632174028182472012197791653306648067962114207504888841853254752313822184342829344043028253285087763176816976839219602985900752633788798421802365156777163724743521780060375607506242692718315299078104710983637353515944430437712607451078303509530539260657291973611896675785100483218112781144038004807688281170137433780853291933873671533348523292439854954465561</code></pre><p>在这个例题中，$p$ 和 $q$ 的差值过小，攻击者可以使用 Fermat 分解法快速分解出 $n$，从而获取到 $p$ 和 $q$。</p><p>解题代码示例：</p><pre><code class="lang-python">from Crypto.Util.number import *import gmpy2 as gpc = 62348462953990088934443336160656676256379196401827670581893451052313356600633156941411475073416038229710206387318746963512184736996831716413075320743814866642932860845362219164316326327314284158359209179674094486144680764515105492217458520575237366185272695957361214915406136484746226387883188256560994222709908095180408898696125963039783417587557949172960800557890764169451380255783986305118036512216986529314746084028860213669584595890037896692321861109269594521684968556512950683264437797627221443003439607555292132467628624371973634298128744397311157396100677819983319642517810760039637209759076927554454366134319010242392632132662642903994785600982132993886312621423872136988427063684972059758439594825762030726134443850618714647064586081678339263001700234301845400889943777913130192236281852096864533037602716295223417658798760710279949225248192831610982416617462182909627962556922338248721205926228772344215985646960602952014798520975237358594200048919941619698843434175749789671602254848991688128862206939872826391163844030010778464360088750386438734967569871044926861579066115140189872433762447450824103620809994330813092392551615553658883561091626338360944606525952944254241058687279915857632310080059282018449507613305332n = 359336501271521532887656688609295042672434709877081900753462802000755944104448697042366747461395534728915053035475459015263877654779607712559439958640046447246795492613180033737648218742508752449971055824413830268089448680617248179389657713305429663149548581330682010843775095659107771083498691437964118187909080096258126217389687367383301166809926191321584757387431941409060744328371424948988833446651828309230245313487641590365198484255142967088588384318797132203546546629955996480505149111166600546126192029751124771571918693276230154597894534213860659972166774036644473508159923466417979740342453286817394016723536509392328304731594460395586984216646043768390806512181233642534151692135644819066105646943194201485395325911256860832603278670900133367627500162513041761043011626948699652168654976313717938901597808754267996648935745100287531478172712432058146052903388546565632174028182472012197791653306648067962114207504888841853254752313822184342829344043028253285087763176816976839219602985900752633788798421802365156777163724743521780060375607506242692718315299078104710983637353515944430437712607451078303509530539260657291973611896675785100483218112781144038004807688281170137433780853291933873671533348523292439854954465561e = 65537a = gp.iroot(4*n,2)[0] + 1while 1:    if gp.iroot(a*a - 4 * n,2)[1]:        break    a = a + 1b = gp.iroot(a*a - 4 * n,2)[0]p = (a+b)//2q = (a-b)//2print(p)print(q)phi = (p-1)*(q-1)d = inverse(e,phi)m = pow(c,d,n)print(long_to_bytes(m))# b&#39;flag{u_know_the_fermat_factor!}&#39;</code></pre><h2><span id="模不互素">模不互素</span></h2><p>如果两个 RSA 公钥的模数 $N_1$ 和 $N_2$ 不是互素的，即它们有一个公共的素因子，那么攻击者可以通过计算它们的最大公约数（GCD）来获取到这个公共的素因子，从而分解出两个模数，获取到 $p$ 和 $q$。</p><p>当然，如果复杂一点，也可以是有多个模数之间存在公共的素因子，那么攻击者可以通过计算它们之间的 GCD 来获取到这个公共的素因子，从而分解出多个模数，获取到多个 $p$ 和 $q$。</p><h3><span id="模不互素例题">模不互素例题</span></h3><p><a href="https://files.catbox.moe/qievky.zip">模不互素例题下载</a></p><pre><code class="lang-python">from Crypto.Util.number import *from secret import m1, m2e = 65537c1 = pow(bytes_to_long(m1.encode()), e, n1)c2 = pow(bytes_to_long(m2.encode()), e, n2)print(f&quot;c1 = {c1}&quot;)print(f&quot;c2 = {c2}&quot;)#n1 = 108895617879498519202012987242867653325279569011932848975855816698747912070665498376685393333717683240456930313786558144149400824750715586448894259226955671919120642624030060495951818893447378750288121119385599097111470518763491413669500398960687373958595273557114005654480025508336323302952322683369530980107#n2 = 92629502961791811672543248549202232353258702786741743055932183827495468369794305903601634793096476966443748199284199451053350116924505444008940619603683242386320802539761036526333916507865522071840234750951837168343853321642508612600344263266261925281998163722124602125092198291300471453120330544931376631613#c1 = 61235742166601682407600603003185668421611519337914948325202303574908037131452140001004081261277556291983868494754678213440816904181133566193839896694643634357526170240634104009464312693976654292785764598780495479072122249149103810491522930818342063044200307840151530874059848631341258068315918907694244629521#c2 = 50837238937861806304599627769544061465095296860582855610664744497211980029955384757173623065990262445808162369513719809531991409013899575630438092515903564752263986071475906232663711236691132660232092717966742157839890484764660045346149600612477895717855763187735898010895553739962656299222384523235710729405</code></pre><p>在这个例题中，两个模数 $N_1$ 和 $N_2$ 不是互素的，它们有一个公共的素因子，攻击者可以通过计算它们的 GCD 来获取到这个公共的素因子，从而分解出两个模数，获取到 $p$ 和 $q$。</p><p>解题代码示例：</p><pre><code class="lang-python">from Crypto.Util.number import *p = gcd(n1, n2)q1 = n1 // pq2 = n2 // pphi1 = (p-1)*(q1-1)phi2 = (p-1)*(q2-1)d1 = inverse_mod(e, phi1)d2 = inverse_mod(e, phi2)m1 = pow(c1, d1, n1)m2 = pow(c2, d2, n2)flag = long_to_bytes(m1) + long_to_bytes(m2)print(flag.decode())# flag{u_know_that_the_same_prime_is_dangerous}</code></pre><h2><span id="光滑模数">光滑模数 😝</span></h2><p>由于 $N$ 是由两个大素数 $p$ 和 $q$ 组成的，如果 $p-1$ 或 $q-1$ 是光滑数（即它们的质因数都比较小），那么攻击者可以利用 Pollard 的 p-1 算法来分解出 $N$，从而获取到 $p$ 和 $q$。</p><p>如果 $p+1$ 或 $q+1$ 是光滑数，那么攻击者可以利用 Williams 的 p+1 算法来分解出 $N$，从而获取到 $p$ 和 $q$。</p><h3><span id="光滑模数例题">光滑模数例题</span></h3><p><a href="https://files.catbox.moe/ggqlr2.zip">光滑模数例题下载</a></p><pre><code class="lang-python">from Crypto.Util.number import *from secret import mn = 149767527975084886970446073530848114556615616489502613024958495602726912268566044330103850191720149622479290535294679429142532379851252608925587476670908668848275349192719279981470382501117310509432417895412013324758865071052169170753552224766744798369054498758364258656141800253652826603727552918575175830897e = 65537c = pow(bytes_to_long(m.encode()), e, n)print(&quot;c =&quot;, c)#c = 43990054306025482446722327591812617378270114979666211260906092677157557305353209885519658615131923119123915114818456042307622384696570346974045212401273302638527074498582589588420265753013768960836315458711255307430199646105955701939961071797607203329554440687624048258855706057753535085388735489880883291030</code></pre><p>在这个例题中，模数 $n$ 的 $p-1$ 或 $q-1$ 是光滑数，攻击者可以利用 Pollard 的 p-1 算法来分解出 $N$，从而获取到 $p$ 和 $q$。当然，$p+1$ 或 $q+1$ 也是光滑数，那么攻击者也可以利用 Williams 的 p+1 算法来分解出 $N$，从而获取到 $p$ 和 $q$。</p><p>解题代码示例：</p><pre><code class="lang-python">from sage.all import *from Crypto.Util.number import *from itertools import countfrom sympy import primerange# williams&#39;s p+1 factorizationdef mlucas(v, a, n):    v1, v2 = v, (v ** 2 - 2) % n    for bit in bin(a)[3:]:        if bit == &quot;0&quot;:            v1, v2 = (v1 ** 2 - 2) % n, (v1 * v2 - v) % n        else:            v1, v2 = (v1 * v2 - v) % n, (v2 ** 2 - 2) % n    return v1# 素数生成器def primegen():    yield from primerange(2, 10**6)  # 生成到 10^6 的素数，够用了# 整数对数：ilog(x, b) = 最大整数 l，使得 b^l &lt;= xdef ilog(x, b):    l = 0    while x &gt;= b:        x //= b        l += 1    return l# Williams p+1 分解攻击def Williams_p_plus_1_attack(n):    for v in count(1):  # 不断尝试新的 v        for p in primegen():            e = ilog(isqrt(n), p)            if e == 0:                break            for _ in range(e):                v = mlucas(v, p, n)            g = gcd(v - 2, n)            if 1 &lt; g &lt; n:                return int(g), int(n // g)            if g == n:                break# 开始攻击p1, q1 = Williams_p_plus_1_attack(n)if p1 and q1:    print(&quot;williams&#39;s p+1 factorization successful!&quot;)    print(f&quot;p = {p1}&quot;)    print(f&quot;q = {q1}&quot;)# pollard&#39;s p-1 factorizationdef pollard_p_minus_1(n, B=10**5): # B为素数表的上限,可以根据实际往上调    a = 2  # 通常选2作为基    for p in primerange(2, B):        e = int(isqrt(n).bit_length() / p.bit_length())        a = pow(a, pow(p, e), n)    g = gcd(a - 1, n)    if 1 &lt; g &lt; n:        return g, n // g    else:        return Nonep2, q2 = pollard_p_minus_1(n)if p2 and q2:    print(&quot;pollard&#39;s p-1 factorization successful!&quot;)    print(f&quot;p = {p2}&quot;)    print(f&quot;q = {q2}&quot;)phi = (p1 - 1) * (q1 - 1)d = inverse(e, phi)m = pow(c, d, n)print(long_to_bytes(m))# flag{smoothness_is_important}</code></pre><p>上一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/4000.html">RSA加解密专题 - 前言</a> 👈<br>下一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/4002.html">RSA加解密专题 - 公钥指数相关攻击</a> 👈<br>回到开始：<a href="http://lr2006-robot.github.io/myblog.github.io/about/">关于我</a> 👈</p><p>相关链接：<br><a href="http://lr2006-robot.github.io/myblog.github.io/post/1001.html">密码学数学基础 - 整数关系</a> 👈</p>]]>
    </content>
    <id>https://lr2006-robot.github.io/myblog.github.io/post/4001.html</id>
    <link href="https://lr2006-robot.github.io/myblog.github.io/post/4001.html"/>
    <published>2026-03-02T08:08:00.000Z</published>
    <summary>
      <![CDATA[<p>当你在 CTF 赛场遇到 RSA 题型时，模数相关的攻击手法⚔️往往是破解的关键。无论是共模攻击、模重复攻击，还是模数分解的相关漏洞，这些都是 RSA 加解密中常见且高频出现的题型。掌握这些攻击手法，不仅能让你在 CTF 赛场上如鱼得水，还能加深你对 RSA 加解密原理的理]]>
    </summary>
    <title>RSA加解密专题 - 模数相关攻击</title>
    <updated>2026-03-17T15:07:15.411Z</updated>
  </entry>
  <entry>
    <author>
      <name>Dorange</name>
    </author>
    <category term="加解密模式分析" scheme="https://lr2006-robot.github.io/myblog.github.io/categories/%E5%8A%A0%E8%A7%A3%E5%AF%86%E6%A8%A1%E5%BC%8F%E5%88%86%E6%9E%90/"/>
    <category term="密码学" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="加解密" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E5%8A%A0%E8%A7%A3%E5%AF%86/"/>
    <category term="现代密码" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E7%8E%B0%E4%BB%A3%E5%AF%86%E7%A0%81/"/>
    <category term="非对称加密" scheme="https://lr2006-robot.github.io/myblog.github.io/tags/%E9%9D%9E%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86/"/>
    <content>
      <![CDATA[<p>ECC（Elliptic Curve Cryptography）加密算法，作为非对称加密的现代方案之一，基于椭圆曲线上的离散对数问题的困难性，为数据加密和数字签名提供了高效且安全的解决方案。它在实际应用中被广泛采用，尤其是在资源受限的环境中，如移动设备和物联网设备。</p><p>在本章中，我们将深入分析 ECC 加密算法的核心原理和实现细节。我们将从 ECC 的数学基础开始，逐步揭示其加密和解密过程中的关键步骤。通过对 ECC 算法的详细解析，你将能够理解它是如何利用椭圆曲线上的离散对数问题的困难性来确保数据安全的。</p><h2><span id="ecc-加密数学原理">ECC 加密数学原理 😇</span></h2><p>ECC 加密算法的安全性基于椭圆曲线上的离散对数问题的困难性。</p><h3><span id="椭圆曲线介绍">椭圆曲线介绍</span></h3><p>首先，ECC 定义了一个椭圆曲线 $E_p$，通常采用 Weierstrass 形式的曲线，满足方程 $y^2 = x^3 + ax + b \mod p$，其中 $\Delta = -16(4a^3 + 27b^2) \neq 0$。</p><p>这个曲线的数学公式有点复杂，不过可以通过图像来理解。可以进入下面这个链接，改变参数来进行观察椭圆曲线的图像：</p><p><a href="https://www.desmos.com/calculator/qmviljttzc?lang=zh-CN">椭圆曲线图像</a></p><p>接下来我们介绍一下 ECC 中的群运算。对于椭圆曲线上的点 $A$ 和 $B$，我们定义了点加法和点乘法运算。</p><p>点加法是指将两个点 $A$ 和 $B$ 相加得到一个新的点 $C$，即 $C = A + B$。如图所示，点加法的几何意义是：如果 $A$ 和 $B$ 是曲线上的两个点，那么连接 $A$ 和 $B$ 的直线会与曲线相交于第三个点 $C’$，然后以 $C’$ 关于 x 轴的对称点 $C$ 作为结果。</p><p><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAERjfJprmdlokWw6WFnGvsPX0Q3tYzS7wACEiMAAnZPcFVqV38r38RXrToE.png" alt="ECC 点加法示例一"></p><p>如果 $A$ 和 $B$ 是同一个点，那么点加法的结果是 $2A$，即 $C = A + A$。如图所示，点加法的几何意义是：如果 $A$ 和 $B$ 是同一个点，那么连接 $A$ 和 $B$ 的切线会与曲线相交于另一个点 $C’$，然后以 $C’$ 关于 x 轴的对称点 $C$ 作为结果。</p><p><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAERjh5prmk2oJs8HgUM_EZKQBNye6N-HQACQiMAAnZPcFVzOPmDBZWQODoE.png" alt="ECC 点加法示例二"></p><p>当然，这个运算是我们定义的，并不是真实存在的运算。我们可以通过一些算法来实现这个运算，比如使用斜率公式来计算点加法的结果。</p><p>曲线上的非对称点 $P(x_p,y_p)$ 和 $Q(x_q,y_q)$ 的点加法 $P + Q = R$ 可以通过以下公式计算：</p><script type="math/tex; mode=display">\begin{align*}\lambda = \frac{y_q - y_p}{x_q - x_p} \mod p \\x_r = \lambda^2 - x_p - x_q \mod p \\y_r = \lambda(x_p - x_r) - y_p \mod p\end{align*}</script><p>曲线上的同一点 $P(x_p,y_p)$ 的点加法 $P + P = 2P$ 可以通过以下公式计算：</p><script type="math/tex; mode=display">\begin{align*}\lambda = \frac{3x_p^2 + a}{2y_p} \mod p \\x_{2P} = \lambda^2 - 2x_p \mod p \\y_{2P} = \lambda(x_p - x_{2P}) - y_p \mod p\end{align*}</script><p>本质上就是计算斜率 $\lambda$，然后根据斜率计算出新的点的坐标。</p><h3><span id="标量积与倍加算法">标量积与倍加算法</span></h3><p>在 ECC 中，我们还定义了标量积运算，即将一个点 $P$ 与一个整数 $k$ 相乘，得到一个新的点 $Q$，即 $Q = kP$。这个运算可以理解为将点 $P$ 自身相加 $k$ 次。</p><p><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAERjkJprm7mfxj80jMB_R9k5AABXrUVQL0AAm8jAAJ2T3BVgtm8MjGi24Q6BA.png" alt="ECC 标量积示例"></p><p>倍加算法是一种高效计算标量积的方法，它通过将 $k$ 表示为二进制形式来减少计算次数。具体来说，倍加算法的步骤如下：</p><ol><li>将 $k$ 转换为二进制表示。</li><li>初始化结果点 $Q$ 为无穷远点。</li><li>从二进制表示的最高位开始，依次处理每一位：<ul><li>如果当前位为 1，则将 $Q$ 加上 $P$。</li><li>将 $P$ 加倍，即 $P = 2P$。</li><li>继续处理下一位，直到处理完所有位。</li></ul></li></ol><p>倍加算法的时间复杂度为 $O(\log k)$，相较于直接相加的 $O(k)$，大大提高了计算效率。</p><p>举个例子，如果我们要计算 $kP$，其中 $k = 151$，如果直接暴力运算，我们要进行 $151$ 次点加法但是，我们可以将 $151$ 转换为二进制表示为 $10010111$。</p><p>将 $151$ 转化为二进制：</p><script type="math/tex; mode=display">151 =1 \cdot 2^0  + 1 \cdot 2^1 + 1 \cdot 2^2 + 0 \cdot 2^3 + 1 \cdot 2^4 + 0 \cdot 2^5 + 0 \cdot 2^6 + 1 \cdot 2^7</script><p>所以我们可以通过倍加算法来计算</p><script type="math/tex; mode=display">151P = 2^7 P + 2^4 P + 2^2 P + 2^1 P + 2^0 P</script><p>倍加算法告诉我们的是：</p><ul><li>取一个 $P$</li><li>倍乘，我们得到 $2^1 P$</li><li>$2^1 P$ 加 $P$（为了得到 $2^1P + 2^0P$）</li><li>$2*2P$，我们得到 $2^2P$</li><li>$2^2P$ 加到结果上，（$2^2P + 2^1P + 2^0P$）</li><li>$2*2^2P$，得到 $2^3P$</li><li>扔掉，$2^3P$ 不参加任何加法运算</li><li>$2*2^3P$，得到 $2^4P$</li><li>加到结果上，（$2^4P + 2^2P + 2^1P + 2^0P$）</li><li>…</li></ul><p>最后，我们计算151P只用了7次倍乘和4次加法，极大简化了运算速度。</p><h3><span id="离散化">离散化</span></h3><p>现在，我们将这个椭圆曲线放入模 $p$ 的有限域中，这样我们就得到了一个有限的点集。对于这个点集，我们定义了一个特殊的点，称为无穷远点，记作 $O$，它是群的单位元。</p><p>这样，我们就得到了一个有限的群，群中的元素是椭圆曲线上的点，群运算是我们之前定义的点加法和标量积。而且，我们发现可视化的曲线变成了一个离散的点集，如下图所示：</p><p><img src="https://img.remit.ee/api/file/BQACAgUAAyEGAASHRsPbAAERjnJprnPgLw_iDldkRkqjdLZ9mB2rZgACuSMAAnZPcFUZID_jEoaGSjoE.png" alt="ECC 离散化示例"></p><p>我们之前提到的，ECC 的安全性基于椭圆曲线上的离散对数问题的困难性。对于一个足够大的 $p$ 和一个生成元 $P$，计算 $Q = kP$ 的离散对数 $k$ 是非常困难的。这意味着攻击者无法轻易地从公钥 $Q$ 中推断出私钥 $k$，从而无法解密密文。</p><p>就像打台球，知道球的起点和终点，但是由于中途碰了几次边，所以球的路径是非常复杂的，无法通过简单的计算来推断出球的路径。以此，我们可以理解为什么 ECC 的安全性是基于离散对数问题的困难性。</p><h2><span id="ecc-加密过程">ECC 加密过程 😁</span></h2><p>一天，Alice 想要向 Bob 发送一条秘密消息。为了确保消息的安全性，Alice 决定使用 ECC 加密算法。</p><p>首先通信双方先共享一个椭圆曲线 $E_p$ 和一个基点 $P$，然后构建椭圆曲线上的群，定义群运算（注意和我们平时的运算不同）。</p><p>Alice本地随机生成一个私钥 $k$，并计算公钥 $Q = k P$，将 $Q$ 作为公钥发送给 Bob。</p><p>Bob收到 $Q$ 后，选择一个随机数 $r$，计算临时公钥 $C_1 = r P$。然后，计算共享密钥 $K = r Q = r k P$。<br>选取明文 $M$，将其映射到椭圆曲线上的一个点（如果 $M$ 过大，可以分段加密），计算 $C_2 = M + K$。<br>Bob将密文对 $(C_1, C_2)$ 发送给 Alice。</p><p>Alice收到密文对 $(C_1, C_2)$ 后，计算共享密钥 $K = k C_1 = k r P$。使用共享密钥 $K$ 对 $C_2$ 进行解密，计算 $M = C_2 - K$,，从而恢复出明文 $M$。</p><h2><span id="ecc-加密算法实现">ECC 加密算法实现 🥰</span></h2><p>下面是一个简单的 ECC 加密算法的 Python 实现示例：</p><pre><code class="lang-python">from sage.all import EllipticCurve, GF, randint, random_prime# 1. 初始化 ECC 参数# 选取足够大的 p 以容纳消息编码后的整数# 消息&quot;hello_Alice&quot;约88位，编码需要额外空间，故选取 160 位以上素数bits = 160p = random_prime(2^bits, lbound=2^(bits-1))# 随机选取参数 a, b 保证判别式不为 0while True:    a = randint(1, p-1)    b = randint(1, p-1)    if (4*a**3 + 27*b**2) % p != 0:        break# 定义椭圆曲线 y^2 = x^3 + ax + b mod pE = EllipticCurve(GF(p), [a, b])print(f&quot;模数 p (前20位): {str(p)[:20]}...&quot;)print(f&quot;椭圆曲线方程参数 a={a}, b={b}&quot;)G = E.gens()[0] # 基点n = G.order()   # 基点的阶print(f&quot;基点 G: {G.xy()}&quot;)# 2. Key Generation (Alice)# Alice 生成私钥 kk_private = randint(1, n-1)# Alice 计算公钥 Q = k * GQ_public = k_private * Gprint(f&quot;Alice 私钥: {k_private}&quot;)print(f&quot;Alice 公钥: {Q_public.xy()}&quot;)# 3. Encryption (Bob)msg_str = &quot;hello_Alice&quot;# 辅助函数：将消息映射到椭圆曲线上的点 Mdef encode_message(msg, curve):    m = int.from_bytes(msg.encode(), &#39;big&#39;)    scale = 100 # 辅助系数    # 检查消息是否过大导致 x 坐标超过 p    if m * scale &gt;= int(curve.base_ring().order()):        raise ValueError(f&quot;消息过大 (数值={m*scale})，超过了模数 p&quot;)    for j in range(scale):        x = m * scale + j        try:            # lift_x 会尝试找到对应的 y，如果 x^3+ax+b 是二次剩余则成功            P = curve.lift_x(x)            return P        except ValueError:            continue    raise ValueError(&quot;无法将消息编码为曲线上的点&quot;)def decode_message(point, scale=100):    if point.is_zero():        raise ValueError(&quot;解密得到无穷远点&quot;)    x = int(point[0])    m = x // scale    byte_len = (m.bit_length() + 7) // 8    return m.to_bytes(byte_len, &#39;big&#39;).decode()M = encode_message(msg_str, E)print(f&quot;消息 &#39;{msg_str}&#39; 编码后的点 x坐标: {M[0]}&quot;)# Bob 选择随机数 rr = randint(1, n-1)# 计算 C1 = r * GC1 = r * G# 计算共享密钥 K = r * QK_shared = r * Q_public# 计算密文 C2 = M + KC2 = M + K_sharedprint(f&quot;密文 C1 x坐标: {C1[0]}&quot;)print(f&quot;密文 C2 x坐标: {C2[0]}&quot;)# 4. Decryption (Alice)# Alice 计算共享密钥 K = k * C1K_alice = k_private * C1# 恢复消息点 M = C2 - KM_decrypted = C2 - K_alice# 解码消息decrypted_str = decode_message(M_decrypted)print(f&quot;解密结果: {decrypted_str}&quot;)</code></pre><p>简单说明一下辅助系数的意义，椭圆曲线的点必须满足方程 $y^2 = x^3 + ax + b \mod p$，这就意味着：不是所有的 $x$ 都能找到对应的 $y$，只有当 $x^3 + ax + b$ 是模 $p$ 的二次剩余时，才存在对应的点。</p><p>这个时候，把整数 $m$ 乘以一个辅助系数（比如 100），然后在这个基础上加上一个小的偏移量 $j$，形成一个区间，我们在这个区间内寻找一个 $x$，使得 $x^3 + ax + b$ 是二次剩余，从而找到一个对应的点。这种方法可以增加找到对应点的概率，同时也能确保消息能够被正确编码为曲线上的点。</p><p>举个例子：</p><ul><li>假设消息 “hi” 转化成整数 $m$ 是 12345。</li><li>scale 设为 100，那么我们会尝试 $x$ 从 $1234500$ 开始，依次增加 $j$（0, 1, 2, …），直到找到一个 $x$ 使得 $x^3 + ax + b$ 是二次剩余。</li><li>假设 $x = 1234507$ 满足条件，那么我们就将消息 “hi” 编码为点 $(1234507, y)$，其中 $y$ 是通过方程计算得到的。</li><li>解密时， $x = 1234507$，我们通过 $x // scale$ 就能得到原始消息的整数形式 $m = 12345$，然后再转回字符串 “hi”。</li></ul><p>上一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/2005.html">加解密模式分析 - ElGamal 加密算法</a> 👈<br>下一章：<a href="http://lr2006-robot.github.io/myblog.github.io/post/2007.html">加解密模式分析 - 背包加密算法</a> 👈<br>回到开始：<a href="http://lr2006-robot.github.io/myblog.github.io/about/">关于我</a> 👈</p><p>相关链接：<br><a href="https://lr2006-robot.github.io/myblog.github.io/post/1007.html">密码学数学基础 - 群论基础</a> 👈<br><a href="http://lr2006-robot.github.io/myblog.github.io/post/3006.html">密码学算法 - BSGS算法二</a> 👈</p>]]>
    </content>
    <id>https://lr2006-robot.github.io/myblog.github.io/post/2006.html</id>
    <link href="https://lr2006-robot.github.io/myblog.github.io/post/2006.html"/>
    <published>2026-03-02T03:30:00.000Z</published>
    <summary>
      <![CDATA[<p>ECC（Elliptic Curve Cryptography）加密算法，作为非对称加密的现代方案之一，基于椭圆曲线上的离散对数问题的困难性，为数据加密和数字签名提供了高效且安全的解决方案。它在实际应用中被广泛采用，尤其是在资源受限的环境中，如移动设备和物联网设备。</p>]]>
    </summary>
    <title>加解密模式分析 - ECC 加密算法</title>
    <updated>2026-03-16T07:26:23.182Z</updated>
  </entry>
</feed>
