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
注意到p
和q
具有如下关系:
q
是p
的十进制逆序的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
,q
低k
位乘积再取低k
位,应与n
的低k
位相同
当然要注意一些细节,比如本题中自己生成几组数据会发现,p
,q
可能为154
或155
位,因此返回条件在位数不同时会有差别,不过到底是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