EVA_log–RSA密码学挑战完整技术解析

admin 2025-12-22 04:18:06 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细解析了一道结合RSA加密和Coppersmith定理的CTF密码学题目,展示了两种Coppersmith攻击应用场景:已知高位的因子分解和部分已知明文攻击。通过构造特定多项式并应用格理论,成功恢复了RSA加密中的隐藏信息。文章强调了RSA安全性的关键要素,包括因子保密性、明文随机性和适当padding的重要性,为密码学学习者提供了深入理解格攻击的实践案例。 综合评分: 100 文章分类: CTF,漏洞分析


cover_image

EVA_log – RSA密码学挑战完整技术解析

原创

破镜安全

破镜安全

2025年12月16日 08:01 四川

EVA_log – RSA密码学挑战完整技术解析

0x01 前言

本文将深入分析一道结合了RSA加密和Coppersmith定理的CTF密码学题目。这道题目巧妙地考察了格攻击(Lattice Attack)在密码分析中的应用,涉及两个不同场景的Coppersmith攻击。通过本文的详细讲解,即使是密码学新手也能理解整个攻击过程的数学原理和实现细节。

0x02 题目分析

2.1 题目文件结构

题目给出了一个损坏的EVA系统日志文件(EVA_log.txt),其中包含了两段加密相关的信息泄露。题目描述提示我们需要从日志中提取有效的flag。

2.2 日志内容解析

打开日志文件,我们可以看到两个独立的部分:

第一部分(第1-11行):

p = next_prime(2^int(round(512)));
q = next_prime(round(pi.n()*p));
N = p*q;
X = [数据损坏]
X_x = q + X;

关键泄露信息:

  • N = 5647619545892256857906003571756549276305071…(一个1026-bit的整数)
  • X_x = 4212187089345063457746391498588929911986622…(一个512-bit的整数)

第二部分(第13-35行):

e = 3
p = next_prime(2^int(round(512)));
q = next_prime(round(pi.n()*p));
N = 1797693134862315907729305190789024733617976...(一个1025-bit的整数)
K = [数据损坏]
Kdigits = K.digits(2)
M = [0]*Kbits + [1]*(length_N-Kbits);
for i in range(len(Kdigits)):
    M[i] = Kdigits[i]
M = ZZ(M, 2)
C = ZmodN(M)^e

关键信息:

  • e = 3(公钥指数)
  • N(另一个模数)
  • C = 1797693134862315907729305190788796226369386…(密文)
  • M的构造代码片段

2.3 攻击目标

通过分析,我们需要:

  1. 第一部分:从N和X_x = q + X恢复未知的X
  2. 第二部分:从C = M^e mod N恢复未知的K

X和K组合起来就是完整的flag。

0x03 密码学基础知识

在开始解题之前,我们需要理解几个核心概念。

3.1 RSA加密基础

RSA是一种非对称加密算法,其安全性基于大整数分解的困难性:

密钥生成:

  1. 选择两个大素数p和q
  2. 计算N = p × q
  3. 选择公钥指数e
  4. 计算私钥指数d,满足ed ≡ 1 (mod φ(N))

加密和解密:

  • 加密:C = M^e mod N
  • 解密:M = C^d mod N

RSA的安全性依赖于:已知N的情况下,很难分解出p和q。

3.2 Coppersmith定理

Coppersmith定理由Don Coppersmith于1996年提出,是密码分析中的一个强大工具。

核心思想:如果我们知道一个多项式方程在模N下有一个”小”的根,那么可以在多项式时间内找到这个根。

数学表述:给定模数N和一元多项式f(x),如果存在根x₀满足:

  • |x₀| < X
  • f(x₀) ≡ 0 (mod b),其中b是N的因子且b ≥ N^β

当X < N^(β²/δ)时(δ是多项式的次数),我们可以高效地找到x₀。

关键参数:

  • β:b相对于N的大小,通常0 < β ≤ 1
  • X:根的上界
  • δ:多项式的次数

典型应用场景:

  1. 部分已知明文攻击(Stereotyped Messages):当我们知道明文的固定部分时
  2. 已知高位的因子分解(Factoring with High Bits Known):当我们知道某个因子的近似值时
  3. 低指数攻击(Low Public Exponent):当RSA的e很小且明文有特殊结构时

3.3 Howgrave-Graham的实现

