RSA密码学攻击实战:小模数分解攻击完全解析

admin 2026-01-26 14:55:43 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文通过CTF实战解析RSA小模数分解攻击。针对256位密钥过短漏洞,分解模数获取私钥并解密Flag。文章详细阐述了RSA原理与攻击流程,强调生产环境须使用2048位以上密钥及安全填充方案,内容兼具理论深度与实战指导价值。 综合评分: 91 文章分类: CTF,漏洞分析


cover_image

RSA密码学攻击实战:小模数分解攻击完全解析

原创

破镜安全 破镜安全

破镜安全

2026年1月26日 08:00 四川

RSA密码学攻击实战:小模数分解攻击完全解析

前言

RSA是现代密码学中最重要的公钥加密算法之一,广泛应用于网络安全、数字签名等领域。然而,如果RSA的参数选择不当,就会产生严重的安全隐患。本文将通过一道真实的CTF密码学题目,深入剖析RSA的工作原理、安全性基础,以及当密钥长度不足时如何实施攻击。

一、题目分析

1.1 题目文件

拿到题目后,我们发现有两个文件:

pubkey.pem    - RSA公钥文件
flag.enc      - 加密后的flag文件

这是一个典型的RSA加密挑战:攻击者拥有公钥和密文,需要想办法恢复出明文。

让我们首先查看这两个文件的具体内容:

# 查看公钥文件
cat pubkey.pem

输出:

-----BEGIN PUBLIC KEY-----
MDwwDQYJKoZIhvcNAQEBBQADKwAwKAIhAMJjauXD2OQ/+5erCQKPGqxsC/bNPXDr
yigb/+l/vjDdAgMBAAE=
-----END PUBLIC KEY-----

这是一个标准的PEM格式RSA公钥文件。PEM(Privacy-Enhanced Mail)是一种用Base64编码的密钥存储格式,被广泛应用于各种密码学工具中。

查看加密文件:

# 查看加密文件的十六进制内容
xxd flag.enc

输出:

00000000: 6d3e b7df 23ee e1d3 8710 beba 78a0 878e  m>..#.......x...
00000010: 0e9c 65bd 3d08 496d da64 9241 9911 0c79  ..e.=.Im.d.A...y

这是32字节的二进制数据,是RSA加密后的密文。

1.2 初步思考

面对这道题,我们需要思考几个关键问题:

  1. RSA加密的原理是什么? 理解算法才能找到攻击点
  2. 如何从公钥中提取参数? 需要获取n和e的值
  3. RSA的安全性依赖什么? 找到可能的薄弱环节
  4. 如何在没有私钥的情况下解密? 考虑攻击思路

带着这些问题,让我们开始分析。

二、RSA加密算法原理深度解析

2.1 RSA算法的数学基础

RSA算法基于数论中的几个重要定理,主要包括:

1. 欧拉定理

对于互质的整数a和n,有:

a^φ(n) ≡ 1 (mod n)

其中φ(n)是欧拉函数,表示小于等于n且与n互质的正整数个数。

2. 欧拉函数的性质

当n是两个质数p和q的乘积时:

φ(n) = φ(p × q) = (p-1) × (q-1)

这是因为:

  • 对于质数p,所有小于p的正整数都与p互质,所以φ(p) = p-1
  • 欧拉函数具有积性:如果gcd(a,b)=1,则φ(a×b) = φ(a)×φ(b)

3. 模逆元

对于整数e和φ(n),如果gcd(e, φ(n)) = 1,则存在整数d使得:

e × d ≡ 1 (mod φ(n))

这个d称为e在模φ(n)下的乘法逆元。

2.2 RSA密钥生成过程

理解RSA的密钥生成过程对于后续攻击至关重要:

步骤1:选择两个大质数

选择两个足够大的质数 p 和 q
要求:p ≠ q,且都是大质数

步骤2:计算模数

n = p × q

这个n就是RSA加密的模数,它的大小决定了密钥的长度。

步骤3:计算欧拉函数值

φ(n) = (p-1) × (q-1)

步骤4:选择公钥指数

选择整数 e,满足:1 < e < φ(n) 且 gcd(e, φ(n)) = 1
通常选择 e = 65537 = 2^16 + 1

为什么选择65537?

  • 它是质数,容易保证与φ(n)互质
  • 二进制表示只有2个1(10000000000000001),加密运算快
  • 足够大,避免某些特殊攻击

步骤5:计算私钥指数

计算 d,使得 e × d ≡ 1 (mod φ(n))
使用扩展欧几里得算法求解

最终得到:

  • 公钥:(n, e) – 可以公开
  • 私钥:(n, d) – 必须保密

2.3 RSA加密和解密过程

加密过程:

发送方使用接收方的公钥(n, e)对明文M进行加密:

C = M^e mod n

这里使用了模幂运算,将明文转换为密文。

解密过程:

接收方使用自己的私钥(n, d)对密文C进行解密:

M = C^d mod n

为什么这样能正确解密?

这是RSA算法最精妙的地方。让我们来证明:

C^d = (M^e)^d = M^(e×d) (mod n)

因为 e × d ≡ 1 (mod φ(n)),所以可以写成:

e × d = k × φ(n) + 1 &nbsp;(k是某个整数)

代入得:

M^(e×d) = M^(k×φ(n)+1) = M^(k×φ(n)) × M = (M^φ(n))^k × M (mod n)

根据欧拉定理,如果gcd(M, n) = 1,则M^φ(n) ≡ 1 (mod n),因此:

(M^φ(n))^k × M ≡ 1^k × M = M (mod n)

完美恢复明文!这就是RSA能够正确加解密的数学原理。

2.4 RSA安全性的核心

RSA的安全性完全依赖于一个数学难题:大整数因数分解问题

如果攻击者知道公钥(n, e),想要计算出私钥d,需要:

  1. 将n分解为p和q
  2. 计算φ(n) = (p-1)(q-1)
  3. 计算d = e^(-1) mod φ(n)

关键在于第一步:分解n

目前,对于足够大的整数(如2048位以上),不存在高效的分解算法。即使用最强大的计算机和最优秀的算法(如通用数域筛法GNFS),分解一个2048位的整数也需要数百年时间。

这就是RSA安全性的基础:计算复杂度

但如果n不够大会怎样?这就是本题的突破口。

三、解题思路:寻找安全漏洞

3.1 第一步:提取公钥参数

要分析RSA的安全性,首先需要从公钥文件中提取出n和e的具体值。

我们可以使用两种方法:

方法一:使用OpenSSL命令行工具

OpenSSL是一个功能强大的密码学工具包,可以解析和操作各种密钥格式。

openssl rsa -pubin -text -modulus -in&nbsp;pubkey.pem

参数说明:

  • -pubin:表示输入是公钥(而非私钥)
  • -text:以文本形式显示密钥内容
  • -modulus:显示模数的十六进制值
  • -in pubkey.pem:指定输入文件

执行结果:

Public-Key: (256 bit)
Modulus:
&nbsp; &nbsp; 00:c2:63:6a:e5:c3:d8:e4:3f:fb:97:ab:09:02:8f:
&nbsp; &nbsp; 1a:ac:6c:0b:f6:cd:3d:70:eb:ca:28:1b:ff:e9:7f:
&nbsp; &nbsp; be:30:dd
Exponent: 65537 (0x10001)
Modulus=C2636AE5C3D8E43FFB97AB09028F1AAC6C0BF6CD3D70EBCA281BFFE97FBE30DD

从输出中我们提取到关键信息:

  • 密钥长度:256位 ← 这是一个重要发现!
  • e = 65537 (这是标准值)
  • n = 0xC2636AE5C3D8E43FFB97AB09028F1AAC6C0BF6CD3D70EBCA281BFFE97FBE30DD (十六进制)

方法二:使用Python的Crypto库

编写Python脚本来解析:

from&nbsp;Crypto.PublicKey&nbsp;import&nbsp;RSA

# 读取公钥文件
with&nbsp;open('pubkey.pem',&nbsp;'r')&nbsp;as&nbsp;f:
&nbsp; &nbsp; key = f.read()
&nbsp; &nbsp; pubkey = RSA.import_key(key)

# 显示参数
print('n (十进制):', pubkey.n)
print('e:', pubkey.e)
print('n (十六进制):', hex(pubkey.n))
print('密钥位数:', pubkey.n.bit_length(),&nbsp;'bits')

运行结果:

n (十进制): 87924348264132406875276140514499937145050893665602592992418171647042491658461
e: 65537
n (十六进制): 0xc2636ae5c3d8e43ffb97ab09028f1aac6c0bf6cd3d70ebca281bffe97fbe30dd
密钥位数: 256 bits

两种方法得到相同的结果,验证了数据的正确性。

3.2 第二步:安全性评估

现在我们得到了关键参数:

  • n = 87924348264132406875276140514499937145050893665602592992418171647042491658461
  • e = 65537
  • 密钥长度 = 256 bits

让我们评估这个RSA密钥的安全性。

业界推荐的RSA密钥长度标准:

| 密钥长度 | 安全等级 | 说明 | | — | — | — | | < 1024位 | 不安全 | 已被破解,禁止使用 | | 1024位 | 已过时 | 2015年后不再推荐 | | 2048位 | 基本安全 | 当前最低推荐标准 | | 3072位 | 中等安全 | 适用于敏感数据 | | 4096位 | 高安全 | 适用于高机密数据 |

而本题的密钥长度是256位

这意味着什么?

256位整数分解的难度:

对于256位的合数,现代计算机使用通用的分解算法(如椭圆曲线方法ECM或二次筛法QS)可以在几秒到几分钟内完成分解。这完全在可接受的计算范围内。

结论:这个RSA密钥存在严重的安全漏洞,可以通过暴力分解n来破解。

3.3 第三步:攻击策略

明确了攻击思路后,我们的攻击链条如下:

1. 分解n得到p和q
&nbsp; &nbsp;↓
2. 计算φ(n) = (p-1) × (q-1)
&nbsp; &nbsp;↓
3. 计算d = e^(-1) mod φ(n)
&nbsp; &nbsp;↓
4. 构造完整的RSA私钥
&nbsp; &nbsp;↓
5. 使用私钥解密flag.enc
&nbsp; &nbsp;↓
6. 获得flag

