只解出一个题,还是得多加努力啊。最近事情也不少,后几个题目要复现的话估计也只能等到寒假了。
Double RSA 0
题目描述:
1 | A baby RSA challenge. |
题目:
1 | #!/usr/bin/env python3 |
这次比赛基于Double RSA系列一共出了四道题,这一道题是这一系列的第一道目。相比于另外几道应该涉及到Lattice的题,这一道题目其实就是一个trick。
简单描述一下题目内容,首先是类的定义:
- 首先定义一个LCG类,其具体实现与熟知的那种LCG有一点区别,不过由于这一道题目中会给出LCG的所有参数,所以只需要对该类做调用就可以求出之后的所有state,因此并不需要过于关注LCG的具体实现。
- 然后定义一个RSA类,他具有正常RSA的生成参数、加密解密等一切功能,此外还有一些额外的功能与限制:
- 会经过check来保证解密指数安全(无法用低解密指数等攻击方式)
- 定义了一个noisy_enc函数,每次调用该函数,会利用LCG对加密指数e做一个更新,并且再加一个noise后对指定明文做RSA加密。具体来说,令noise_i表示LCG的一个状态,则一次noisy_enc加密过程如下:
类定义完毕后正式进入题目,任务如下:
- 通过proof
- 发送给靶机端一个RSA私钥对(p,q)
- 靶机端生成一个LCG类对象lcg
- 靶机用lcg对象生成一个RSA类对象alice,同样的,用lcg对象以及刚才传入的RSA私钥对(p,q)生成一个RSA类对象bob
- 然后,靶机生成一个secret后,用alice的公钥对其进行加密得到secret_ct,并发送给我们alice的公钥e和n,并且发送给我们lcg的全部参数
到这一步我们先整理一下我们的已知信息:
- alice:已知e,n,未知p,q
- bob:已知p,q,n,未知e
- lcg:已知当前全部参数,可以计算出以后的所有state
然后继续回到题目,接下来进行到核心的交互部分,我们有总计不超过七次机会来进行如下几种交互:
- 首先是第一种交互,我们可以输入一段明文的十六进制pt,靶机会返回给我们如下信息:
1 | ct = bob.noisy_enc(alice.noisy_enc(pt)) |
- 输入0可以退出第一种交互,退出后靶机会给我们发送:
1 | bob.noisy_enc(secrets_ct) |
- 发送完毕后,靶机重新生成lcg用于重新初始化bob的RSA类对象,当然同样给我们发送了lcg的全部参数。然后进入第二种交互,每次交互我们可以输入一段密文ct(不能重复),然后靶机会返回给我们:
1 | bob.noisy_enc(alice.dec(ct)) |
- 依然是输入0退出交互,退出后我们可以输入secret值,如果与靶机的secret相等,则可以获得flag
没想到光是说一遍题目内容就写了这么多。。。任务的确还是有一点复杂,可以多理几遍把这个过程搞清楚。
那么接下来进入解题环节,一言以蔽之——我们的目标是利用开始发送给靶机的RSA私钥对(p,q),以及后面最多七次的交互,求出secret值。而我们对于secret的了解在一开始只有:
1 | bob.noisy_enc(alice.enc(secret)) |
也就是:
我们要想办法解开这两层加密,为此我们需要解出外层的加密指数值。而想要解出这一层我们需要利用两种交互,可以发现,第一层交互,我们可以输入pt得到:
然后这里就是本题目的核心trick,内层的加密值看上去我们并不知道,但是实际上我们如果令:
那么当内层加密指数为奇数时,其内层的加密结果其实就是:
所以内层的加密结果我们就可以脱掉,得到:
此时,对于这个等式,我们有:
所以求解指数变成了一个离散对数问题,而组成nb的私钥p、q是我们可以控制的,所以为了使这个离散对数易求,我们可以选择p-1光滑的素数来组成nb,这样我们就可以求解离散对数问题得到以下两个值:
然后crt组合就能得到:
然后再爆破几位就可以得到数量级为1024bit的 了。而由于lcg全部参数都已知,因此我们可以知道任意noise的值,因此我们就可以知道第一层交互每一次的加密指数值,所以对于退出第一层交互后提供给我们的信息:
1 | bob.noisy_enc(secrets_ct) |
我们就可以解开这一层bob.noisy_enc,得到secrets_ct,其实也就得到了:
然后,我们进入第二层交互,把这个值发送给靶机端,靶机端会用alice的私钥解密该信息后,再用更新后的bob对象去noisyenc一遍。而这里的trick其实和第一层是一模一样的,因此我们像第一层交互一样如法炮制,就可以得到更新后的bob的加密指数,然后由于我们有bob的私钥p,q,因此可以RSA解密得到secret,之后发送给靶机就可以了。
当然,这个解法需要满足的条件不少:首先就是trick那一步需要加密指数为奇数,所以把noise看作纯随机数的话,两次利用trick均成功的概率就只有1/4,同时我们最后解密还需要e和phi互素(当然不互素也可以用AMM解,但是限时20s,时间很紧,并且会加大脚本工作量,所以不如反复重连靶机),就进一步降低了成功概率。但是实际上最多十几次一般也就可以成功一次了。
exp:
1 | from Crypto.Util.number import * |
*Double RSA1
(坑啊,有一个小区别没注意到,直接导致了这题没做出来(╯°Д°)╯︵ ┻━┻)
回到题目吧,这一题与Double RSA0有以下几个区别:
- LCG类的参数大小有区别,上一题是1024bit的数量级,这一题是512bit的数量级(就是这一点没注意到,实在没想到会在这里设置一个可操作点)
- 最大交互次数从7提高到了70
- 不再提供alice的公钥对(na,ea)
- 第一种交互不再提供lcg的初始状态
- 第二种交互完全不提供lcg的任何参数
而我们要实现的目标与第一题是一样的,利用两种交互去得到secret的值并发送给靶机从而得到flag,但是这一题未知的参数有点多,又该怎么样转换思路实现呢?
首先,发送给靶机的bob的私钥对(p,q)肯定是不变的,依然要选择光滑数便于离散对数求解。
然后就是第一种交互,这里我们没有alice的公钥对(na,ea),但是由第一题的trick可知(其实第一题有公钥的情况下完全不需要trick,我当时也是为了后面的题目考虑了一下),我们完全可以传-1给靶机,那么经alice的扰动加密后得到的只有1或An-1两种。而如果是1,那么bob扰动加密得到的结果肯定也是1,因此如果靶机返回的结果不是1的话,那么我们得到的肯定是以下的值:
我们可以多次交互,因此我们可以得到多组上述结果,然后接下来就是如何利用这一结果。我们首先把整个指数看作一个数xi:
然后这一题与上一题的区别就体现出来了:xi是一个512bit数量级的值而不再是1024bit,也就是说他在模phinb下是一个小量。为了利用这一点,我们选取模phinb下的一个生成元g,那么就有:
代回到刚才的式子中,就有:
这个时候就可以把底数g摘掉,而由于g是生成元,因此指数上群阶为phinb,所以有:
这个时候应该发现,上面的式子中,zi和phinb我们已知,但是y和xi我们并不知道,但是我们知道的是,y在每一组式子中都不变,且xi是一个小量,因此这里完全可以用HNP解出y和xi,具体实现如下,我们移项得到:
展开为等式:
因此我们可以构造以下格:
这个格具有如下关系:
然后配平一下数量级就可以规约出所有xi,进一步就能得到y,也就能得到na的值。
然后在第一次交互结束后,bob会将扰动加密后的secret_ct发送给我们,我们需要解开这一层扰动加密才能获得alice公钥加密后的secret值,为此我们需要知道这一次bob扰动加密的加密指数值,再为此,我们需要知道lcg的当前状态,才能向后预测下一个状态。此时我们就需要关注一下LCG的具体实现过程:
其实这个结构我之前看maple的博客有看到过叫做MRG (Multiple Recursive Generator),是一种LCG的延申。而容易想到,这既然也是一个线性递推过程,那么就可以用矩阵表示如下:
因此,往后的每一次的状态,其实都可以用初始状态来表示,也就是:
这一点又要怎么利用呢,我们注意到刚才我们利用HNP得到了多个xi的值,而xi又满足:
而eb仅有EBITS,因此xi其实会泄露noisei的高(PBITS-EBITS)个bit,而我们知道noisei是MRG的一个状态,也就是说我们能获得多个MRG状态的高位。同时,我们很清楚我们获得的noisei是MRG的第几个状态,那么我们又可以构造一个HNP来直接恢复这个MRG的初始状态,恢复过程如下:
由刚才的MRG递推矩阵表示知道,我们要想获得任意的si,我们只需要让递推矩阵自乘相应的次数,然后取得到矩阵的倒数第二行去乘上初始状态向量(s0,s1,s2,s3,s4,s5,1)就可以了。因此,我们得到的每个state其实都满足以下式子:
其中,t0-t6就是矩阵自乘对应次数后倒数第二行的值,而由于我们知道state的部分高位就是xi的高位,因此我们可以拆分成如下式子:
其中,xih是xi的高位,statel是每个state未知的低位。那么利用多组上述式子我们构造如下的格:
这个格具有如下线性关系:
然后配平一下系数,就可以在规约向量中找到初始状态,从而可以预测之后的所有state,进而得到bob的扰动加密的指数,解得alice公钥加密后的secret值,记为ct。
而第二种交互中,我们并不需要向后预测state,而直接一直发送ct的不同幂次,然后类似于上面得到xi的过程构造HNP就可以直接规约出每个加密指数了,然后就可以解出secret。具体来说,比如我们发送ct和ct^2,就分别得到:
仍选择生成元g,把这个式子化到指数上阶为phinb的群下,令:
依然把扰动加密指数看作一个整体xi,就有:
同理,就可以获得多组:
就可以构造HNP了。
而最终实施以上想法的时候其实还会遇到诸多问题,比如模phinb下的群其实并不存在生成元,因此只能退而求其次选一个模phinb/4的生成元,但是这就有可能覆盖不到所有元素,同时最后得到加密指数求解d时也可能会遇到逆元不存在的问题。此外,为了尽可能保证能规约出向量且用更少的次数,我自己测试了一下高斯启发式,发现最少选17,可以稍微多取一点(并且限时20s,再选大点我的小破电脑在这个时间跑不出来)因为选17也就存在较大的可能规约不出的问题。
同时第二个交互要注意我们发送的解密密文是模an下的,然而最后会在模bn下加密,所以要让解密的密文小于bn本身才行,这就要求an小于bn,且发送的幂次不能超过16,而经测试选6就基本能百分百规约出目标向量了。
综上,exp是概率性的,我自己跑了十几二十次的样子。
exp(很冗长):
1 | from Crypto.Util.number import * |