Coppersmith定理的实际实现依赖于格理论和LLL算法:

格(Lattice):由一组基向量生成的所有整数线性组合构成的点集。

LLL算法:Lenstra-Lenstra-Lovász算法,用于在格中找到相对较短的向量。

Howgrave-Graham方法:

  1. 构造一系列相关多项式
  2. 用这些多项式的系数构造格矩阵
  3. 对格进行LLL规约
  4. 从规约后的格中提取多项式
  5. 对新多项式求根

0x04 第一部分攻击 – 已知高位的因子分解

4.1 问题建模

第一部分给出了:

N = p * q
X_x = q + X

我们已知N和X_x,需要求X。

关键观察:

  • N = p * q是标准RSA模数(两个素数的乘积)
  • X_x = q + X意味着X_x是q的一个”近似值”
  • 如果X相对较小,那么X_x ≈ q

这是一个典型的”已知因子高位”场景。

4.2 数学分析

由于N = p * q,我们有:

p = N / q
p ≈ N / X_x

设q = X_x – X,则:

N = p * (X_x - X)

如果我们构造多项式f(x) = X_x – x,那么f(X) = q,而q是N的一个因子。

在模N下,q满足:

q | N &nbsp;=> &nbsp;q * p = N &nbsp;=> &nbsp;q * p ≡ 0 (mod N)

4.3 Coppersmith攻击实现

步骤1:估算X的大小

观察数据:

  • N ≈ 1026 bits
  • X_x ≈ 512 bits(因为q ≈ √N)
  • X应该是flag的一部分,通常几十到几百bits

我们尝试X的bit长度在100-300之间。

步骤2:构造多项式

ZmodN = Zmod(N)
P.<x> = PolynomialRing(ZmodN)

# q的高位近似:假设X < 2^X_bits
q_high = X_x -&nbsp;2^X_bits
f = q_high + x

这里,我们假设q = q_high + x,其中x是我们要找的调整值。

步骤3:设置Coppersmith参数

beta =&nbsp;0.5&nbsp; &nbsp;&nbsp;# 因为q ≈ √N,所以q ≈ N^0.5
XX =&nbsp;2^X_bits&nbsp;# 搜索范围

beta = 0.5是因为在RSA中,p和q通常大小相近,都约为N的平方根。

步骤4:使用small_roots方法

SageMath提供了内置的small_roots方法实现Coppersmith攻击:

roots = f.small_roots(X=XX, beta=beta)

步骤5:验证和恢复X

if&nbsp;roots:
&nbsp; &nbsp; x0 = int(roots[0])
&nbsp; &nbsp; q = q_high + x0

&nbsp; &nbsp;&nbsp;# 验证q是否是N的因子
&nbsp; &nbsp;&nbsp;if&nbsp;N % q ==&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; p = N // q
&nbsp; &nbsp; &nbsp; &nbsp; X = X_x - q

4.4 实际攻击代码

#!/usr/bin/env sage
from&nbsp;sage.all&nbsp;import&nbsp;*

# 题目数据
N =&nbsp;564761954589225685790600357175654927630507102952458240533488436763726131946307269500484579650081638366712976866408828439929564233188458969845553726574072994626147918173387818392470889235452670343083910939718492107773926516940195616367841541551573034827861600615124882958291113530607621451878269031617025549171
X_x =&nbsp;42121870893450634577463914985889299119866228583627912396576170307551916037987547771260822964333183931568836770305094948800384063683438446157974528104607194

# 定义多项式环
ZmodN = Zmod(N)
P.<x> = PolynomialRing(ZmodN)

# 尝试X_bits = 200
X_bits =&nbsp;200
q_high = X_x -&nbsp;2^X_bits
f = q_high + x

# Coppersmith参数
beta =&nbsp;0.5
XX =&nbsp;2^X_bits

# 攻击
roots = f.small_roots(X=XX, beta=beta)

if&nbsp;roots:
&nbsp; &nbsp; x0 = int(roots[0])
&nbsp; &nbsp; q = q_high + x0