接下来,让我们逐步实施这个攻击。

四、攻击实施过程

4.1 第一步:分解模数n

对于256位的整数分解,我们有多种选择:

方法1:使用在线分解数据库

factordb.com 是一个公开的质因数分解数据库,收录了大量已分解的整数。这些数据来自:

  • 学术研究项目
  • CTF比赛中使用的数值
  • 密码学测试数据

访问 http://www.factordb.com/,在搜索框中输入n的值:

87924348264132406875276140514499937145050893665602592992418171647042491658461

查询结果显示n已经被成功分解:

p = 275127860351348928173285174381581152299
q = 319576316814478949870590164193048041239

方法2:使用本地分解工具

如果factordb上没有记录,也可以使用本地工具如:

  • yafu(适合中等大小整数)
  • msieve(通用数域筛法实现)
  • sage(数学软件,包含多种分解算法)

对于256位整数,这些工具都能在合理时间内完成分解。

验证分解结果

无论使用哪种方法,都应该验证结果的正确性:

n =&nbsp;87924348264132406875276140514499937145050893665602592992418171647042491658461
p =&nbsp;275127860351348928173285174381581152299
q =&nbsp;319576316814478949870590164193048041239

# 验证分解
print('验证分解结果:')
print('p * q =', p * q)
print('n &nbsp; &nbsp; =', n)
print('分解正确:', p * q == n)

# 检查p和q的位数
print('\np和q的位数:')
print('p:', p.bit_length(),&nbsp;'bits')
print('q:', q.bit_length(),&nbsp;'bits')

运行结果:

验证分解结果:
p * q = 87924348264132406875276140514499937145050893665602592992418171647042491658461
n &nbsp; &nbsp; = 87924348264132406875276140514499937145050893665602592992418171647042491658461
分解正确: True

p和q的位数:
p: 128 bits
q: 128 bits

可以看到,256位的n是由两个128位的质数相乘得到的。分解验证正确!

4.2 第二步:计算欧拉函数φ(n)

得到p和q后,根据欧拉函数的性质计算φ(n)。

公式回顾:

φ(n) = (p-1) × (q-1)

为什么是这个公式?

这涉及欧拉函数的定义和性质:

  1. 定义:φ(n)表示小于等于n且与n互质的正整数个数
  2. 对于质数p:所有小于p的正整数(1, 2, 3, …, p-1)都与p互质 因此:φ(p) = p – 1
  3. 积性性质:当gcd(a, b) = 1时,φ(a × b) = φ(a) × φ(b) 因为p和q是不同的质数,所以gcd(p, q) = 1 因此:φ(p × q) = φ(p) × φ(q) = (p-1) × (q-1)

计算代码:

p =&nbsp;275127860351348928173285174381581152299
q =&nbsp;319576316814478949870590164193048041239

# 计算欧拉函数
phi_n = (p -&nbsp;1) * (q -&nbsp;1)

print('欧拉函数计算:')
print('φ(n) = (p-1) × (q-1)')
print('φ(n) =', phi_n)

运行结果:

欧拉函数计算:
φ(n) = (p-1) × (q-1)
φ(n) = 87924348264132406875276140514499937144456189488436765114374296308467862464924

4.3 第三步:计算私钥指数d

私钥指数d需要满足以下关系:

e × d ≡ 1 (mod φ(n))

换句话说,d是e在模φ(n)意义下的乘法逆元。

为什么要计算模逆?

回顾RSA的解密原理:

C^d ≡ M (mod n)

这个等式成立的前提是:

e × d ≡ 1 (mod φ(n))

这样才能通过欧拉定理推导出M^(e×d) ≡ M (mod n)。

如何计算模逆?

计算模逆需要使用扩展欧几里得算法(Extended Euclidean Algorithm)。

扩展欧几里得算法不仅能计算gcd(a, b),还能找到整数x和y使得:

a × x + b × y = gcd(a, b)

当gcd(e, φ(n)) = 1时,存在x和y使得:

e × x + φ(n) × y = 1

两边对φ(n)取模:

e × x ≡ 1 (mod φ(n))

这个x就是我们要找的d。

使用Python的gmpy2库计算:

import&nbsp;gmpy2&nbsp;as&nbsp;gp

e =&nbsp;65537
phi_n =&nbsp;87924348264132406875276140514499937144456189488436765114374296308467862464924

# 计算模逆
d = gp.invert(e, phi_n)

print('私钥指数计算:')
print('d = e^(-1) mod φ(n)')
print('d =', d)

# 验证计算是否正确
verification = (e * d) % phi_n
print('\n验证:')
print('(e × d) mod φ(n) =', verification)
print('计算正确:', verification ==&nbsp;1)

运行结果:

私钥指数计算:
d = e^(-1) mod φ(n)
d = 10866948760844599168252082612378495977388271279679231539839049698621994994673

验证:
(e × d) mod φ(n) = 1
计算正确: True

验证通过!我们成功计算出了私钥指数d。

4.4 第四步:构造完整的RSA私钥

现在我们有了RSA的所有关键参数:

  • n = 87924348264132406875276140514499937145050893665602592992418171647042491658461
  • e = 65537
  • d = 10866948760844599168252082612378495977388271279679231539839049698621994994673

