题目

附件内容如下
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秒左右就出结果了~





![[LitCTF 2023]只需要nc一下~-好久不见6](https://i-blog.csdnimg.cn/direct/9b153ae608a5447289269cde1b5e47bf.png)