&nbsp; &nbsp;&nbsp;if&nbsp;N % q ==&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; p = N // q
&nbsp; &nbsp; &nbsp; &nbsp; X = X_x - q

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 转换为字符串
&nbsp; &nbsp; &nbsp; &nbsp; X_hex = hex(X)[2:]
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;len(X_hex) %&nbsp;2:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; X_hex =&nbsp;'0'&nbsp;+ X_hex
&nbsp; &nbsp; &nbsp; &nbsp; X_bytes = bytes.fromhex(X_hex)
&nbsp; &nbsp; &nbsp; &nbsp; flag_part1 = X_bytes.decode('utf-8')

&nbsp; &nbsp; &nbsp; &nbsp; print(f"Flag Part 1:&nbsp;{flag_part1}")

4.5 攻击结果

运行上述代码,在约0.06秒内得到结果:

p = 13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084171
q = 42121870893450634577463914985889299119866228583627912396576170307551916037987547771260822964332541009710061289949691591169163556780902047793243036193915001
X = 642921858775480355403357631220506902536398364731491910692193
X (string) = flag{c16cd5d9-3537-4a84-a

成功恢复了flag的第一部分!

4.6 为什么攻击有效?

数学条件满足:

  1. X足够小:X的bit长度(约200 bits)远小于N(1026 bits)
  2. q的近似已知:X_x提供了q的一个很好的近似值
  3. 满足Coppersmith条件:X < N^0.5

安全性启示:

  • 任何关于素因子的信息泄露都可能致命
  • 即使只是近似值也不能泄露
  • “部分信息泄露”在密码学中往往等同于”完全泄露”

0x05 第二部分攻击 – Stereotyped Messages

5.1 M的构造分析

第二部分的代码显示了M的构造方式:

M = [0]*Kbits + [1]*(length_N-Kbits)
for&nbsp;i&nbsp;in&nbsp;range(len(Kdigits)):
&nbsp; &nbsp; M[i] = Kdigits[i]
M = ZZ(M,&nbsp;2)

理解这段代码:

  1. 创建一个长度为length_N的bit数组
  2. 前Kbits个位置初始化为0
  3. 后(length_N-Kbits)个位置初始化为1
  4. 将K的每个bit(从低位到高位)填入前面的位置
  5. 将bit数组转换为整数

数学表示:

如果我们将bit数组从右到左(低位到高位)编号为0, 1, 2, …,那么:

  • 位置0到len(Kdigits)-1:K的各个bit
  • 位置len(Kdigits)到Kbits-1:保持为0(如果len(Kdigits) < Kbits)
  • 位置Kbits到length_N-1:全是1

这等价于:

M = K + (2^Kbits + 2^(Kbits+1) + ... + 2^(length_N-1))
M = K + sum(2^i for i in range(Kbits, length_N))
M = K + (2^length_N - 2^Kbits)

M的二进制结构:

  • 低Kbits位:K的二进制表示
  • 高(length_N-Kbits)位:全是1

5.2 问题建模

已知:

e = 3
C = M^e mod N
M = K + (2^length_N - 2^Kbits)

需要求K。

这是一个典型的”部分已知明文”(Stereotyped Messages)场景:

  • M的高位(known_part = 2^length_N – 2^Kbits)是已知的
  • M的低位(K)是未知的

5.3 参数确定

根据GitHub仓库mimoo/RSA-and-LLL-attacks中的demo1示例,我们发现:

关键参数:

  • length_N = 1024(固定值)
  • Kbits = 200(经过尝试确定)
  • e = 3

这些参数不是随意猜测的,而是通过以下方式确定:

  1. length_N = 1024是demo1中的标准设置
  2. Kbits = 200通过尝试不同值并使用Coppersmith攻击验证

5.4 Coppersmith攻击实现

步骤1:构造多项式

ZmodN = Zmod(N)
P.<x> = PolynomialRing(ZmodN)

known_part =&nbsp;2^length_N -&nbsp;2^Kbits
pol = (known_part + x)^e - C

多项式f(x) = (known_part + x)^e – C在模N下有一个小根x = K。

步骤2:设置Coppersmith参数

根据Coppersmith理论,参数设置为:

dd = pol.degree() &nbsp;# 多项式次数,这里是3
beta =&nbsp;1.0&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# 整个N都是有效的
epsilon = beta /&nbsp;7&nbsp;# 理论上的安全裕度
mm = ceil(beta^2&nbsp;/ (dd * epsilon))
tt = floor(dd * mm * ((1/beta) -&nbsp;1))
XX = ceil(N^((beta^2/dd) - epsilon))

**关键点:**XX的计算不是简单的2^Kbits,而是根据Coppersmith理论优化的值。

步骤3:实现Coppersmith方法

我们需要实现完整的Howgrave-Graham算法:

def&nbsp;coppersmith_howgrave_univariate(pol, modulus, beta, mm, tt, XX):
&nbsp; &nbsp; dd = pol.degree()
&nbsp; &nbsp; nn = dd * mm + tt

&nbsp; &nbsp;&nbsp;# 转换到整数环
&nbsp; &nbsp; polZ = pol.change_ring(ZZ)
&nbsp; &nbsp; x = polZ.parent().gen()

&nbsp; &nbsp;&nbsp;# 构造多项式集合
&nbsp; &nbsp; gg = []
&nbsp; &nbsp;&nbsp;for&nbsp;ii&nbsp;in&nbsp;range(mm):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;jj&nbsp;in&nbsp;range(dd):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gg.append((x * XX)^jj * modulus^(mm - ii) * polZ(x * XX)^ii)
&nbsp; &nbsp;&nbsp;for&nbsp;ii&nbsp;in&nbsp;range(tt):
&nbsp; &nbsp; &nbsp; &nbsp; gg.append((x * XX)^ii * polZ(x * XX)^mm)

&nbsp; &nbsp;&nbsp;# 构造格矩阵
&nbsp; &nbsp; BB = Matrix(ZZ, nn)
&nbsp; &nbsp;&nbsp;for&nbsp;ii&nbsp;in&nbsp;range(nn):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;jj&nbsp;in&nbsp;range(ii+1):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; BB[ii, jj] = gg[ii][jj]

&nbsp; &nbsp;&nbsp;# LLL规约
&nbsp; &nbsp; BB = BB.LLL()

&nbsp; &nbsp;&nbsp;# 转换最短向量为多项式
&nbsp; &nbsp; new_pol =&nbsp;0
&nbsp; &nbsp;&nbsp;for&nbsp;ii&nbsp;in&nbsp;range(nn):
&nbsp; &nbsp; &nbsp; &nbsp; new_pol += x^ii * BB[0, ii] / XX^ii

&nbsp; &nbsp;&nbsp;# 求根
&nbsp; &nbsp; potential_roots = new_pol.roots()

&nbsp; &nbsp;&nbsp;# 验证根
&nbsp; &nbsp; roots = []
&nbsp; &nbsp;&nbsp;for&nbsp;root&nbsp;in&nbsp;potential_roots:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;root[0].is_integer():
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result = polZ(ZZ(root[0]))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;gcd(modulus, result) >= modulus^beta:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; roots.append(ZZ(root[0]))

&nbsp; &nbsp;&nbsp;return&nbsp;roots

步骤4:执行攻击

roots = coppersmith_howgrave_univariate(pol, N, beta, mm, tt, XX)

if&nbsp;roots:
&nbsp; &nbsp;&nbsp;# 注意:找到的根可能需要特殊处理
&nbsp; &nbsp;&nbsp;for&nbsp;root&nbsp;in&nbsp;roots:
&nbsp; &nbsp; &nbsp; &nbsp; K_candidate = ZZ(root)
&nbsp; &nbsp; &nbsp; &nbsp; M = known_part + K_candidate

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 验证
&nbsp; &nbsp; &nbsp; &nbsp; C_verify = power_mod(M, e, N)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;C_verify == C:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 从M中提取K
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; K = M % (2^Kbits)

5.5 实际攻击代码

#!/usr/bin/env sage
from&nbsp;sage.all&nbsp;import&nbsp;*

# 题目数据
e =&nbsp;3
N =&nbsp;179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639477074095512480796227391561801824887394139579933613278628104952355769470429079061808809522886423955917442317693387325171135071792698344550223571732405562649211
C =&nbsp;179769313486231590772930519078879622636938643302225534782830469757312072683043631509515420460371558689969495356234291151237187401183904166468092527888038016548030997882577001018380034032688990171947934428361633212455892663120099715510300757263464214136154730101572820730916893268994804116455440971785992345443

# 参数
length_N =&nbsp;1024
Kbits =&nbsp;200

# 构造多项式
ZmodN = Zmod(N)
P.<x> = PolynomialRing(ZmodN)
pol = (2^length_N -&nbsp;2^Kbits + x)^e - C
dd = pol.degree()

# Coppersmith参数
beta =&nbsp;1
epsilon = beta /&nbsp;7
mm = ceil(beta^2&nbsp;/ (dd * epsilon))
tt = floor(dd * mm * ((1/beta) -&nbsp;1))
XX = ceil(N^((beta^2/dd) - epsilon))

# 攻击
roots = coppersmith_howgrave_univariate(pol, N, beta, mm, tt, XX)

if&nbsp;roots:
&nbsp; &nbsp;&nbsp;for&nbsp;root&nbsp;in&nbsp;roots:
&nbsp; &nbsp; &nbsp; &nbsp; K_from_poly = ZZ(root)
&nbsp; &nbsp; &nbsp; &nbsp; M =&nbsp;2^length_N -&nbsp;2^Kbits + K_from_poly
&nbsp; &nbsp; &nbsp; &nbsp; C_verify = power_mod(M, e, N)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;C_verify == C:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 从M中提取K
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; K = M % (2^Kbits)

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 转换为字符串
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; K_hex = hex(K)[2:]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;len(K_hex) %&nbsp;2:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; K_hex =&nbsp;'0'&nbsp;+ K_hex
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; K_bytes = bytes.fromhex(K_hex)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; flag_part2 = K_bytes.decode('utf-8')

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print(f"Flag Part 2:&nbsp;{flag_part2}")

5.6 攻击结果

运行代码,在约0.01秒内得到结果:

M = 179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827235556412466425596022731124646522419978119605603503266633967229

K = M的低200位 = 33139492925102864336973240956435245131389
K (string) = acb-7d2490cf6b5f}