可以使用这些参数构造一个完整的RSA私钥对象。

使用Python的Crypto库:

from&nbsp;Crypto.PublicKey&nbsp;import&nbsp;RSA

n =&nbsp;87924348264132406875276140514499937145050893665602592992418171647042491658461
e =&nbsp;65537
d =&nbsp;10866948760844599168252082612378495977388271279679231539839049698621994994673

# 构造RSA密钥对象
# RSA.construct()参数: (n, e, d, p, q)
# 最后一个参数False表示不进行一致性检查(加快速度)
prikey = RSA.construct((n, e, d),&nbsp;False)

print('RSA私钥构造成功')
print('密钥信息:')
print(' &nbsp;n:', prikey.n)
print(' &nbsp;e:', prikey.e)
print(' &nbsp;d:', prikey.d)

# 导出为PEM格式
key_pem = prikey.export_key()
print('\n导出的私钥 (PEM格式):')
print(key_pem.decode('utf-8'))

# 保存到文件
with&nbsp;open('prikey.pem',&nbsp;'wb')&nbsp;as&nbsp;f:
&nbsp; &nbsp; f.write(key_pem)

print('私钥已保存到 prikey.pem')

运行结果:

RSA私钥构造成功
密钥信息:
&nbsp; n: 87924348264132406875276140514499937145050893665602592992418171647042491658461
&nbsp; e: 65537
&nbsp; d: 10866948760844599168252082612378495977388271279679231539839049698621994994673

导出的私钥 (PEM格式):
-----BEGIN RSA PRIVATE KEY-----
MIGqAgEAAiEAwmNq5cPY5D/7l6sJAo8arGwL9s09cOvKKBv/6X++MN0CAwEAAQIg
GAZ5m9RM5kkSK3i0MGDHhvi3f7FZPghC2gY7oNhyi/ECEQDO+7LPfhipjr7cNuPn
w7ArAhEA8Gwo6RyJIrnCNuI1YMCXFwIRAJulRkclqWIHx5pNZIAp9VUCEGjeJLIZ
ek+lSut5m+LJ3p0CEDRBEd7C622/wt1+58xOIfE=
-----END RSA PRIVATE KEY-----

私钥已保存到 prikey.pem

成功!我们从公钥和密文出发,通过分解攻击,完整恢复了RSA私钥。

4.5 第五步:解密flag

有了私钥文件后,就可以使用OpenSSL工具解密flag.enc了。

使用OpenSSL解密:

openssl pkeyutl -decrypt -in&nbsp;flag.enc -inkey prikey.pem

命令参数说明:

  • pkeyutl:公钥算法实用工具(新版本推荐,替代旧的rsautl)
  • -decrypt:执行解密操作
  • -in flag.enc:输入的密文文件
  • -inkey prikey.pem:使用的私钥文件

执行结果:

PCTF{256b_i5_m3dium}

成功获得flag!

也可以使用Python解密:

from&nbsp;Crypto.PublicKey&nbsp;import&nbsp;RSA
from&nbsp;Crypto.Cipher&nbsp;import&nbsp;PKCS1_v1_5

# 读取私钥
with&nbsp;open('prikey.pem',&nbsp;'rb')&nbsp;as&nbsp;f:
&nbsp; &nbsp; prikey = RSA.import_key(f.read())

# 读取密文
with&nbsp;open('flag.enc',&nbsp;'rb')&nbsp;as&nbsp;f:
&nbsp; &nbsp; ciphertext = f.read()

# 创建解密器
cipher = PKCS1_v1_5.new(prikey)

# 解密
plaintext = cipher.decrypt(ciphertext,&nbsp;None)

print('解密结果:', plaintext.decode('utf-8'))

输出:

解密结果: PCTF{256b_i5_m3dium}

两种方法都成功解密,验证了我们的攻击是正确的。

五、完整攻击脚本

为了方便理解和复用,这里提供一个完整的自动化攻击脚本:

#!/usr/bin/env python3
"""
RSA小模数分解攻击脚本
适用于密钥长度不足的RSA加密系统
"""

from&nbsp;Crypto.PublicKey&nbsp;import&nbsp;RSA
from&nbsp;Crypto.Cipher&nbsp;import&nbsp;PKCS1_v1_5
import&nbsp;gmpy2&nbsp;as&nbsp;gp

def&nbsp;parse_public_key(pubkey_file):
&nbsp; &nbsp;&nbsp;"""解析公钥文件,提取n和e"""
&nbsp; &nbsp; print('[*] 步骤1: 解析公钥文件')
&nbsp; &nbsp;&nbsp;with&nbsp;open(pubkey_file,&nbsp;'r')&nbsp;as&nbsp;f:
&nbsp; &nbsp; &nbsp; &nbsp; pubkey = RSA.import_key(f.read())

&nbsp; &nbsp; n = pubkey.n
&nbsp; &nbsp; e = pubkey.e
&nbsp; &nbsp; bits = n.bit_length()

