题目
附件内容如下
from Crypto.Util.number import *
from secret import flag
from Cryptodome.PublicKey import RSA
p = getPrime(512)
q = getPrime(512)
n = p * q
d = getPrime(299)
e = inverse(d,(p-1)*(q-1))
m = bytes_to_long(flag)
c = pow(m,e,n)
hint1 = p >> (512-70)
hint2 = q >> (512-70)
print(f"n = {n}")
print(f"e = {e}")
print(f"c = {c}")
print(f"hint1 = {hint1}")
print(f"hint2 = {hint2}")
n = 65318835751656706270462803918372182811096669561139853833192009681234356986381524661604904085035483519298788284835759796179173585004238691057332447589167439506386243352011548441011828732868691543989256629925692290088144403935880664585931943707422938295860559274669263630591393175387389387981929391774213395003
e = 40738963799387627677248718814260921636491770341278750841486515130562842134876396915106681433734276005332410415984785584884091334455816402584507178231273998519376915363193650533215442952274343814099450187672503465016280527554101223321817109358581483535333734940968961773897326303002987203525415266163296607215
c = 28858301095788879003830568949705095027466057921892765321643055383309726369907606901866332954652632036004551285284813812187678314978562231120323470819722044516158810710408496414616381570397632550468546302235826400628265458069027801013266037490695637948933487646553291637002155559023268928639502489285322508063
hint1 = 624859718207126357681
hint2 = 810475217500119132950
除了已知条件,还需要知道p和q,再利用p和q计算d
已知p和q的高比特(70位),可以利用穷举或者boneh-durfee方法进行计算
Boneh-Durfee
Boneh-Durfee 是一种针对 RSA 公钥加密的攻击方法,特别适用于低指数加密的情况。它由密码学家 Dan Boneh 和 Ramarathnam V. Durfee 在 1999 年提出。这种攻击方法主要针对 RSA 加密中的一个特定场景:即密钥的私钥指数 ddd 过小的时候。
攻击背景
在 RSA 加密算法中,公钥由模数 NNN 和公钥指数 eee 构成,而私钥由模数 NNN 和私钥指数 ddd 构成。公钥和私钥满足以下关系:
e⋅d≡1 (mod ϕ(N))e \cdot d \equiv 1 \ (\text{mod} \ \phi(N))e⋅d≡1 (mod ϕ(N))
其中 ϕ(N)\phi(N)ϕ(N) 是 NNN 的欧拉函数。如果私钥指数 ddd 非常小,那么可以通过数学上的技巧推导出 ddd 的可能值。尤其当 d<N0.292d < N^{0.292}d<N0.292 时,Boneh 和 Durfee 攻击可以用 lattice(格)的方法有效地恢复 ddd。
攻击原理
Boneh-Durfee 攻击利用的是 lattice reduction(格约减)算法,具体来说,利用了 LLL 算法(Lenstra-Lenstra-Lovász)来进行维度缩减。这种方法背后的思想是,将 RSA 的密钥方程转换为求解一个二维格上的近似最小解的问题。这些数学操作可以有效缩小搜索范围,从而恢复私钥 ddd。
攻击条件和适用范围
- Boneh-Durfee 攻击适用于当私钥指数 ddd 非常小时的情况,一般要求 d<N0.292d < N^{0.292}d<N0.292。
- 这意味着在某些特定的 RSA 实现中,为了加快加密速度,私钥 ddd 可能被选择得很小,此时可能会受到此类攻击的威胁。
攻击的限制
Boneh-Durfee 攻击虽然强大,但在实际应用中有一定的局限性。首先,它依赖于私钥 ddd 足够小,若 ddd 大于 N0.292N^{0.292}N0.292,则该攻击将变得不可行。此外,这种攻击也依赖于 lattice-based 技术的成功运用,并且计算量较大,需要对 LLL 算法有较深入的理解和高效的实现。
防御方法
为了避免 Boneh-Durfee 攻击,主要建议:
- 使用较大的私钥指数 ddd:尽量避免选择太小的 ddd 值。
- 增加密钥的位数:一般来说,增加 NNN 的位数(如 2048 位或更高)可以大大增强 RSA 的安全性,使得攻击变得不可行。
总之,Boneh-Durfee 攻击是一种非常经典的针对低私钥指数的 RSA 的攻击方法,通过格约减技术可以有效恢复小 ddd 情况下的 RSA 私钥。这类攻击提醒了我们在选择密钥时应当小心,并遵循推荐的密钥大小和指数选择。
payload
import time
time.clock = time.time
# 调试模式标志
debug = True
# 严格模式标志
strict = False
# 仅考虑有用的向量
helpful_only = True
dimension_min = 7 # 如果晶格达到该尺寸,则停止移除无用向量
# 显示有用向量的统计数据
def helpful_vectors(BB, modulus):
nothelpful = 0 # 记录无用向量的数量
for ii in range(BB.dimensions()[0]):
if BB[ii, ii] >= modulus:
nothelpful += 1 # 如果当前向量大于等于模数,则认为是无用向量
print(nothelpful, "/", BB.dimensions()[0], " vectors are not helpful")
return nothelpful # 返回无用向量的数量
# 显示带有 0 和 X 的矩阵
def matrix_overview(BB, bound):
for ii in range(BB.dimensions()[0]):
a = ('%02d ' % ii) # 输出当前向量的索引
for jj in range(BB.dimensions()[1]):
a += '0' if BB[ii,jj] == 0 else 'X' # 用 0 或 X 表示向量中元素
if BB.dimensions()[0] < 60:
a += ' '
if BB[ii, ii] >= bound:
a += '~' # 用 ~ 表示大于界限的向量
# print(a) # 可选,调试输出
# 尝试删除无用的向量
def remove_unhelpful(BB, monomials, bound, current):
# 从当前 = n-1(最后一个向量)开始
if current == -1 or BB.dimensions()[0] <= dimension_min:
return BB # 如果没有向量或达到最小维度,返回原矩阵
# 从后面检查
for ii in range(current, -1, -1):
if BB[ii, ii] >= bound: # 如果当前向量被认为是无用的
affected_vectors = 0
affected_vector_index = 0
# 检查是否影响其他向量
for jj in range(ii + 1, BB.dimensions()[0]):
if BB[jj, ii] != 0:
affected_vectors += 1 # 受影响的向量数量
affected_vector_index = jj # 记录受影响的向量索引
# 等级:0
if affected_vectors == 0: # 如果没有向量受到影响
# print("* removing unhelpful vector", ii)
BB = BB.delete_columns([ii]) # 删除当前向量
BB = BB.delete_rows([ii])
monomials.pop(ii) # 从单项式列表中删除
BB = remove_unhelpful(BB, monomials, bound, ii - 1) # 递归调用
return BB
# 等级:1
elif affected_vectors == 1:
affected_deeper = True
for kk in range(affected_vector_index + 1, BB.dimensions()[0]):
if BB[kk, affected_vector_index] != 0:
affected_deeper = False # 如果有其他向量受到影响,则不删除
if affected_deeper and abs(bound - BB[affected_vector_index, affected_vector_index]) < abs(bound - BB[ii, ii]):
# 如果没有其他向量受到影响且当前向量更无用
# print("* removing unhelpful vectors", ii, "and", affected_vector_index)
BB = BB.delete_columns([affected_vector_index, ii])
BB = BB.delete_rows([affected_vector_index, ii])
monomials.pop(affected_vector_index)
monomials.pop(ii)
BB = remove_unhelpful(BB, monomials, bound, ii - 1) # 递归调用
return BB
# 如果没有向量被删除,返回原矩阵
return BB
def boneh_durfee(pol, modulus, mm, tt, XX, YY):
PR.<u, x, y> = PolynomialRing(ZZ) # 创建多项式环
Q = PR.quotient(x * y + 1 - u) # 设定 u = xy + 1
polZ = Q(pol).lift() # 提升多项式
UU = XX * YY + 1 # 计算 UU
# x-移位
gg = []
for kk in range(mm + 1):
for ii in range(mm - kk + 1):
xshift = x^ii * modulus^(mm - kk) * polZ(u, x, y)^kk # 生成 x 的移位
gg.append(xshift)
gg.sort() # 排序
# 单项式 x 移位列表
monomials = []
for polynomial in gg:
for monomial in polynomial.monomials(): # 获取单项式
if monomial not in monomials: # 如果单项式不在列表中
monomials.append(monomial)
monomials.sort() # 排序
# y-移位
for jj in range(1, tt + 1):
for kk in range(floor(mm / tt) * jj, mm + 1):
yshift = y^jj * polZ(u, x, y)^kk * modulus^(mm - kk) # 生成 y 的移位
yshift = Q(yshift).lift() # 提升
gg.append(yshift) # 添加到移位列表
# 单项式 y 移位列表
for jj in range(1, tt + 1):
for kk in range(floor(mm / tt) * jj, mm + 1):
monomials.append(u^kk * y^jj) # 添加到单项式列表
# 构造格 B
nn = len(monomials) # 单项式数量
BB = Matrix(ZZ, nn) # 初始化格矩阵
for ii in range(nn):
BB[ii, 0] = gg[ii](0, 0, 0) # 设置第一列
for jj in range(1, ii + 1):
if monomials[jj] in gg[ii].monomials():
BB[ii, jj] = gg[ii].monomial_coefficient(monomials[jj]) * monomials[jj](UU, XX, YY)
# 如果只考虑有用的向量,尝试移除无用向量
if helpful_only:
BB = remove_unhelpful(BB, monomials, modulus^mm, nn - 1)
nn = BB.dimensions()[0] # 更新维度
if nn == 0:
print("failure") # 如果没有向量,返回失败
return 0, 0
# 检查向量是否有帮助
if debug:
helpful_vectors(BB, modulus^mm)
# 检查行列式是否正确界定
det = BB.det()
bound = modulus^(mm * nn)
if det >= bound:
print("We do not have det < bound. Solutions might not be found.")
print("Try with higher m and t.")
if debug:
diff = (log(det) - log(bound)) / log(2)
print("size det(L) - size e^(m*n) = ", floor(diff))
if strict:
return -1, -1 # 如果严格模式并且行列式不符合约束,返回失败
else:
print("det(L) < e^(m*n) (good! If a solution exists < N^delta, it will be found)")
# 显示格基
if debug:
matrix_overview(BB, modulus^mm)
# LLL算法优化格基
if debug:
print("optimizing basis of the lattice via LLL, this can take a long time")
BB = BB.LLL() # 使用LLL算法进行优化
if debug:
print("LLL is done!")
# 查找线性无关的向量
if debug:
print("在格中寻找线性无关向量")
found_polynomials = False
for pol1_idx in range(nn - 1):
for pol2_idx in range(pol1_idx + 1, nn):
# 构造两个多项式
PR.<w, z> = PolynomialRing(ZZ)
pol1 = pol2 = 0
for jj in range(nn):
pol1 += monomials[jj](w * z + 1, w, z) * BB[pol1_idx, jj] / monomials[jj](UU, XX, YY)
pol2 += monomials[jj](w * z + 1, w, z) * BB[pol2_idx, jj] / monomials[jj](UU, XX, YY)
# 结果
PR.<q> = PolynomialRing(ZZ)
rr = pol1.resultant(pol2) # 计算结果
if rr.is_zero() or rr.monomials() == [1]:
continue # 如果结果为零或为常数,继续
else:
print("found them, using vectors", pol1_idx, "and", pol2_idx)
found_polynomials = True
break # 找到后跳出循环
if found_polynomials:
break # 如果找到多项式,跳出外循环
if not found_polynomials:
print("no independant vectors could be found. This should very rarely happen...")
return 0, 0 # 如果没有找到独立向量,返回失败
rr = rr(q, q) # 使用 q 替代变量
# 获取解
soly = rr.roots() # 计算根
if len(soly) == 0:
print("Your prediction (delta) is too small")
return 0, 0 # 如果没有根,返回失败
soly = soly[0][0] # 选择第一个根
ss = pol1(q, soly) # 计算另一个多项式
solx = ss.roots()[0][0] # 获取解 x
return solx, soly # 返回解
def example():
############################################
# 随机生成数据
##########################################
start = time.clock() # 记录开始时间
size = 512 # 设置大小
length_N = 2 * size
ss = 0 # 解决方案计数
s = 70 # 指定比特数
M = 1 # 实验次数
delta = 299 / 1024 # 设置 delta 值
# 进行实验
for i in range(M):
N = 65318835751656706270462803918372182811096669561139853833192009681234356986381524661604904085035483519298788284835759796179173585004238691057332447589167439506386243352011548441011828732868691543989256629925692290088144403935880664585931943707422938295860559274669263630591393175387389387981929391774213395003
e = 40738963799387627677248718814260921636491770341278750841486515130562842134876396915106681433734276005332410415984785584884091334455816402584507178231273998519376915363193650533215442952274343814099450187672503465016280527554101223321817109358581483535333734940968961773897326303002987203525415266163296607215
c = 28858301095788879003830568949705095027466057921892765321643055383309726369907606901866332954652632036004551285284813812187678314978562231120323470819722044516158810710408496414616381570397632550468546302235826400628265458069027801013266037490695637948933487646553291637002155559023268928639502489285322508063
hint1 = 624859718207126357681 # p 的高位
hint2 = 810475217500119132950 # q 的高位
# 解密指数 d 的最大值
m = 7 # 设置格大小
t = round(((1 - 2 * delta) * m)) # 根据 Herrmann 和 May 的优化计算
X = floor(N**delta) # 计算 X
Y = floor(N**(1/2) / 2**s) # 计算 Y
# 循环范围内进行测试
for l in range(int(hint1), int(hint1) + 1):
print('\n\n\n l=', l)
pM = l
p0 = pM * 2**(size - s) + 2**(size - s) - 1 # 计算 p 的值
q0 = N / p0 # 计算 q 的值
qM = int(q0 / 2**(size - s)) # 计算 q 的高位
A = N + 1 - pM * 2**(size - s) - qM * 2**(size - s) # 计算 A
P.<x, y> = PolynomialRing(ZZ) # 创建多项式环
pol = 1 + x * (A + y) # 构建多项式方程
# 运行 Boneh-Durfee 算法
if debug:
start_time = time.time() # 记录开始时间
solx, soly = boneh_durfee(pol, e, m, t, X, Y) # 调用算法
if solx > 0:
d_sol = int(pol(solx, soly) / e) # 计算解 d
ss += 1 # 增加解决方案计数
print("=== solution found ===")
print("p的高比特为:", l)
print("q的高比特为:", qM)
print("d =", d_sol) # 输出解
if debug:
print("=== %s seconds ===" % (time.time() - start_time))
print("ss =", ss) # 输出解决方案计数
end = time.clock() # 记录结束时间
print('Running time: %s Seconds' % (end - start)) # 输出运行时间
if __name__ == "__main__":
example() # 执行示例
利用sagemath运行可得p,q,d
已知d,计算RSA解密公式 m=cdmod nm = c^{d} \mod nm=cdmodn
Payload
from Crypto.Util.number import getPrime, inverse, long_to_bytes
from sympy import isprime
# 已知的参数
n = 65318835751656706270462803918372182811096669561139853833192009681234356986381524661604904085035483519298788284835759796179173585004238691057332447589167439506386243352011548441011828732868691543989256629925692290088144403935880664585931943707422938295860559274669263630591393175387389387981929391774213395003
e = 40738963799387627677248718814260921636491770341278750841486515130562842134876396915106681433734276005332410415984785584884091334455816402584507178231273998519376915363193650533215442952274343814099450187672503465016280527554101223321817109358581483535333734940968961773897326303002987203525415266163296607215
c = 28858301095788879003830568949705095027466057921892765321643055383309726369907606901866332954652632036004551285284813812187678314978562231120323470819722044516158810710408496414616381570397632550468546302235826400628265458069027801013266037490695637948933487646553291637002155559023268928639502489285322508063
hint1 = 624859718207126357681
hint2 = 810475217500119132950
d = 514966421261428616864174659198108787824325455855002618826560538514908088230254475149863519
# 根据 hint1 和 hint2 生成 p 和 q
def recover_p_q(hint1, hint2):
# p 的可能值
for i in range(2**70): # 70 位可以变化的部分
p_candidate = (hint1 << (512 - 70)) | i
if isprime(p_candidate):
for j in range(2**70):
q_candidate = (hint2 << (512 - 70)) | j
if isprime(q_candidate):
if p_candidate * q_candidate == n:
return p_candidate, q_candidate
return None, None
p, q = recover_p_q(hint1, hint2)
if p and q:
print(f"Recovered p: {p}")
print(f"Recovered q: {q}")
# 验证 d 是否正确
phi_n = (p - 1) * (q - 1)
assert (d * e) % phi_n == 1
# 解密密文 c
m = pow(c, d, n)
plaintext = long_to_bytes(m)
print(f"Recovered plaintext: {plaintext.decode('utf-8')}")
else:
print("Failed to recover p and q.")
运行之后得到
有原题Σ(⊙▽⊙"a
Crypto01: 领航杯原题。参考: https://www.cnblogs.com/mumuhhh/p/17789591.html
可以在sage notebook上跑,也可以命令行: sage high_p_q_rsa.sage跑, 大概 10秒左右就出结果了~