成功恢复了flag的第二部分!

5.7 技术细节说明

为什么要从M中提取K?

Coppersmith攻击找到的根对应的是多项式f(x) = (known_part + x)^e – C中的x。由于数值计算的原因,这个x可能不直接等于K,而是使得M = known_part + x满足加密关系的值。

通过计算M % (2^Kbits),我们提取M的低Kbits位,这就是K的真实值。

M的二进制验证:

分析M的二进制表示,我们确认:

  • M的bit长度:1024
  • M的高824位:全是1
  • M的低200位:K的二进制表示(混合0和1)

这完全符合M = K + (2^1024 – 2^200)的结构。

0x06 完整解题流程

6.1 综合解题脚本

将两部分整合,得到完整的解题脚本:

#!/usr/bin/env sage
from&nbsp;sage.all&nbsp;import&nbsp;*
import&nbsp;time

def&nbsp;coppersmith_howgrave_univariate(pol, modulus, beta, mm, tt, XX):
&nbsp; &nbsp;&nbsp;"""Coppersmith方法实现"""
&nbsp; &nbsp; dd = pol.degree()
&nbsp; &nbsp; nn = dd * mm + tt

&nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;0&nbsp;< beta <=&nbsp;1:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;ValueError("beta should belongs in (0, 1]")
&nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;pol.is_monic():
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;ArithmeticError("Polynomial must be monic.")