&nbsp; &nbsp; print(f' &nbsp; &nbsp;n =&nbsp;{n}')
&nbsp; &nbsp; print(f' &nbsp; &nbsp;e =&nbsp;{e}')
&nbsp; &nbsp; print(f' &nbsp; &nbsp;密钥长度:&nbsp;{bits}&nbsp;bits')

&nbsp; &nbsp;&nbsp;if&nbsp;bits <&nbsp;1024:
&nbsp; &nbsp; &nbsp; &nbsp; print(f' &nbsp; &nbsp;[!] 警告: 密钥长度仅{bits}位,存在安全隐患')

&nbsp; &nbsp;&nbsp;return&nbsp;n, e

def&nbsp;factorize_n(n):
&nbsp; &nbsp;&nbsp;"""分解模数n为两个质因子"""
&nbsp; &nbsp; print('\n[*] 步骤2: 分解模数n')
&nbsp; &nbsp; print(' &nbsp; &nbsp;提示: 可使用 factordb.com 或本地工具分解')

&nbsp; &nbsp;&nbsp;# 这里使用题目中已知的分解结果
&nbsp; &nbsp;&nbsp;# 实际攻击中需要使用分解工具
&nbsp; &nbsp; p =&nbsp;275127860351348928173285174381581152299
&nbsp; &nbsp; q =&nbsp;319576316814478949870590164193048041239

&nbsp; &nbsp;&nbsp;# 验证分解
&nbsp; &nbsp;&nbsp;if&nbsp;p * q != n:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;ValueError('分解结果错误')

&nbsp; &nbsp; print(f' &nbsp; &nbsp;p =&nbsp;{p}&nbsp;({p.bit_length()}&nbsp;bits)')
&nbsp; &nbsp; print(f' &nbsp; &nbsp;q =&nbsp;{q}&nbsp;({q.bit_length()}&nbsp;bits)')
&nbsp; &nbsp; print(' &nbsp; &nbsp;[+] 分解验证通过')

&nbsp; &nbsp;&nbsp;return&nbsp;p, q

def&nbsp;calculate_private_key(n, e, p, q):
&nbsp; &nbsp;&nbsp;"""计算RSA私钥参数"""
&nbsp; &nbsp; print('\n[*] 步骤3: 计算私钥参数')

&nbsp; &nbsp;&nbsp;# 计算欧拉函数
&nbsp; &nbsp; phi_n = (p -&nbsp;1) * (q -&nbsp;1)
&nbsp; &nbsp; print(f' &nbsp; &nbsp;φ(n) = (p-1) × (q-1) =&nbsp;{phi_n}')

&nbsp; &nbsp;&nbsp;# 计算私钥指数d
&nbsp; &nbsp; d = gp.invert(e, phi_n)
&nbsp; &nbsp; print(f' &nbsp; &nbsp;d = e^(-1) mod φ(n) =&nbsp;{d}')

&nbsp; &nbsp;&nbsp;# 验证
&nbsp; &nbsp;&nbsp;if&nbsp;(e * d) % phi_n !=&nbsp;1:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;ValueError('私钥计算错误')

&nbsp; &nbsp; print(' &nbsp; &nbsp;[+] 私钥计算验证通过')

&nbsp; &nbsp;&nbsp;return&nbsp;d

def&nbsp;construct_private_key(n, e, d, output_file):
&nbsp; &nbsp;&nbsp;"""构造并保存RSA私钥"""
&nbsp; &nbsp; print('\n[*] 步骤4: 构造并保存私钥')

&nbsp; &nbsp; prikey = RSA.construct((n, e, d),&nbsp;False)

&nbsp; &nbsp;&nbsp;with&nbsp;open(output_file,&nbsp;'wb')&nbsp;as&nbsp;f:
&nbsp; &nbsp; &nbsp; &nbsp; key_pem = prikey.export_key()
&nbsp; &nbsp; &nbsp; &nbsp; f.write(key_pem)

&nbsp; &nbsp; print(f' &nbsp; &nbsp;[+] 私钥已保存到&nbsp;{output_file}')

&nbsp; &nbsp;&nbsp;return&nbsp;prikey

def&nbsp;decrypt_flag(prikey, encrypted_file):
&nbsp; &nbsp;&nbsp;"""使用私钥解密flag"""
&nbsp; &nbsp; print('\n[*] 步骤5: 解密flag')

&nbsp; &nbsp;&nbsp;# 读取密文
&nbsp; &nbsp;&nbsp;with&nbsp;open(encrypted_file,&nbsp;'rb')&nbsp;as&nbsp;f:
&nbsp; &nbsp; &nbsp; &nbsp; ciphertext = f.read()

&nbsp; &nbsp;&nbsp;# 创建解密器并解密
&nbsp; &nbsp; cipher = PKCS1_v1_5.new(prikey)
&nbsp; &nbsp; plaintext = cipher.decrypt(ciphertext,&nbsp;None)

&nbsp; &nbsp;&nbsp;if&nbsp;plaintext:
&nbsp; &nbsp; &nbsp; &nbsp; flag = plaintext.decode('utf-8')
&nbsp; &nbsp; &nbsp; &nbsp; print(f' &nbsp; &nbsp;[+] 解密成功!')
&nbsp; &nbsp; &nbsp; &nbsp; print(f' &nbsp; &nbsp;Flag:&nbsp;{flag}')
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;flag
&nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; print(' &nbsp; &nbsp;[-] 解密失败')
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;None

