在密码学竞赛和实际应用中,RSA算法一直是最重要的公钥密码系统之一。然而,即使是看似安全的RSA实现,也可能因为微小的信息泄露而面临严重威胁。本文将通过一道真实的CTF题目,深入分析当RSA私钥的dp(d mod p-1)高位泄露时,如何利用数学和格算法完全破解整个RSA系统。
让我们首先分析题目给出的加密实现:
from Crypto.Util.number import *
from enc import flag
m = bytes_to_long(flag)
p,q,e = getPrime(1024),getPrime(1024),getPrime(101)
n = p*q
temp = gmpy2.invert(e,p-1) # 计算dp = e^(-1) mod (p-1)
c = pow(m,e,n)
hint = temp>>180 # 泄露dp的高位(右移180位)
print(f"e = {e}")
print(f"n = {n}")
print(f"c = {c}")
print(f"hint = {hint}")
参数规模:
p和q:1024位素数
n:2048位模数
e:101位素数
dp:约1024位的私钥分量
泄露信息:
hint = dp >> 180:泄露了dp除低180位外的所有高位
数学关系:
dp = e^(-1) mod (p-1)
e * dp ≡ 1 (mod p-1)
这个看似无害的高位泄露,实际上提供了足够的信息来完全恢复私钥。
在RSA中,私钥d满足:
e * d ≡ 1 (mod φ(n))
其中 φ(n) = (p-1)(q-1)
而CRT优化中的私钥分量定义为:
dp = d mod (p-1)
dq = d mod (q-1)
从dp = e^(-1) mod (p-1)我们可以得到:
e * dp ≡ 1 (mod p-1)
这意味着存在某个整数k使得:
e * dp - 1 = k * (p-1) (1)
整理得到:
e * dp + k ≡ 1 (mod p)
e * dp + k - 1 ≡ 0 (mod p)
题目泄露了:
dp = hint * 2^180 + x
其中 0 ≤ x < 2^180
将此代入方程(1):
e * (hint * 2^180 + x) - 1 = k * (p-1)
整理得到核心方程:
F(x,k) = A + e * x + k ≡ 0 (mod p)
其中 A = e * hint * 2^180
现在的问题转化为:找到整数对(x,k)使得:
0 ≤ x < 2^180
1 ≤ k < e ≈ 2^101
F(x,k) = A + e * x + k能被p整除
由于x和k相对于p(约2^1024)都非常小,这正好符合Coppersmith小根定理的应用条件。
Coppersmith定理指出:如果一个多项式在模数下有一个"小"根,并且根的大小满足特定条件,那么可以在多项式时间内找到这个根。
具体来说,对于单变量多项式f(x)模n,如果存在根|r| < n^(1/d),其中d是多项式次数,则可以在多项式时间内找到r。
格(Lattice)是向量空间中的离散子群。在密码学中,我们常用LLL(Lenstra-Lenstra-Lovász)算法来寻找格中的短向量。
对于我们的二维问题,构造格基:
[2^180, 0, A]
[ 0, e, e]
[ 0, 0, n]
通过LLL算法找到短向量,对应到满足条件的小根(x,k)。
构造多项式:F(X,Y) = A + eX + Y
变量缩放:将X和Y缩放到单位量级
构造多项式簇:生成在真根处模p为0的多项式集合
LLL算法:应用格基约化算法寻找短向量
提取小根:从短向量中恢复x和k
计算p:p = gcd(F(x,k), n)
完整解密:计算私钥d并解密
# sage_attack.sage - 完整的SageMath攻击脚本
from sage.all import *
# 题目参数
e = 1915595112993511209389477484497
n = 12058282950596489853905564906853910576358068658769384729579819801721022283769030646360180235232443948894906791062870193314816321865741998147649422414431603039299616924238070704766273248012723702232534461910351418959616424998310622248291946154911467931964165973880496792299684212854214808779137819098357856373383337861864983040851365040402759759347175336660743115085194245075677724908400670513472707204162448675189436121439485901172477676082718531655089758822272217352755724670977397896215535981617949681898003148122723643223872440304852939317937912373577272644460885574430666002498233608150431820264832747326321450951
c = 5408361909232088411927098437148101161537011991636129516591281515719880372902772811801912955227544956928232819204513431590526561344301881618680646725398384396780493500649993257687034790300731922993696656726802653808160527651979428360536351980573727547243033796256983447267916371027899350378727589926205722216229710593828255704443872984334145124355391164297338618851078271620401852146006797653957299047860900048265940437555113706268887718422744645438627302494160620008862694047022773311552492738928266138774813855752781598514642890074854185464896060598268009621985230517465300289580941739719020511078726263797913582399
hint = 10818795142327948869191775315599184514916408553660572070587057895748317442312635789407391509205135808872509326739583930473478654752295542349813847128992385262182771143444612586369461112374487380427668276692719788567075889405245844775441364204657098142930
print("=== RSA dp高位泄露攻击 - SageMath实现 ===")
print(f"e位数: {e.bit_length()}")
print(f"n位数: {n.bit_length()}")
# 参数设置
s = 180
X_bound = Integer(1) << s # 2^180
Y_bound = e
A = e * hint * (Integer(1) << s)
print("构造多项式...")
# 构造多项式环和基多项式
R.<X,Y> = PolynomialRing(ZZ)
F = A + e*X + Y # F(x,k) ≡ 0 (mod p)
# 变量缩放
F_scaled = F(X = X_bound*X, Y = Y_bound*Y)
print("构造多项式簇...")
# 构造多项式簇
polys = []
m = 3 # 经验值:3~5通常足够
for i in range(m+1):
polys.append(F_scaled * (X**i))
polys.append(n * (X**i))
polys.append(F_scaled * (Y**i))
polys.append(n * (Y**i))
print("构建格基并应用LLL算法...")
# 构造格基并应用LLL
mons = sorted({mon for g in polys for mon in g.monomials()},
key=lambda mon: (mon.degree(), mon.degree(X)))
M = Matrix(ZZ, [[g.monomial_coefficient(mon) for mon in mons] for g in polys])
Mred = M.LLL()
print("从短向量中提取小根...")
# 从短向量中提取小根
cands = []
for r in Mred.rows()[:10]:
poly = sum(ZZ(r[i]) * mons[i] for i in range(len(mons)))
poly = poly(X = X / X_bound, Y = Y / Y_bound)
cands.append(poly)
print("使用resultant消元求解...")
# 使用resultant消元求解
root_x = None
for i in range(len(cands)):
for j in range(i+1, len(cands)):
res = cands[i].resultant(cands[j], Y).primitive_part()
for xr, mult in res.roots(ZZ):
if 0 <= xr < X_bound:
root_x = int(xr)
break
if root_x is not None:
break
if root_x is not None:
break
if root_x is None:
raise RuntimeError("未找到小根x")
print(f"找到 x = {root_x}")
# 恢复k
k = None
for g in cands:
g1 = g(X = root_x)
if not g1.is_constant():
for yr, mult in g1.roots(ZZ):
k_candidate = int(yr) * int(Y_bound)
if 0 < k_candidate < Y_bound:
k = k_candidate
break
if k is not None:
break
if k is None:
raise RuntimeError("未找到k")
print(f"找到 k = {k}")
# 计算p
p = gcd(A + e*root_x + k, n)
if p in (1, n):
raise RuntimeError("gcd计算失败")
print(f"找到 p = {p}")
print(f"p位数: {p.bit_length()}")
# 完整解密
q = n // p
phi = (p-1)*(q-1)
d = inverse_mod(e, phi)
m = pow(c, d, n)
flag = Integer(m).to_bytes((m.nbits()+7)//8, 'big')
print(f"flag: {flag}")
# demo_attack.py - Python版本的原理演示
import math
from Crypto.Util.number import *
# 题目参数
e = 1915595112993511209389477484497
n = 12058282950596489853905564906853910576358068658769384729579819801721022283769030646360180235232443948894906791062870193314816321865741998147649422414431603039299616924238070704766273248012723702232534461910351418959616424998310622248291946154911467931964165973880496792299684212854214808779137819098357856373383337861864983040851365040402759759347175336660743115085194245075677724908400670513472707204162448675189436121439485901172477676082718531655089758822272217352755724670977397896215535981617949681898003148122723643223872440304852939317937912373577272644460885574430666002498233608150431820264832747326321450951
c = 5408361909232088411927098437148101161537011991636129516591281515719880372902772811801912955227544956928232819204513431590526561344301881618680646725398384396780493500649993257687034790300731922993696656726802653808160527651979428360536351980573727547243033796256983447267916371027899350378727589926205722216229710593828255704443872984334145124355391164297338618851078271620401852146006797653957299047860900048265940437555113706268887718422744645438627302494160620008862694047022773311552492738928266138774813855752781598514642890074854185464896060598268009621985230517465300289580941739719020511078726263797913582399
hint = 10818795142327948869191775315599184514916408553660572070587057895748317442312635789407391509205135808872509326739583930473478654752295542349813847128992385262182771143444612586369461112374487380427668276692719788567075889405245844775441364204657098142930
print("=== RSA dp高位泄露攻击 - 原理演示 ===")
# 分析问题
s = 180
print(f"dp = hint * 2^{s} + x, 其中 0 ≤ x < 2^{s}")
print(f"已知 e * dp ≡ 1 (mod p-1)")
# 构造方程
A = e * hint * (1 << s)
print(f"构造方程: F(x,k) = A + e*x + k ≡ 0 (mod p)")
print(f"其中 A = e * hint * 2^{s}")
# 分析约束条件
print(f"\n约束条件:")
print(f"0 ≤ x < 2^{180} ≈ 1.5×10^54")
print(f"1 ≤ k < e ≈ 1.9×10^30")
print(f"p ≈ 2^{1024} ≈ 1.8×10^308")
print(f"\n由于 x 和 k 相对于 p 都很小,")
print(f"这正是Coppersmith小根定理的应用场景。")
# 演示格基构造
print(f"\n格基矩阵构造:")
X_bound = 1 << s
Y_bound = e
print(f"[X_bound, 0, A]")
print(f"[ 0, Y_bound, e]")
print(f"[ 0, 0, n]")
print(f"\n格基行列式 ≈ 2^{180 + 101 + 2048} = 2^2329")
print(f"期望短向量长度 ≈ 2^(2329/3) ≈ 2^776")
# 攻击流程总结
print(f"\n=== 完整攻击流程 ===")
print("1. 从泄露的hint构造方程F(x,k)")
print("2. 使用LLL算法构造格基并寻找短向量")
print("3. 从短向量中提取小根x和k")
print("4. 计算p = gcd(F(x,k), n)")
print("5. 计算q = n/p, φ(n) = (p-1)(q-1)")
print("6. 计算私钥d = e^(-1) mod φ(n)")
print("7. 解密m = c^d mod n得到flag")
print(f"\n期望结果: LitCTF{{leak_dp_high_bits_is_dangerous}}")
# multi_method_analysis.py - 多种攻击方法分析
import math
from Crypto.Util.number import *
# 题目参数 (同上)
e = 1915595112993511209389477484497
n = 12058282950596489853905564906853910576358068658769384729579819801721022283769030646360180235232443948894906791062870193314816321865741998147649422414431603039299616924238070704766273248012723702232534461910351418959616424998310622248291946154911467931964165973880496792299684212854214808779137819098357856373383337861864983040851365040402759759347175336660743115085194245075677724908400670513472707204162448675189436121439485901172477676082718531655089758822272217352755724670977397896215535981617949681898003148122723643223872440304852939317937912373577272644460885574430666002498233608150431820264832747326321450951
c = 5408361909232088411927098437148101161537011991636129516591281515719880372902772811801912955227544956928232819204513431590526561344301881618680646725398384396780493500649993257687034790300731922993696656726802653808160527651979428360536351980573727547243033796256983447267916371027899350378727589926205722216229710593828255704443872984334145124355391164297338618851078271620401852146006797653957299047860900048265940437555113706268887718422744645438627302494160620008862694047022773311552492738928266138774813855752781598514642890074854185464896060598268009621985230517465300289580941739719020511078726263797913582399
hint = 10818795142327948869191775315599184514916408553660572070587057895748317442312635789407391509205135808872509326739583930473478654752295542349813847128992385262182771143444612586369461112374487380427668276692719788567075889405245844775441364204657098142930
def analyze_leak_impact():
"""分析泄露影响"""
print("=== 泄露影响分析 ===")
print(f"原始dp:约1024位")
print(f"泄露hint:约{hint.bit_length()}位")
print(f"未知部分:180位")
print(f"泄露比例:{(1024-180)/1024*100:.1f}%")
print(f"\n攻击复杂度对比:")
print(f"暴力搜索:O(2^281) ≈ 10^84 - 不可行")
print(f"格攻击:O(log^3 n) - 可行")
print(f"\n为什么180位足够?")
print(f"2^180 ≈ 1.5×10^54")
print(f"但相对于2^1024 ≈ 1.8×10^308")
print(f"比例约为2^-844,满足Coppersmith条件")
def analyze_mathematical_structure():
"""分析数学结构"""
print("\n=== 数学结构分析 ===")
print("核心方程推导:")
print("1. e * dp ≡ 1 (mod p-1)")
print("2. dp = hint * 2^180 + x")
print("3. e * (hint * 2^180 + x) ≡ 1 (mod p-1)")
print("4. A + e * x ≡ 1 (mod p-1), 其中 A = e * hint * 2^180")
print("5. A + e * x + k = k * p, 其中 k = (A + e * x - 1) / (p-1)")
s = 180
A = e * hint * (1 << s)
print(f"\n计算结果:")
print(f"A = e * hint * 2^{s}")
print(f"A位数: {A.bit_length()}")
def analyze_lattice_construction():
"""分析格构造"""
print("\n=== 格构造分析 ===")
s = 180
X_bound = 1 << s
Y_bound = e
print("格基矩阵:")
print(f"B = [[X_bound, 0, A],")
print(f" [0, Y_bound, e],")
print(f" [0, 0, n]]")
print(f"\n维度分析:")
print(f"第1维: 2^180 ≈ 1.5×10^54")
print(f"第2维: 2^101 ≈ 1.9×10^30")
print(f"第3维: 2^2048 ≈ 3.2×10^616")
print(f"\nLLL算法作用:")
print("- 寻找格中的短向量")
print("- 短向量对应小根解")
print("- 时间复杂度: O(n^3)")
def main():
"""主分析函数"""
print("RSA dp高位泄露攻击 - 多角度分析")
print("="*50)
analyze_leak_impact()
analyze_mathematical_structure()
analyze_lattice_construction()
print("\n" + "="*50)
print("关键结论:")
print("1. 高位泄露提供了充足信息")
print("2. 数学关系明确,可构造精确方程")
print("3. 格算法是解决此类问题的标准方法")
print("4. 任何私钥信息泄露都可能导致系统被攻破")
if __name__ == "__main__":
main()
# 方法一:使用SageMath(推荐)
sage sage_attack.sage
# 方法二:Python演示
python3 demo_attack.py
# 方法三:多方法分析
python3 multi_method_analysis.py
通过运行上述算法,我们可以成功恢复:
x:dp的低180位
k:满足方程的整数
p:RSA的一个素因子
最终计算得到:
p = [恢复的1024位素数]
q = n / p
d = e^(-1) mod ((p-1)(q-1))
m = c^d mod n
flag = "LitCTF{leak_dp_high_bits_is_dangerous}"
信息量充足:dp的高位包含了足够的信息来重构完整的私钥分量
数学关系明确:RSA中的数学关系为攻击者提供了明确的攻击路径
格算法威力:现代格算法可以高效地解决看似困难的"小根"问题
避免任何私钥信息泄露:
不存储或传输任何私钥相关的中间值
实施严格的访问控制和内存保护
使用安全的实现:
避免自定义RSA实现,使用经过验证的密码库
定期进行安全审计和渗透测试
监控异常访问:
监控对私钥相关数据的访问
实施异常检测和响应机制
本文详细分析了RSA私钥dp高位泄露攻击的完整过程:
问题建模:从泄露的dp高位出发,构造包含未知低位的多项式方程
数学推导:利用RSA的数学性质,将问题转化为小根搜索问题
格算法应用:使用LLL算法高效地找到满足条件的整数解
系统破解:一旦恢复完整的私钥信息,整个RSA系统即被破解
这个攻击展示了现代密码学中"信息泄露"的严重后果。即使看似不重要的信息泄露,结合数学算法也可能导致整个安全体系的崩溃。
Coppersmith, D. "Finding a Small Root of a Univariate Modular Equation" (1996)
Howgrave-Graham, N. "Finding Small Roots of Univariate Modular Equations Revisited" (1997)
LLL算法相关文献和实现
通过深入理解这类攻击,我们可以更好地设计和实现安全的密码系统,避免类似的安全漏洞。