&nbsp; &nbsp; polZ = pol.change_ring(ZZ)
&nbsp; &nbsp; x = polZ.parent().gen()

&nbsp; &nbsp; gg = []
&nbsp; &nbsp;&nbsp;for&nbsp;ii&nbsp;in&nbsp;range(mm):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;jj&nbsp;in&nbsp;range(dd):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gg.append((x * XX)^jj * modulus^(mm - ii) * polZ(x * XX)^ii)
&nbsp; &nbsp;&nbsp;for&nbsp;ii&nbsp;in&nbsp;range(tt):
&nbsp; &nbsp; &nbsp; &nbsp; gg.append((x * XX)^ii * polZ(x * XX)^mm)

&nbsp; &nbsp; BB = Matrix(ZZ, nn)
&nbsp; &nbsp;&nbsp;for&nbsp;ii&nbsp;in&nbsp;range(nn):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;jj&nbsp;in&nbsp;range(ii+1):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; BB[ii, jj] = gg[ii][jj]

&nbsp; &nbsp; BB = BB.LLL()

&nbsp; &nbsp; new_pol =&nbsp;0
&nbsp; &nbsp;&nbsp;for&nbsp;ii&nbsp;in&nbsp;range(nn):
&nbsp; &nbsp; &nbsp; &nbsp; new_pol += x^ii * BB[0, ii] / XX^ii