def&nbsp;main():
&nbsp; &nbsp;&nbsp;"""主函数"""
&nbsp; &nbsp; print('='*60)
&nbsp; &nbsp; print('RSA小模数分解攻击工具')
&nbsp; &nbsp; print('='*60)

&nbsp; &nbsp;&nbsp;# 配置文件路径
&nbsp; &nbsp; pubkey_file =&nbsp;'pubkey.pem'
&nbsp; &nbsp; encrypted_file =&nbsp;'flag.enc'
&nbsp; &nbsp; prikey_file =&nbsp;'prikey.pem'

&nbsp; &nbsp;&nbsp;try:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 执行攻击流程
&nbsp; &nbsp; &nbsp; &nbsp; n, e = parse_public_key(pubkey_file)
&nbsp; &nbsp; &nbsp; &nbsp; p, q = factorize_n(n)
&nbsp; &nbsp; &nbsp; &nbsp; d = calculate_private_key(n, e, p, q)
&nbsp; &nbsp; &nbsp; &nbsp; prikey = construct_private_key(n, e, d, prikey_file)
&nbsp; &nbsp; &nbsp; &nbsp; flag = decrypt_flag(prikey, encrypted_file)

&nbsp; &nbsp; &nbsp; &nbsp; print('\n'&nbsp;+&nbsp;'='*60)
&nbsp; &nbsp; &nbsp; &nbsp; print('攻击完成!')
&nbsp; &nbsp; &nbsp; &nbsp; print('='*60)

&nbsp; &nbsp;&nbsp;except&nbsp;Exception&nbsp;as&nbsp;e:
&nbsp; &nbsp; &nbsp; &nbsp; print(f'\n[!] 错误:&nbsp;{e}')
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;import&nbsp;traceback
&nbsp; &nbsp; &nbsp; &nbsp; traceback.print_exc()

if&nbsp;__name__ ==&nbsp;'__main__':
&nbsp; &nbsp; main()

运行这个脚本:

python3 attack.py

输出:

============================================================
RSA小模数分解攻击工具
============================================================
[*] 步骤1: 解析公钥文件
&nbsp; &nbsp; n = 87924348264132406875276140514499937145050893665602592992418171647042491658461
&nbsp; &nbsp; e = 65537
&nbsp; &nbsp; 密钥长度: 256 bits
&nbsp; &nbsp; [!] 警告: 密钥长度仅256位,存在安全隐患

[*] 步骤2: 分解模数n
&nbsp; &nbsp; 提示: 可使用 factordb.com 或本地工具分解
&nbsp; &nbsp; p = 275127860351348928173285174381581152299 (128 bits)
&nbsp; &nbsp; q = 319576316814478949870590164193048041239 (128 bits)
&nbsp; &nbsp; [+] 分解验证通过

[*] 步骤3: 计算私钥参数
&nbsp; &nbsp; φ(n) = (p-1) × (q-1) = 87924348264132406875276140514499937144456189488436765114374296308467862464924
&nbsp; &nbsp; d = e^(-1) mod φ(n) = 10866948760844599168252082612378495977388271279679231539839049698621994994673
&nbsp; &nbsp; [+] 私钥计算验证通过

[*] 步骤4: 构造并保存私钥
&nbsp; &nbsp; [+] 私钥已保存到 prikey.pem

[*] 步骤5: 解密flag
&nbsp; &nbsp; [+] 解密成功!
&nbsp; &nbsp; Flag: PCTF{256b_i5_m3dium}

============================================================
攻击完成!
============================================================

六、技术深入解析

6.1 为什么256位RSA如此脆弱?

让我们从计算复杂度的角度分析:

1. 分解算法的时间复杂度

最先进的通用分解算法是通用数域筛法(GNFS),其时间复杂度为:

O(exp((ln n)^(1/3) × (ln ln n)^(2/3)))

这是一个次指数级别的复杂度,意味着随着n的增大,分解时间呈指数级增长。

2. 不同密钥长度的分解难度

| 密钥长度 | 十进制位数 | 估计分解时间 | 实际案例 | | — | — | — | — | | 256位 | 77位 | 几秒到几分钟 | 普通PC即可 | | 512位 | 154位 | 几小时到几天 | 1999年已破解 | | 768位 | 232位 | 几个月 | 2009年破解,用时2年 | | 1024位 | 309位 | 数年(估算) | 理论上可行 | | 2048位 | 617位 | 数百万年(估算) | 目前安全 |

可以看到,256位RSA的分解难度微乎其微。

3. 为什么factordb能快速返回结果?

factordb是一个众包的分解数据库,包含:

  • 学术界已分解的大整数
  • CTF比赛中使用的整数
  • 各种加密测试数据
  • 志愿者贡献的分解结果

对于256位这样的小整数,几乎都已被预先分解并收录。

6.2 RSA密钥长度的演进历史

1977年:RSA算法发明,最初使用426位密钥

1994年:RSA-129(426位)被破解,用时8个月

1999年:RSA-512被破解,推荐使用1024位

2010年:开始推荐2048位作为最低标准

