nextprime剪枝

题目:

from sympy import *
from Crypto.Util.number import *
from secret import flag

def gen():
    p = getPrime(512)
    q = nextprime(int(str(p)[::-1]))
    a = p&(10**6)
    b = q%(10**6)
    return p, q, a, b

p, q, a, b = gen()
n = p * q
e = 65537
c = pow(flag, e, n)

print('a =', a)
print('b =', b)
print('n =', n)
print('c =', c)

'''
a = 393792
b = 657587
n = 369861662178631957928245949770898273807810914183024109983427665445735262653137205864716795669578458854257293391751966280054435051199001813062804877555180285022533020724584142987601931508644551710279816508694942797383860411279321090090880184911983657558416043011288974643282183209071299542379616927857228873411
c = 71672159337725868237152385281240906653912972455699529924728534161923598107229667985188160316648005550663968313537597184857765083556920840254397949219027087098695062021613177519248117588571374167137534818793726427056016629301262986053118989821488864074425276527050110912787300008659230186841168308795745942580
'''

解题思路:

本题重点在于由gen函数获得n的分解:

def gen():
    p = getPrime(512)
    q = nextprime(int(str(p)[::-1]))
    a = p&(10**6)
    b = q%(10**6)
    return p, q, a, b

注意到pq具有如下关系:

  • qp的十进制逆序的nextprime

同时,题目还给了两个额外信息a,b,分别是:

  • a = p&(10**6)
  • b = q%(10**6)

而这一题的p,q生成方式其实和本篇第一题首尾剪枝有异曲同工之妙,区别在于两点:

  • 一是由二进制逆序换成十进制逆序
  • 二是q在取逆序后还作了一次nextprime操作

那么首先,因为p,q乘积的低六位一定与n的低六位相等,因此我们可以爆破出p的低六位,相应的,我们也就有了q的高六位

而由于给出的额外信息b暴露了q的低六位,并且我们知道,nextprime一般最多也就产生不超过两千的偏移,因此我们可以对偏移量进行爆破,从而得到正确的p的高六位的逆序,爆破的依据为:

  • 对可能的p高六位后面填充全0,与q高六位后面填充全0,乘积应小于n
  • 对可能的p高六位后面填充全9,与q高六位后面填充全9,乘积应大于n

这一步解决后,问题就变成了:已知p,q的高六位和低六位以及p,q的乘积n,要求还原p,q,并且他们之间存在逆序关系,因此用与第一题相似的方法进行深搜,具体如下:

已知条件:

  • p,q的高六位与低六位(十进制位)

搜索方式:

  • 从两端向中间搜索
  • 每一次搜索10x10种可能,分别对应高位部分的一个十进制位和低位部分的一个十进制位

剪枝条件:

  • p,q未搜索到的位全填0,乘积应小于n
  • p,q未搜索到的位全填9,乘积应大于n
  • p,qk位乘积再取低k位,应与n的低k位相同

当然要注意一些细节,比如本题中自己生成几组数据会发现,p,q可能为154155位,因此返回条件在位数不同时会有差别,不过到底是154位还是155位在爆破p高六位时就可以确定了

解答:

from Crypto.Util.number import *
import sys
sys.setrecursionlimit(100000)

def dec(p, q, c, n):
    phi = (p - 1) * (q - 1)
    d = inverse(65537, phi)
    print(long_to_bytes(pow(c, d, n)))

def find(ph, qh, pl, ql):
    # check1
    padding0 = (dec_len - len(ph) - len(pl)) * "0"
    padding9 = (dec_len - len(ph) - len(pl)) * "9"
    if (int(qh + padding0 + ql) * int(ph + padding0 + pl) > n or int(qh + padding9 + ql) * int(ph + padding9 + pl) < n):
        return

    # check2
    mask = 10 ** len(pl)
    if (int(ql) * int(pl) % mask != n % mask):
        return

    # return(因为p、q长度为奇数,才这样,否则可以直接判断)
    if (len(ph + pl) == dec_len - 1):
        for i in range(10):
            if (n % int(ph + str(i) + pl) == 0):
                p = int(ph + str(i) + pl)
                q = n // p
                dec(p, q, c, n)
                exit()

    # search
    for i in range(10):
        for j in range(10):
            find(ph + str(i), qh + str(j), str(j) + pl, str(i) + ql)