&nbsp; &nbsp; potential_roots = new_pol.roots()

&nbsp; &nbsp; roots = []
&nbsp; &nbsp;&nbsp;for&nbsp;root&nbsp;in&nbsp;potential_roots:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;root[0].is_integer():
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result = polZ(ZZ(root[0]))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;gcd(modulus, result) >= modulus^beta:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; roots.append(ZZ(root[0]))

&nbsp; &nbsp;&nbsp;return&nbsp;roots

# Part 1: 恢复q和X
N1 =&nbsp;564761954589225685790600357175654927630507102952458240533488436763726131946307269500484579650081638366712976866408828439929564233188458969845553726574072994626147918173387818392470889235452670343083910939718492107773926516940195616367841541551573034827861600615124882958291113530607621451878269031617025549171
X_x =&nbsp;42121870893450634577463914985889299119866228583627912396576170307551916037987547771260822964333183931568836770305094948800384063683438446157974528104607194

ZmodN1 = Zmod(N1)
P1.<x> = PolynomialRing(ZmodN1)
q_high = X_x -&nbsp;2^200
f1 = q_high + x
roots1 = f1.small_roots(X=2^200, beta=0.5)

if&nbsp;roots1:
&nbsp; &nbsp; q = q_high + int(roots1[0])
&nbsp; &nbsp;&nbsp;if&nbsp;N1 % q ==&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; p = N1 // q
&nbsp; &nbsp; &nbsp; &nbsp; X = X_x - q
&nbsp; &nbsp; &nbsp; &nbsp; X_hex = hex(X)[2:]
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;len(X_hex) %&nbsp;2:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; X_hex =&nbsp;'0'&nbsp;+ X_hex
&nbsp; &nbsp; &nbsp; &nbsp; FLAG_PART1 = bytes.fromhex(X_hex).decode('utf-8')

# Part 2: 恢复K
e =&nbsp;3
N2 =&nbsp;179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639477074095512480796227391561801824887394139579933613278628104952355769470429079061808809522886423955917442317693387325171135071792698344550223571732405562649211
C =&nbsp;179769313486231590772930519078879622636938643302225534782830469757312072683043631509515420460371558689969495356234291151237187401183904166468092527888038016548030997882577001018380034032688990171947934428361633212455892663120099715510300757263464214136154730101572820730916893268994804116455440971785992345443

length_N =&nbsp;1024
Kbits =&nbsp;200

ZmodN2 = Zmod(N2)
P2.<x> = PolynomialRing(ZmodN2)
pol2 = (2^length_N -&nbsp;2^Kbits + x)^e - C
dd2 = pol2.degree()

beta2 =&nbsp;1
epsilon2 = beta2 /&nbsp;7
mm2 = ceil(beta2^2&nbsp;/ (dd2 * epsilon2))
tt2 = floor(dd2 * mm2 * ((1/beta2) -&nbsp;1))
XX2 = ceil(N2^((beta2^2/dd2) - epsilon2))

roots2 = coppersmith_howgrave_univariate(pol2, N2, beta2, mm2, tt2, XX2)

if&nbsp;roots2:
&nbsp; &nbsp;&nbsp;for&nbsp;root&nbsp;in&nbsp;roots2:
&nbsp; &nbsp; &nbsp; &nbsp; M =&nbsp;2^length_N -&nbsp;2^Kbits + ZZ(root)
&nbsp; &nbsp; &nbsp; &nbsp; C_verify = power_mod(M, e, N2)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;C_verify == C:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; K = M % (2^Kbits)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; K_hex = hex(K)[2:]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;len(K_hex) %&nbsp;2:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; K_hex =&nbsp;'0'&nbsp;+ K_hex
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; FLAG_PART2 = bytes.fromhex(K_hex).decode('utf-8')

# 输出结果
FULL_FLAG = FLAG_PART1 + FLAG_PART2
print(f"完整FLAG:&nbsp;{FULL_FLAG}")

6.2 运行结果

完整FLAG: flag{c16cd5d9-3537-4a84-aacb-7d2490cf6b5f}

验证成功!

0x07 技术总结与学习要点

7.1 Coppersmith攻击的两种应用

本题展示了Coppersmith定理的两个经典应用场景:

场景1:已知高位的因子分解

  • 适用条件:知道某个因子的近似值
  • 关键参数:beta = 0.5(因子约为N^0.5)
  • 搜索范围:根据误差估计设置

场景2:部分已知明文攻击

  • 适用条件:明文有固定格式或已知部分
  • 关键参数:beta = 1.0(整个N都是有效模数)
  • 搜索范围:根据Coppersmith理论计算最优值

7.2 参数选择的重要性

在Coppersmith攻击中,参数设置直接影响成功率:

beta参数:

  • 反映了我们对问题的理解
  • 第一部分:beta=0.5因为q ≈ √N
  • 第二部分:beta=1.0因为整个N都参与

XX参数(搜索范围):

  • 不是随意设置的
  • 应根据Coppersmith理论计算:XX = ceil(N^((beta^2/dd) – epsilon))
  • 设置不当会导致攻击失败

mm和tt参数:

  • 控制格的维度
  • 影响攻击的成功率和计算时间
  • 通常使用理论上的最优值

7.3 格理论的作用

Coppersmith攻击的核心是格论:

格的构造:

  • 从多项式系数构造格矩阵
  • 格的维度影响算法效率

LLL算法:

  • 找到格中的短向量
  • 短向量对应的多项式包含我们要找的根

理论保证:

  • Howgrave-Graham定理保证了方法的正确性
  • 需要满足特定的数学条件

7.4 RSA安全性启示

这道题揭示了RSA安全性的几个关键点:

1. 因子保密性至关重要

  • 任何关于p或q的信息泄露都可能致命
  • 即使是近似值也不能泄露
  • “部分信息”往往等同于”完全信息”

2. 明文结构的重要性

  • 明文应该是完全随机的
  • 固定格式的明文容易受到攻击
  • 应该使用适当的padding(如OAEP)

3. 低指数的风险

  • e=3虽然加密快,但在某些情况下不够安全
  • 现代RSA推荐使用e=65537
  • 低指数需要配合proper padding使用

4. 侧信道信息泄露

  • 密码系统的实现需要防止信息泄露
  • timing攻击、功耗分析等都可能泄露信息
  • 安全不仅是算法本身,还包括实现

7.5 工具和资源

学习Coppersmith攻击的资源:

  1. SageMath
  • 强大的数学计算软件
  • 内置Coppersmith攻击实现(small_roots方法)
  • 适合密码学研究
  1. GitHub仓库
  • https://github.com/mimoo/RSA-and-LLL-attacks
  • 包含多种RSA攻击的实现示例
  • 是学习格攻击的好资源
  1. 学术论文
  • Coppersmith的原始论文(1996)
  • Howgrave-Graham的改进(1997)
  • 提供了理论基础

7.6 CTF实战技巧

在CTF竞赛中遇到RSA题目时:

1. 信息收集阶段

  • 列出所有已知信息:N, e, C, 其他泄露
  • 列出未知信息:p, q, d, M
  • 分析已知和未知之间的关系

2. 识别攻击类型

  • 小e:考虑低指数攻击
  • 部分信息泄露:考虑Coppersmith
  • 特殊N:尝试factordb、yafu等工具
  • 多个密文:考虑common modulus等攻击

3. 参数调整

  • Coppersmith攻击需要调整参数
  • beta、XX等参数影响成功率
  • 需要多次尝试不同的假设

4. 验证结果

  • 始终验证计算结果的正确性
  • 检查flag格式是否正确
  • 确认数学关系是否成立

5. 参考实现

  • 参考已有的攻击代码
  • 理解原理后修改以适应具体题目
  • 不要盲目套用模板

0x08 深入理解

8.1 为什么Coppersmith攻击有效?

Coppersmith攻击的有效性来自于数学上的深刻洞察:

核心思想:虽然在模N下求解多项式方程很难,但如果根足够”小”,我们可以将模运算问题转化为整数环上的问题。

数学保证:Howgrave-Graham定理:如果多项式g(x)满足:

  1. g(x₀) = 0在整数环Z上成立
  2. ||g(xX)|| < N^β / √n(其中n是格的维度)

那么g(x₀) ≡ 0 (mod N^β)也成立。

关键转换:

  • 我们构造多个相关的多项式
  • 这些多项式在模N^m下都有共同的根x₀
  • 通过格规约找到系数向量很小的多项式
  • 这个多项式在整数环上也有根x₀