2015年:NIST要求政府系统使用至少2048位

2020年代:3072位和4096位逐渐成为高安全场景标准

未来展望:随着量子计算的发展,RSA可能需要更长的密钥,或被后量子密码算法替代

6.3 欧拉定理在RSA中的核心作用

让我们更深入地理解欧拉定理:

欧拉定理表述:

如果 gcd(a, n) = 1,则 a^φ(n) ≡ 1 (mod n)

费马小定理(欧拉定理的特殊情况):

如果 p 是质数,gcd(a, p) = 1,则 a^(p-1) ≡ 1 (mod p)

在RSA中的应用:

设计d使得 e × d = k × φ(n) + 1,则:

M^(e×d) = M^(k×φ(n)+1) = M × M^(k×φ(n)) = M × (M^φ(n))^k

由欧拉定理,M^φ(n) ≡ 1 (mod n),所以:

M × (M^φ(n))^k ≡ M × 1^k = M (mod n)

这就是RSA能够正确解密的数学基础。

一个有趣的思考:

如果不知道p和q,能否计算φ(n)?

答案是:非常困难!

事实上,计算φ(n)的难度与分解n的难度是等价的。如果能高效计算φ(n),就能高效分解n,反之亦然。这就是为什么φ(n)必须保密的原因。

6.4 扩展欧几里得算法详解

扩展欧几里得算法是计算模逆的核心工具。让我们看看它是如何工作的:

算法原理:

欧几里得算法(辗转相除法)可以计算最大公约数:

gcd(a, b) = gcd(b, a mod b)

扩展版本不仅计算gcd,还找到整数x和y使得:

a × x + b × y = gcd(a, b)

算法实现:

def&nbsp;extended_gcd(a, b):
&nbsp; &nbsp;&nbsp;"""扩展欧几里得算法"""
&nbsp; &nbsp;&nbsp;if&nbsp;b ==&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;a,&nbsp;1,&nbsp;0
&nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; gcd, x1, y1 = extended_gcd(b, a % b)
&nbsp; &nbsp; &nbsp; &nbsp; x = y1
&nbsp; &nbsp; &nbsp; &nbsp; y = x1 - (a // b) * y1
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;gcd, x, y

def&nbsp;mod_inverse(e, phi_n):
&nbsp; &nbsp;&nbsp;"""计算模逆"""
&nbsp; &nbsp; gcd, x, y = extended_gcd(e, phi_n)
&nbsp; &nbsp;&nbsp;if&nbsp;gcd !=&nbsp;1:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;ValueError('模逆不存在')
&nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;(x % phi_n + phi_n) % phi_n

# 示例
e =&nbsp;65537
phi_n =&nbsp;87924348264132406875276140514499937144456189488436765114374296308467862464924
d = mod_inverse(e, phi_n)
print(f'd =&nbsp;{d}')
print(f'验证: (e × d) mod φ(n) =&nbsp;{(e * d) % phi_n}')

这个算法的时间复杂度是O(log min(a,b)),非常高效。

七、其他RSA攻击方法简介

除了本题使用的小模数分解攻击,RSA还存在其他攻击方式:

7.1 共模攻击(Common Modulus Attack)

场景: 多个用户使用相同的n,但不同的e加密同一消息M

原理: 如果gcd(e1, e2) = 1,可以使用扩展欧几里得算法找到s和t使得:

s × e1 + t × e2 = 1

则:

C1^s × C2^t ≡ M^(s×e1) × M^(t×e2) = M^(s×e1+t×e2) = M^1 = M (mod n)

无需私钥即可解密!

7.2 低加密指数攻击(Low Encryption Exponent Attack)

场景: 使用很小的e(如e=3),且明文M也很小

原理: 如果M^e < n,则加密后的C = M^e在整数域上成立,可以直接开e次方根得到M

防御: 使用OAEP等填充方案,确保M^e > n

7.3 Wiener攻击

场景: 当d相对较小时(d < n^0.25)

原理: 利用连分数逼近e/n,可以在多项式时间内恢复d

防御: 确保d足够大

7.4 选择密文攻击(Chosen Ciphertext Attack)

场景: 攻击者可以让系统解密任意密文(除了目标密文)

原理: 利用RSA的同态性:

(C × r^e)^d = C^d × r = M × r (mod n)

通过选择r,可以间接获取M的信息

防御: 使用OAEP等安全的填充方案

7.5 时序攻击(Timing Attack)

场景: 攻击者可以测量解密操作的时间

原理: 解密时间与私钥d的比特模式相关

防御: 使用固定时间的实现,或加入随机延迟

八、实际应用中的RSA安全建议

8.1 密钥长度选择

  • 最低要求: 2048位(预计安全到2030年)
  • 推荐配置: 3072位(预计安全到2040年)
  • 高安全场景: 4096位

8.2 参数选择建议

1. 公钥指数e的选择:

  • 推荐使用e = 65537(0x10001)
  • 不要使用e = 3(容易受到低指数攻击)
  • 确保gcd(e, φ(n)) = 1

2. 质数p和q的选择:

  • p和q长度接近,都应为密钥长度的一半
  • |p – q|要足够大,避免Fermat分解
  • p-1和q-1都应有大质因子,避免Pollard p-1分解

3. 私钥指数d的选择:

  • 确保d > n^0.29,避免Wiener攻击

8.3 使用安全的填充方案

不要使用:

  • 教科书式RSA(无填充)
  • PKCS#1 v1.5(已知有padding oracle攻击)

推荐使用:

  • OAEP(Optimal Asymmetric Encryption Padding)
  • PSS(对于签名)

8.4 密钥管理

  • 定期更换密钥(建议每1-2年)
  • 使用硬件安全模块(HSM)存储私钥
  • 实施严格的访问控制
  • 及时撤销泄露的密钥

8.5 后量子密码学考虑

随着量子计算的发展,Shor算法可以在多项式时间内分解大整数,RSA将不再安全。

应对措施:

  • 关注NIST后量子密码标准化进程
  • 考虑混合加密方案
  • 提前规划向抗量子算法的迁移

九、总结与思考

9.1 本题的核心教训

通过这道题目,我们深刻认识到:

1. 密钥长度至关重要

密码学中没有”差不多安全”的概念。256位的RSA密钥看似是一个很大的数字,但在现代计算能力面前,它与完全不设防没有区别。

2. 安全标准需要与时俱进

随着计算能力的提升和算法的进步,昨天的安全标准可能成为今天的漏洞。必须持续关注最新的安全建议。

3. 理论与实践的结合

理解RSA的数学原理不仅能帮助我们正确使用它,还能让我们识别和利用其弱点。

9.2 从攻击者到防御者

作为安全研究人员,我们需要具备双重视角:

攻击者视角:

  • 寻找系统的弱点
  • 理解攻击的可行性
  • 评估攻击的成本

防御者视角:

  • 遵循安全最佳实践
  • 使用经过验证的实现
  • 进行定期的安全审计

9.3 RSA的未来

尽管存在各种攻击方式,RSA仍然是当今最重要的公钥密码系统之一。只要参数选择正确、实现得当,它仍然是安全的。

然而,量子计算的威胁日益临近。密码学界正在积极研究后量子密码算法,如:

  • 基于格的密码学(Lattice-based)
  • 基于编码的密码学(Code-based)
  • 基于哈希的签名(Hash-based)
  • 基于多变量的密码学(Multivariate)

未来的密码系统可能会采用混合方案,同时抵御传统和量子攻击。

9.4 延伸学习建议

如果你对密码学感兴趣,建议深入学习:

数学基础:

  • 数论(质数、模运算、欧拉函数)
  • 群论和域论
  • 概率论和信息论

密码学理论:

  • 对称加密算法(AES、ChaCha20)
  • 其他公钥算法(ECC、DH密钥交换)
  • 哈希函数和消息认证码
  • 数字签名和证书体系

实践技能:

  • 密码学库的使用(OpenSSL、Crypto)
  • 密码协议分析(TLS/SSL)
  • 侧信道攻击和防御
  • 安全编程实践

推荐资源:

  • 书籍:《应用密码学》(Bruce Schneier)
  • 课程:Coursera的密码学课程(Dan Boneh)
  • 平台:CryptoHack、CryptoPals挑战
  • 工具:SageMath、yafu、factordb

十、总结

本文通过一道CTF密码学题目,完整展示了RSA小模数分解攻击的全过程:

攻击流程回顾:

解析公钥(n, e) → 发现密钥过短(256位) → 分解n得到(p, q) →
计算φ(n) → 计算私钥d → 构造完整私钥 → 解密获得flag

关键技术点:

  1. RSA算法的数学原理(欧拉定理、模逆运算)
  2. 公钥密钥文件的解析和操作
  3. 大整数因数分解方法
  4. 欧拉函数的计算
  5. 扩展欧几里得算法求模逆
  6. 密码学工具的实际使用

安全启示:

  • 密钥长度直接决定安全性,必须使用足够长的密钥
  • 密码学参数的选择需要严格遵循标准
  • 理解算法原理对于安全使用至关重要
  • 密码学是一个持续演进的领域,需要不断学习

Flag: PCTF{256b_i5_m3dium}

题目的flag直白地指出了问题所在:”256b_is_medium” – 256位是”medium”(中等),讽刺地暗示这样的密钥长度根本不安全,在攻击者眼中只是”中等难度”的练习题。

通过这道题目的学习,我们不仅掌握了RSA攻击的具体技术,更重要的是建立了密码学安全的正确认知。在实际应用中,我们应该:

  • 使用至少2048位的RSA密钥
  • 遵循最新的密码学安全标准
  • 使用经过验证的密码学库
  • 保持对新威胁和新技术的关注

密码学既是一门数学艺术,也是一项实践技能。希望本文能够帮助读者深入理解RSA算法,并在实际工作中正确、安全地使用密码学技术。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:破镜安全 破镜安全 破镜安全《RSA密码学攻击实战:小模数分解攻击完全解析》

今日腊八 网络安全文章

今日腊八

文章总结: 该文档标题为今日腊八,副标题是网络安全与取证研究,标注了2026年1月26日北京的发布信息,主要内容仅为两个图片占位符,全文无实质性技术内容、核心观
评论:0   参与:  0