题目
附件内容如下
from Crypto.Util.number import *
from secret import flag
from Cryptodome.PublicKey import RSAp = 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 = 0affected_vector_index = 0# 检查是否影响其他向量for jj in range(ii + 1, BB.dimensions()[0]):if BB[jj, ii] != 0:affected_vectors += 1 # 受影响的向量数量affected_vector_index = jj # 记录受影响的向量索引# 等级:0if 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# 等级:1elif affected_vectors == 1:affected_deeper = Truefor 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 BBdef boneh_durfee(pol, modulus, mm, tt, XX, YY):PR.<u, x, y> = PolynomialRing(ZZ) # 创建多项式环Q = PR.quotient(x * y + 1 - u) # 设定 u = xy + 1polZ = 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) # 添加到单项式列表# 构造格 Bnn = 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 = Falsefor pol1_idx in range(nn - 1):for pol2_idx in range(pol1_idx + 1, nn):# 构造两个多项式PR.<w, z> = PolynomialRing(ZZ)pol1 = pol2 = 0for 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 = Truebreak # 找到后跳出循环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] # 获取解 xreturn solx, soly # 返回解def example():############################################# 随机生成数据##########################################start = time.clock() # 记录开始时间size = 512 # 设置大小length_N = 2 * sizess = 0 # 解决方案计数s = 70 # 指定比特数M = 1 # 实验次数delta = 299 / 1024 # 设置 delta 值# 进行实验for i in range(M):N = 65318835751656706270462803918372182811096669561139853833192009681234356986381524661604904085035483519298788284835759796179173585004238691057332447589167439506386243352011548441011828732868691543989256629925692290088144403935880664585931943707422938295860559274669263630591393175387389387981929391774213395003e = 40738963799387627677248718814260921636491770341278750841486515130562842134876396915106681433734276005332410415984785584884091334455816402584507178231273998519376915363193650533215442952274343814099450187672503465016280527554101223321817109358581483535333734940968961773897326303002987203525415266163296607215c = 28858301095788879003830568949705095027466057921892765321643055383309726369907606901866332954652632036004551285284813812187678314978562231120323470819722044516158810710408496414616381570397632550468546302235826400628265458069027801013266037490695637948933487646553291637002155559023268928639502489285322508063hint1 = 624859718207126357681 # p 的高位hint2 = 810475217500119132950 # q 的高位# 解密指数 d 的最大值m = 7 # 设置格大小t = round(((1 - 2 * delta) * m)) # 根据 Herrmann 和 May 的优化计算X = floor(N**delta) # 计算 XY = floor(N**(1/2) / 2**s) # 计算 Y# 循环范围内进行测试for l in range(int(hint1), int(hint1) + 1):print('\n\n\n l=', l)pM = lp0 = 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) # 计算 AP.<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) # 计算解 dss += 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)) | iif isprime(p_candidate):for j in range(2**70):q_candidate = (hint2 << (512 - 70)) | jif isprime(q_candidate):if p_candidate * q_candidate == n:return p_candidate, q_candidatereturn None, Nonep, 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# 解密密文 cm = 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秒左右就出结果了~