8.2 格规约的作用

LLL算法是Coppersmith攻击的核心工具:

格的定义:给定基向量b₁, b₂, …, bₙ,格L是它们所有整数线性组合的集合:

L = {∑ᵢ aᵢbᵢ : aᵢ ∈ Z}

LLL算法的作用:

  • 输入:格的一组基
  • 输出:一组”规约后”的基(向量更短、更正交)
  • 保证:找到的短向量长度有理论上界

在Coppersmith中的应用:

  • 每个多项式对应格中的一个向量
  • LLL找到的短向量对应系数小的多项式
  • 系数小的多项式满足Howgrave-Graham条件

8.3 参数选择的数学原理

参数选择不是随意的,而是基于理论分析:

beta的选择:

  • beta表示b相对于N的大小关系:b ≥ N^β
  • 第一部分:q ≈ √N,所以β=0.5
  • 第二部分:整个N都是模数,所以β=1

epsilon的选择:

  • epsilon是理论中的安全裕度
  • 通常取epsilon = beta/7
  • 保证理论条件能够满足

mm和tt的计算:

mm = ceil(beta^2 / (dd * epsilon))
tt = floor(dd * mm * ((1/beta) - 1))

这些公式来自于Coppersmith理论的优化分析。

XX的计算:

XX = ceil(N^((beta^2/dd) - epsilon))

这是根的理论上界,由Coppersmith定理保证。

8.4 实现中的技术细节

1. 多项式环的选择

P.<x> = PolynomialRing(ZmodN) &nbsp;# 模N的多项式环

初始在模N下构造多项式。

2. 转换到整数环

polZ = pol.change_ring(ZZ) &nbsp;# 转换到整数环

格规约在整数环上进行。

3. 变量替换

(x * XX)^jj &nbsp;# 用XX缩放变量

将根x₀缩放到合适的范围。

4. 格矩阵构造

for&nbsp;ii&nbsp;in&nbsp;range(nn):
&nbsp; &nbsp;&nbsp;for&nbsp;jj&nbsp;in&nbsp;range(ii+1):
&nbsp; &nbsp; &nbsp; &nbsp; BB[ii, jj] = gg[ii][jj]

只填充下三角矩阵(上三角为0),这是格的标准形式。

5. 反向缩放

new_pol += x^ii * BB[0, ii] / XX^ii

从规约后的格向量恢复多项式,并反向缩放。

0x09 结语

本文详细分析了EVA_log这道CTF密码学题目,展示了Coppersmith定理在RSA攻击中的两个经典应用:已知高位的因子分解和部分已知明文攻击。

通过这道题,我们学习了:

  • Coppersmith定理的数学原理和应用场景
  • 格理论和LLL算法在密码分析中的作用
  • 如何根据具体问题设置Coppersmith攻击的参数
  • RSA安全性的关键要素和常见陷阱

密码学是一个深奥而迷人的领域,每一次成功的攻击都加深了我们对安全性的理解。希望本文能够帮助读者在密码学的道路上更进一步。

攻击成功的关键在于:

  • 扎实的数学基础
  • 对理论的深刻理解
  • 实践中的细心调试
  • 持续的学习和探索

最后,需要强调的是:本文所述的攻击技术仅用于学习和研究目的,应在合法授权的范围内使用。密码学研究者应该遵守法律法规和职业道德,为构建更加安全的密码系统而努力。


完整FLAG:flag{c16cd5d9-3537-4a84-aacb-7d2490cf6b5f}

所需工具: SageMath 9.0+

参考资料:

  1. Coppersmith, D. (1996). “Finding a Small Root of a Univariate Modular Equation”
  2. Howgrave-Graham, N. (1997). “Finding Small Roots of Univariate Modular Equations Revisited”
  3. RSA-and-LLL-attacks GitHub Repository: https://github.com/mimoo/RSA-and-LLL-attacks
  4. SageMath Documentation: https://doc.sagemath.org/

作者说明: 本文内容基于对真实CTF题目的完整复现和分析,所有代码均经过实际测试验证。文章旨在帮助密码学学习者理解Coppersmith攻击的原理和实践应用。


查看原文:《EVA_log – RSA密码学挑战完整技术解析》

评论:0   参与:  3