a = 393792
b = 657587
n = 369861662178631957928245949770898273807810914183024109983427665445735262653137205864716795669578458854257293391751966280054435051199001813062804877555180285022533020724584142987601931508644551710279816508694942797383860411279321090090880184911983657558416043011288974643282183209071299542379616927857228873411
c = 71672159337725868237152385281240906653912972455699529924728534161923598107229667985188160316648005550663968313537597184857765083556920840254397949219027087098695062021613177519248117588571374167137534818793726427056016629301262986053118989821488864074425276527050110912787300008659230186841168308795745942580
dec_len = 155

# find plow
qlow = str(b)
for i in range(1000000):
    if (i * b % (10 ** 6) == n % (10 ** 6)):
        plow = str(i)

qhigh = plow[::-1]

# find phigh
for i in range(2000):
    phigh = str(b - i)[::-1]
    if (int(phigh + "0" * (dec_len - 6)) * int(qhigh + "0" * (dec_len - 6)) < n and int(
            phigh + "9" * (dec_len - 6)) * int(qhigh + "9" * (dec_len - 6)) > n):
        break

find(phigh, qhigh, plow, qlow)
#7495268d-9987-497e-be6d-2abd1ad91496

做完了其实还可以思考一个问题,我们真的需要q的低六位这个信息吗?其实并不用,因为可以发现深搜速度很快,那么我们不用q的低六位这个信息,而直接爆破q的低四位,做法也是一样的(之所以爆破低四位是因为偏移量最多也就几千,低四位完全足够)

然后脚本需要注意两个细节问题:

  • 要填充至4个字符
  • b-i可能会小于0,这种情况需要对应加上10000变成四位正数

不用信息b的exp如下:

from Crypto.Util.number import *
import sys
from tqdm import *
sys.setrecursionlimit(100000)

def dec(p, q, c, n):
    phi = (p - 1) * (q - 1)
    d = inverse(65537, phi)
    print(long_to_bytes(pow(c, d, n)))

def find(ph, qh, pl, ql):
    # check1
    padding0 = (dec_len - len(ph) - len(pl)) * "0"
    padding9 = (dec_len - len(ph) - len(pl)) * "9"
    if (int(qh + padding0 + ql) * int(ph + padding0 + pl) > n or int(qh + padding9 + ql) * int(ph + padding9 + pl) < n):
        return

    # check2
    mask = 10 ** len(pl)
    if (int(ql) * int(pl) % mask != n % mask):
        return

    # return(因为p、q长度为奇数,才这样,否则可以直接判断)
    if (len(ph + pl) == dec_len - 1):
        for i in range(10):
            if (n % int(ph + str(i) + pl) == 0):
                p = int(ph + str(i) + pl)
                q = n // p
                dec(p, q, c, n)
                exit()

    # search  
    for i in range(10):
        for j in range(10):
            find(ph + str(i), qh + str(j), str(j) + pl, str(i) + ql)

n = 369861662178631957928245949770898273807810914183024109983427665445735262653137205864716795669578458854257293391751966280054435051199001813062804877555180285022533020724584142987601931508644551710279816508694942797383860411279321090090880184911983657558416043011288974643282183209071299542379616927857228873411
c = 71672159337725868237152385281240906653912972455699529924728534161923598107229667985188160316648005550663968313537597184857765083556920840254397949219027087098695062021613177519248117588571374167137534818793726427056016629301262986053118989821488864074425276527050110912787300008659230186841168308795745942580
dec_len = 155

# find plow
for b in trange(10000):
    try:
        qlow = str(b).zfill(4)
        for i in range(10000):
            if (i * b % (10 ** 4) == n % (10 ** 4)):
                plow = str(i).zfill(4)

        qhigh = plow[::-1]

        # find phigh
        for i in range(2000):
            if (b - i >= 0):
                phigh = str(b - i)[::-1].zfill(4)
            else:
                phigh = str(b - i + 10000)[::-1].zfill(4)
            if (int(phigh + "0" * (dec_len - 4)) * int(qhigh + "0" * (dec_len - 4)) < n and int(phigh + "9" * (dec_len - 4)) * int(qhigh + "9" * (dec_len - 4)) > n):
                break

        find(phigh, qhigh, plow, qlow)
    except:
        pass
#76%|███████▌  | 7588/10000 [01:14<01:08, 35.30it/s]
#7495268d-9987-497e-be6d-2abd1ad91496
posted @ 2025-03-11 22:12  sevensnight  阅读(27)  评论(0)    收藏  举报