在RSA
加密中有这么一类题型,这类题型的特征会给出p异或q
的值。大一的时候在比赛中就经常看到,能搜索到博客,但是看到博客时发现需要用上深度搜索。就没去仔细学下去,也不想直接照抄脚本。
到现在发现这类的深度搜索其实不难,现在就来学习一下。
参考博客:Crypto趣题-剪枝 | 糖醋小鸡块的blog
基础知识
这个其实涉及到深度搜索
,所以我们需要先了解一下这个算法的基本思想。再从算法到RSA
这类题型的剪枝。深度搜索其实就是一个思想,先往其中一种情况深入搜索,当这种情况搜索完发现满足条件,或者搜索的这种情况不满足条件,都会回退一步,往另一种情况搜索。
一般深度搜索
都是采用递归的形式。现在给出深度搜索的一个编程模型。如果要详细了解深度搜索,建议还是去看算法。(注意有的时候并不存在for
循环)
1 2 3 4 5 6 7 8 9 10 11 12 void dfs (int step) { 判断条件条件不满足直接返回; 判断是否符合可能,符合可能就输出后返回; 尝试每种可能 for (i=1 ;i<=n;i++) { 继续下一步 dfs(step+1 ) } 返回 }
解决这个编程问题后,现在就回归到了数学问题,也就是我们要寻找题目中的一些条件,我们需要题目的一些条件:
确定搜索的范围和方式
确定判断条件
确定我们想要的可能
方法1—高位剪枝
方法2—低位剪枝
方法3—首尾剪枝
方法4—特殊剪枝
题型1_已知n和p^q
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from Crypto.Util.number import *p = getPrime(512 ) q = getPrime(512 ) import uuidflag = b'flag{' + str (uuid.uuid4()).encode('utf-8' ) +b'}' m = bytes_to_long(flag) n = p*q e = 65537 c = pow (m,e,n) print ("c =" ,c)print ("e =" ,e)print ("n =" ,n)print ("pxorq=" ,p^q)""" c = 66392304950908878492363710070870951531455545751319561148342067457877398782558949718991790201052777075114258165343597322227423312133870924910093734510606640836588043251561979406140627180220409104790289197279651367750150961609680446724982983006836015046568723770416495789041264015005955613063446430704046342541 e = 65537 n = 108441959144783466019949523499775610834720941613924397283976591029507379036773190737681785553451219224390021126115393524892418373642573838193766663870298419018522866484967864033823662130197017420489851681081822197157446811086780203044600471496440854774133957717397970303786868281088943155384798141132782605963 pxorq= 5365823686462750525081057734877193164653044160825672347607373204558980418392160718932907290867433329472424553303252786076257554667653255425933498955296218 """
p x o r q = p ⊕ q pxorq = p \oplus q
p x o r q = p ⊕ q
确定搜索方式 :这时我们就会发现我们不能直接由pxorq
恢复p、q
,这时我们一位一位的查看,条件如下。这时我们便确定了搜索的方式(条件)
1. 对于这 512 位中的每一位 , 我们都能挑选出一些可能 2. 当 p ⊕ q 的某一位是 1 , 这表明 p 、 q 的那一位为 1 、 0 或者 0 、 1 3. 当 p ⊕ q 的某一位是 0 , 这表明 p 、 q 的那一位为 0 、 0 或者 1 、 1 \begin{array}{l}
1.对于这512位中的每一位,我们都能挑选出一些可能\\
2.当p\oplus q的某一位是1,这表明p、q的那一位为1、0或者0、1\\
3.当p\oplus q的某一位是0,这表明p、q的那一位为0、0或者1、1
\end{array}
1 . 对 于 这 5 1 2 位 中 的 每 一 位 , 我 们 都 能 挑 选 出 一 些 可 能 2 . 当 p ⊕ q 的 某 一 位 是 1 , 这 表 明 p 、 q 的 那 一 位 为 1 、 0 或 者 0 、 1 3 . 当 p ⊕ q 的 某 一 位 是 0 , 这 表 明 p 、 q 的 那 一 位 为 0 、 0 或 者 1 、 1
确定满足条件的情况 :接下来我们还需要确定搜索到什么条件的时候这个值是我们需要寻找的p、q:由于我们搜索是按照一位一位搜索,此时只要我们搜索的位数达到512,这时就很可能是符合题目条件的p、q
确定判断条件 :进行搜索的时候我们需要降低时间复杂度,所以就要设立一些条件,使得我们可以在更少的时间能得到正确的结果。
其实我们在搜索的过程中,基本上可以确定搜索的结果有两个,但是我们其实只要搜索一个q
即可,因为得到q
后,我们可以使用p=n//q
计算得到p
,此时我们可以默认p>q
,也就是当q<p
的时候,我们可以认为是不满足条件的,这个时候可以缩短搜索的范围。
上面的判断条件,只是达到一个锦上添花 的效果(也就是可以缩短时间)。真正的判断条件,我们需要采取极限思维。
当我们从高位搜索的时候,搜索到某一位时,p、q
的前面高位都已知了,现在我们就可以进行极限的判断。我们判断搜索的这一位时,可以将p、q
的剩余位都填充0
或者1
。将他们的相乘的结果与n
比较。
最大的情况就是p、q
剩余位都填充1
,此时如果我们搜索的这一位符合条件,此时应该会有p*q > n
,如果此时p*q < n
,就说明当前我们搜索到的位不符合结果,直接返回即可
最小的情况就是p、q
剩余位都填充0
,此时如果搜索的这一位符合条件,此时应该会有p*q < n
,如果此时p*q > n
,就表明这次搜索就并不满足题目的条件,直接返回即可。
接下来我们就使用这样的剪枝方法,对其进行剪枝。这样就可以解密了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 import libnumimport gmpy2c = 33413207850749511227518404584965321752648197437023083217857527703127099177958468117297469354994985002189524600969289307041505794655427960517170824136053859608423261628244882944505034355584435922708968489131232463522502708804306900444841660801160750466537456598288260419643939235957605370084010443518319880476 e = 65537 n = 69937960689324142377247866549093590080459879074704098933000717642985082246903977856951315583151532583778057166162510030796911163850254255571957322526800143837475133732262049143091225612510157360834204614785780822937994828043052915019016181559335740808052216997021591084236253898991006264268449552394284776071 pxorq= 3582285148305839319026390070977840914669031102024289028546435294136852418894257457449910319953210049437581456372688605696494343886296081252076014395121030 g = bin (pxorq)[2 :].zfill(512 ) x = [] def find (p,q ): l = len (p) tmp1 = p+(512 -l)*'0' tmp2 = p+(512 -l)*'1' tmq1 = q+(512 -l)*'0' tmq2 = q+(512 -l)*'1' if (int (tmp1,2 ) < int (tmq1,2 )): return if (int (tmp1,2 )*int (tmq1,2 ) > n): return elif (int (tmp2,2 )*int (tmq2,2 ) < n): return if (l==512 ): x.append(int (q,2 )) return else : if (g[l] == '1' ): find(p+'1' ,q+'0' ) find(p+'0' ,q+'1' ) else : find(p+'1' ,q+'1' ) find(p+'0' ,q+'0' ) tmp1 = '' tmp2 = '' find(tmp1,tmp2) p = n//x[0 ] phi = (p-1 )*(x[0 ]-1 ) d = gmpy2.invert(e,phi) m = pow (c,d,n) print (libnum.n2s(int (m)))
题型2_已知n和(p^q>>bits)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from Crypto.Util.number import *import uuidflag = "flag{" + str (uuid.uuid4()) + "}" print (flag)m = bytes_to_long(flag.encode()) p = getPrime(1024 ) q = getPrime(1024 ) e = 65537 gift = (p ^ q) >> 400 n = p * q print (gift)print (n)c = pow (m, e, n) print (c)""" gift= 31958645886829465663742610069283430399728839850513083246386448538197604487936046499520648726337487741518220398603291803472699224153248758152580060648773650158416844012882499079656290683984 n= 15619505127163351990488112863141770563681865299685335225044672449726679222174346048307947030511199439211424719776067281155135078932581011042086945379010517674964386503132937564666399232773753915118399621574185937509903051039065814343973923020001058435075609186940135177566113588279789880050765917976777441097617047571689871730580173453909325715236673431821153879912252168063435406632373091016998302026085400209541600123918721359539043369121055076362039338053031901425283160162332806529991986251686578951231643999424466225561068669799631709183429733152004160441220955561218225125773530257724062784558011621107619960331 c= 5265044898684398200513906083356703459876884038316945972785773883929874451990761745669452129171819134620745907830964766040424898002648481947297293242536080478374906513529238083909508902829410210784413646328197032379795305874547377474243240849178748210770629567973487901163276011693717054581720387087565368827406564018666754977477364832065988106365591859733598683183313263133882321735804828207940650807293882383692341076382872724971004960700289433466846333180494480231222493734477317628426618535134595262632520744157878280876244576396437437202332728155506177243283113630607686326730151811058616601645253399574984429323 """
g i f t = ( p ⊕ q ) > > 400 n = p ∗ q e = 65537 \begin{array}{l}
gift = (p\oplus q)>>400\\
n = p*q\\
e = 65537
\end{array}
g i f t = ( p ⊕ q ) > > 4 0 0 n = p ∗ q e = 6 5 5 3 7
首先我们需要查看一下gitf
的位数是不是1024-400=624
,如果不是那么gitf
需要补0,这是因为在最高位异或的时候,一般是两个1异或,此时得到的结果就会为0
,此时最高位就会变小。
1 2 3 4 5 6 7 8 9 10 11 from tqdm import tqdmgift= 31958645886829465663742610069283430399728839850513083246386448538197604487936046499520648726337487741518220398603291803472699224153248758152580060648773650158416844012882499079656290683984 print (int (gift).bit_length())xor_p_q = bin (gift)[2 :].zfill(624 ) print (len (xor_p_q))print (xor_p_q)""" 623 624 011101011000010100010001100011101111011001001111010101011010111010001011011011111011111111001111111000011110101100011010110110111111001110100111000011101010001001100100111010000101001100011111000000111110101111110110011111101100001101100101011110110010000110111010111100011100100110001001101000011110101101100001100100111011101101010100000001000111011010100010010100110011101111100101001111001001101011101010011010101001001100111111001100100110001111101100001010101000100110100110011101110100000111010111100000011111011010110111101001010111001100010010001101111010010100110100111000100011101000001101110111001000110001010000 """
1. 当 p ⊕ q 的某一位是 1 , 这表明 p 、 q 的那一位为 1 、 0 或者 0 、 1 2. 当 p ⊕ q 的某一位是 0 , 这表明 p 、 q 的那一位为 0 、 0 或者 1 、 1 3. p 、 q 剩余位补 0 , 如果还大于 n 则剪去 4. p 、 q 剩余位补 1 , 如果还小于 n 则剪去 5. p < q 剪去 6. 最后达到 624 位的满足条件的 p 值都放入一个列表中 \begin{array}{l}
1.当p\oplus q的某一位是1,这表明p、q的那一位为1、0或者0、1\\
2.当p\oplus q的某一位是0,这表明p、q的那一位为0、0或者1、1\\
3.p、q剩余位补0,如果还大于n则剪去\\
4.p、q剩余位补1,如果还小于n则剪去\\
5.p<q剪去\\
6.最后达到624位的满足条件的p值都放入一个列表中
\end{array}
1 . 当 p ⊕ q 的 某 一 位 是 1 , 这 表 明 p 、 q 的 那 一 位 为 1 、 0 或 者 0 、 1 2 . 当 p ⊕ q 的 某 一 位 是 0 , 这 表 明 p 、 q 的 那 一 位 为 0 、 0 或 者 1 、 1 3 . p 、 q 剩 余 位 补 0 , 如 果 还 大 于 n 则 剪 去 4 . p 、 q 剩 余 位 补 1 , 如 果 还 小 于 n 则 剪 去 5 . p < q 剪 去 6 . 最 后 达 到 6 2 4 位 的 满 足 条 件 的 p 值 都 放 入 一 个 列 表 中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 from tqdm import tqdmgift= 31958645886829465663742610069283430399728839850513083246386448538197604487936046499520648726337487741518220398603291803472699224153248758152580060648773650158416844012882499079656290683984 n= 15619505127163351990488112863141770563681865299685335225044672449726679222174346048307947030511199439211424719776067281155135078932581011042086945379010517674964386503132937564666399232773753915118399621574185937509903051039065814343973923020001058435075609186940135177566113588279789880050765917976777441097617047571689871730580173453909325715236673431821153879912252168063435406632373091016998302026085400209541600123918721359539043369121055076362039338053031901425283160162332806529991986251686578951231643999424466225561068669799631709183429733152004160441220955561218225125773530257724062784558011621107619960331 c= 5265044898684398200513906083356703459876884038316945972785773883929874451990761745669452129171819134620745907830964766040424898002648481947297293242536080478374906513529238083909508902829410210784413646328197032379795305874547377474243240849178748210770629567973487901163276011693717054581720387087565368827406564018666754977477364832065988106365591859733598683183313263133882321735804828207940650807293882383692341076382872724971004960700289433466846333180494480231222493734477317628426618535134595262632520744157878280876244576396437437202332728155506177243283113630607686326730151811058616601645253399574984429323 print (int (gift).bit_length())xor_p_q = bin (gift)[2 :].zfill(624 ) print (len (xor_p_q))print (xor_p_q)p_count = [] bit = 624 print (n_x.bit_length())def find (p,q ): length = len (p) p_min = p + '0' *(1024 -length) p_max = p + '1' *(1024 -length) q_min = q + '0' *(1024 -length) q_max = q + '1' *(1024 -length) if int (p_min,2 ) < int (q_min,2 ): return if int (p_min,2 )*int (q_min,2 ) > n: return if int (p_max,2 )*int (q_max,2 ) < n: return if length == 624 : p_count.append(int (p,2 )) return else : if xor_p_q[length]=="0" : find(p+"0" ,q+"0" ) find(p+"1" ,q+"1" ) else : find(p+"0" ,q+"1" ) find(p+"1" ,q+"0" ) p = "" q = "" find(p,q) print (p_count)
接下来就是使用Coppersmith
攻击求得p的完整位了。最终的exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 from tqdm import tqdmimport libnumgift= 31958645886829465663742610069283430399728839850513083246386448538197604487936046499520648726337487741518220398603291803472699224153248758152580060648773650158416844012882499079656290683984 n= 15619505127163351990488112863141770563681865299685335225044672449726679222174346048307947030511199439211424719776067281155135078932581011042086945379010517674964386503132937564666399232773753915118399621574185937509903051039065814343973923020001058435075609186940135177566113588279789880050765917976777441097617047571689871730580173453909325715236673431821153879912252168063435406632373091016998302026085400209541600123918721359539043369121055076362039338053031901425283160162332806529991986251686578951231643999424466225561068669799631709183429733152004160441220955561218225125773530257724062784558011621107619960331 c= 5265044898684398200513906083356703459876884038316945972785773883929874451990761745669452129171819134620745907830964766040424898002648481947297293242536080478374906513529238083909508902829410210784413646328197032379795305874547377474243240849178748210770629567973487901163276011693717054581720387087565368827406564018666754977477364832065988106365591859733598683183313263133882321735804828207940650807293882383692341076382872724971004960700289433466846333180494480231222493734477317628426618535134595262632520744157878280876244576396437437202332728155506177243283113630607686326730151811058616601645253399574984429323 print (int (gift).bit_length())xor_p_q = bin (gift)[2 :].zfill(624 ) print (len (xor_p_q))print (xor_p_q)p_count = [] bit = 624 e = 65537 print (n_x.bit_length())def find (p,q ): length = len (p) p_min = p + '0' *(1024 -length) p_max = p + '1' *(1024 -length) q_min = q + '0' *(1024 -length) q_max = q + '1' *(1024 -length) if int (p_min,2 ) < int (q_min,2 ): return if int (p_min,2 )*int (q_min,2 ) > n: return if int (p_max,2 )*int (q_max,2 ) < n: return if length == 624 : p_count.append(int (p,2 )) return else : if xor_p_q[length]=="0" : find(p+"0" ,q+"0" ) find(p+"1" ,q+"1" ) else : find(p+"0" ,q+"1" ) find(p+"1" ,q+"0" ) p = "" q = "" find(p,q) PR.<x> = PolynomialRing(Zmod(n)) for i in tqdm(p_count,leave="true" ): f = i*(2 ^400 ) + x f = f.monic() result = f.small_roots(X=2 ^400 ,beta=0.4 ) if result: p = int (f(result[0 ])) q = n//p phi=(p-1 )*(q-1 ) d = inverse_mod(e,phi) m = pow (c,d,n) print (m) flag = libnum.n2s(int (m)) if b'flag' in flag: print (flag) break
题型3_已知n和p^q的低位
该题型与题型3比较相似,都是泄露部分位,但是该题型需要从低位搜索,因为高位是未知的,所以比较的必须是n的低位,所以在与n比较的时候,我们需要对n进行模操作。
题目如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from Crypto.Util.number import *from uuid import uuid4flag = b'flag{' + str (uuid4()).encode() + b'}' p = getPrime(512 ) q = getPrime(512 ) m = bytes_to_long(flag) n = p * q leak = (p ^ q) % 2 **412 e = 65537 c = pow (m,e,n) print (f"n = {n} " )print (f"leak = {leak} " )print ("c=" ,c)""" n = 100067171215784436922726116667829636101749904813139264812522764457839791326558303122342640996637017743078254406667808278471432173199869414158634259101208840509206943398696427874670443166642119337187949569578775835261382262934419063527840553058604243362595487934302573649900855427025107244675713231876070288113 leak = 10543832115168186473789693511591064003599011911243189606838628791917775426849764109796961711367962396680258737918389765791736 c= 80814331464810597768737366158891960237333475871335064565579754621450945292918227366788544042379458885165286365172544344865464054423301878576663429241721492502024564913646743967814247446096208328410179346145050571335354785914338284241561040442534786615044961507831039886737797898408428067352230443860691411097 """
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 from tqdm import tqdmfrom Crypto.Util.number import *import libnumn = 100067171215784436922726116667829636101749904813139264812522764457839791326558303122342640996637017743078254406667808278471432173199869414158634259101208840509206943398696427874670443166642119337187949569578775835261382262934419063527840553058604243362595487934302573649900855427025107244675713231876070288113 leak = 10543832115168186473789693511591064003599011911243189606838628791917775426849764109796961711367962396680258737918389765791736 c= 80814331464810597768737366158891960237333475871335064565579754621450945292918227366788544042379458885165286365172544344865464054423301878576663429241721492502024564913646743967814247446096208328410179346145050571335354785914338284241561040442534786615044961507831039886737797898408428067352230443860691411097 e = 65537 print (leak.bit_length())p_xor_q = bin (leak)[2 :].zfill(412 ) print (p_xor_q)x = [] bit = 412 def find (p,q ): length = len (p) tmp1 = int (p,2 ) if p else 0 tmp2 = int (q,2 ) if q else 0 tmp3 = tmp1*tmp2 % 2 ^length if tmp3 != n % 2 ^length: return if length == 412 : x.append(int (p,2 )) return else : if p_xor_q[bit-1 -length] == '1' : find("1" +p,"0" +q) find("0" +p,"1" +q) else : find("1" +p,"1" +q) find("0" +p,"0" +q) p = "" q = "" find(p,q) for i in tqdm(x,leave="true" ): PR.<y> = PolynomialRing(Zmod(n)) f = y*(2 ^i.bit_length()) + i f = f.monic() result = f.small_roots(X=2 ^(512 -i.bit_length()),beta = 0.4 ) if result: print (result) p = int (result[0 ]*2 ^(i.bit_length()) + i) print (p.bit_length()) q = n//p print (q) phi = (p-1 )*(q-1 ) try : d = inverse_mod(e,phi) m = pow (c,d,n) flag = long_to_bytes(m) if b'flag' in flag: print (flag) break except : continue