RSA Partial Factorization Writeup
题目分析
题目附件是一个 RSA 加密脚本,给出了模数 n、密文 c,以及素数 p 的高位(MSB):
try:
from Crypto.Util.number import getPrime, bytes_to_long
except:
from Cryptodome.Util.number import getPrime, bytes_to_long
import os
flag = os.getenv("GZCTF_FLAG") or "flag{fake_flag_for_testing}"
p = getPrime(512)
q = getPrime(512)
n = p * q
shift = 245
msb = p >> shift
print(f"{n = }")
print(f"{msb = }")
m = bytes_to_long(flag.encode())
e = 65537
c = pow(m, e, n)
print(f"{c = }")
"""
n = 115875396476311164878844415823841082350245930740649724159462505854041600357779384584606867590201120411413035622363685173256457589742586979659611488861300362133287019970031565288521931188121591696006529955353016686922392181478379060749179686147719197947169391942218136659087912749851657253128124861897041685071
msb = 163000585837295888314881725391367744404519415473103980454100723937398095645225036
c = 104425648636226597292001259237932911115107208342809231621163974971158265898985628696950498323584366182895045547922752200783547330765300848978348283927246463324879212916154416662729625318954521503935904635905660154612105895432496397360099609520406239812612326934889422747296854795726003883512669423580785040551
"""
已知信息:
n = p * q,其中p和q都是 512 位素数msb = p >> 245,即p的高 267 位(512 - 245 = 267)- 加密指数
e = 65537 - 密文
c
解题思路
本题考察的是 RSA 部分因子分解(Partial Factorization)攻击。RSA 的安全性依赖于大整数分解的困难性,但当攻击者知道其中一个素数 p 的部分信息时,可以利用 Coppersmith 方法恢复完整的 p。
Coppersmith 方法简介
Coppersmith 方法是一种基于格基规约(Lattice Basis Reduction)的算法,用于求解模多项式的小根。其核心思想是:给定一个模数 N 和一个多项式 f(x),如果存在一个较小的根 x0 满足 f(x0) ≡ 0 (mod p),其中 p 是 N 的一个因子,那么可以通过 Coppersmith 方法找到这个根。
关于 Coppersmith 方法的详细讲解,可以参考我之前制作的 Bilibili 视频。
问题建模
已知 p 的高位 msb,设 p 的低位为 x,则有:
p = msb * 2^245 + x
其中 0 ≤ x < 2^245。
由于 p 整除 n,我们有:
f(x) = msb * 2^245 + x ≡ 0 (mod p)
这是一个模 p 的多项式方程,且 x 相对于 p 很小(x < 2^245,而 p ≈ 2^512)。这正是 Coppersmith 方法适用的场景。
方法一:使用 SageMath 的 small_roots 函数
SageMath 提供了内置的 small_roots 函数来实现 Coppersmith 攻击:
# run with sage --python
from sage.all import Zmod, Integer
from Crypto.Util.number import long_to_bytes
# given numbers
n = 115875396476311164878844415823841082350245930740649724159462505854041600357779384584606867590201120411413035622363685173256457589742586979659611488861300362133287019970031565288521931188121591696006529955353016686922392181478379060749179686147719197947169391942218136659087912749851657253128124861897041685071
msb = 163000585837295888314881725391367744404519415473103980454100723937398095645225036
c = 104425648636226597292001259237932911115107208342809231621163974971158265898985628696950498323584366182895045547922752200783547330765300848978348283927246463324879212916154416662729625318954521503935904635905660154612105895432496397360099609520406239812612326934889422747296854795726003883512669423580785040551
e = 65537
shift = 245
# https://latticehacks.cr.yp.to/rsa.html
# modulo n
R = Zmod(n)["x"]
x = R.gens()[0]
# find small root of equation a + x = 0 (mod p)
# x = the missing LSB bits
# Coppersmith attack
f = msb * (Integer(1) << shift) + x
# small root bound: |x| < 2^shift
# returns small roots of this polynomial modulo some factor b of N
# where b >= N^{beta}, which is p
beta = 0.49
# smaller epsilon finds larger root, but takes longer time
roots = f.small_roots(X=1 << shift, beta=beta, epsilon=0.02)
if len(roots) >= 1:
# roots[0] is x, compute p
p = int(roots[0]) + msb * (2**shift)
q = n // p
assert p * q == n
d = pow(e, -1, (p - 1) * (q - 1))
m = pow(c, d, n)
print(long_to_bytes(m))
方法二:使用 cuso 库
cuso 是一个实现了多变量 Coppersmith 方法的 Python 库,可以处理更复杂的同余方程问题:
# run with sage --python
from sage.all import var
import cuso
from Crypto.Util.number import long_to_bytes
# given numbers
n = 115875396476311164878844415823841082350245930740649724159462505854041600357779384584606867590201120411413035622363685173256457589742586979659611488861300362133287019970031565288521931188121591696006529955353016686922392181478379060749179686147719197947169391942218136659087912749851657253128124861897041685071
msb = 163000585837295888314881725391367744404519415473103980454100723937398095645225036
c = 104425648636226597292001259237932911115107208342809231621163974971158265898985628696950498323584366182895045547922752200783547330765300848978348283927246463324879212916154416662729625318954521503935904635905660154612105895432496397360099609520406239812612326934889422747296854795726003883512669423580785040551
e = 65537
shift = 245
# adapted from https://github.com/keeganryan/cuso/blob/main/examples/rsa_partial_factorization.py
x = var("x")
relations = [msb * 2**shift + x]
p_len = (n.bit_length() + 1) // 2
bounds = {
x: (0, 1 << shift),
}
roots = cuso.find_small_roots(
relations=relations,
bounds=bounds,
modulus="p",
modulus_multiple=n,
modulus_lower_bound=1 << (p_len - 1),
modulus_upper_bound=1 << p_len,
)
p = int(roots[0]["p"])
q = n // p
assert p * q == n
d = pow(e, -1, (p - 1) * (q - 1))
m = pow(c, d, n)
print(long_to_bytes(m))
总结
本题展示了 RSA 部分因子分解攻击的实际应用。当攻击者知道素数 p 的部分信息时,可以利用 Coppersmith 方法恢复完整的 p,从而破解 RSA 加密。这种攻击在实际中具有重要意义,提醒我们在实现 RSA 时要确保随机数的生成过程不会泄露任何部分信息。