该文章主要记录一些格相关的趣题
转化AGCD
题目来源:暂不明确
题目:
1 | from Crypto.Util.number import * |
观察题目,题目给了如下形式的53组等式:
其中,各个值的已知信息如下:
- x 为 (2,p - 1) 之间的随机数,并且在53组等式中保持不变
- a、r 为 (2,2**238) 之间的随机数
- 给出 a 、b
首先,有两点信息暗示要用格:
1、a、r 限制了数据范围,可以看作是小量,暗示用LLL
2、多组线性等式,暗示用矩阵
所以关键就是转化到一个可以运用格的问题形式,推导如下:
首先要想明白下面的式子:(推导比较容易,就不讲了)
然后就可以将问题按下面的方式逐步转化:
1、将b逆元转化到模n
代入上面的式子,变成:
同理:
2、联立消元
相减得到:
有没有发现,小量已经全部移到了等式同一边
3、转化为AGCD问题
将模等式利用同余定理展开:
此时,问题已经变成了AGCD问题,这是因为:
AGCD问题通常形式:
在本题中,各个量如下:
至此问题推导就结束了。
exp.py:
1 | from Crypto.Util.number import * |
Matrix
题目来源:暂不明确
题目:
1 | #!/usr/bin/env sage |
梳理一下解密流程:
- 先将flag串填充至32的整数倍
- 生成三组challenge需要的参数:m,pbits,level
- 将每一组的m用sha512加密后,依次与flag异或
- 给出每组challenge的对应输出与最终密文值
其中,challenge的各个参数值在加密过程中的作用依次是:
- 生成一个pbits的素数p,并以该p生成一个有限域Gf(p)
- 将m的32个随机字节转换成Gf(p)下的长度为32的向量
- 生成一个大小为32*(32-level)的矩阵M,M中元素均为Gf(p)中的随机数
- 计算c=m*M,并给出p、M和c
因此,我们要做的就是由c、M反解出m,并计算其sha512值后与密文依次异或,就能得到明文,但是三次求解的方法由M的大小而产生不同。
challenge1
第一轮,M的大小为32*32,因此有:
可以看作是m的32个变量对应了32组方程,因此满秩,可以直接求解线性方程组(数据有点大,想要复现的师傅可以联系我):
1 | p = |
challenge2
第二轮,M的大小为32*31,因此有:
也就是说,m的32个变量只有31组方程了,因此可以求出无穷多组解。不过由于只差一组方程,因此也只需要选取一个自由变量,并从c中对应减去其值后,解一个31变量的满秩方程。而判断解正确的依据就是解出来的m向量中所有值均在0-256之间。
说起来可能不那么明白,还是上个例子,这里以下面这个线性方程组简单说明一下:
写出来:
变成方程组形式就是:
现在我们假设已经知道了x4的值(对应于题目中,就是在0-256中爆破),那么把方程组中含x4的项都移到右边,就变成:
接下来解的就是这个满秩方程:
很容易想到,当解出的向量 $ (x_1,x_2,x_3)$ 均在0-256之间时,就正确求解了。放在本题中也不过是对这个例子的扩大而已,取x32进行爆破,方法是完全一样的。
1 | p = |
challenge3
第三轮,M的大小为32*16,因此有:
可以想到,如果继续用第二轮中的方法,那么需要爆破256的16次方种可能,数量级达到了2^128,是显然不可能的,所以需要另谋他法。而其实你应该早就想到了,m在每个challenge中,都是一个由32个0-256的值组成的向量。0-256意味着,在模p下这些值都很小,因此很自然地就会联想到格。
所以challenge3其实是一个格基规约问题,但是想构造出这样的格还是有点难度的。首先还是列出构造格需要的多个等式:
从这个矩阵乘法中,可以提取出其中的十六个等式:(i = 1,2,…,16)
以这些等式为基础,我们构造出下面的格:
1 | MM = matrix(ZZ,32,16,M) |
写清楚点就是:
这是一个49x48的矩阵,而我们用这个矩阵规约的依据等式是:
可以看出,规约出的向量 $(0,0,…,0,x_1,x_2,…,x_{32})$ 是非常短的,因此LLL就能得到这个解(不过实际操作会发现满足要求的向量在第二行):
1 | p = |
final
得到三个flag之后,进行sha512后依次与密文异或就好。
exp:
1 | import hashlib |
SSSMMM
题目来源:2023福建省工控赛
题目:
1 | from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse |
题目实现了一个代码很长的椭圆曲线类,不过注意到类中某个函数名:
1 | def _convert_jacb_to_nor(self, Point) |
而从其他地方了解过,椭圆曲线有一种基于雅可比坐标的计算,可以增加运算效率。具体可以参考:
那么这些函数应该就是雅可比坐标意义下的点加法和倍乘法的实现,所以可以不用去理解坐标的计算过程,直接把这个ecc_table中的各参数用sage中的标准ECC表示即可:
1 | ecc_table1 = { |
而可以验证,给出的g点是一个满足阶等于曲线阶的生成元,n也就是曲线的阶,并且是一个素数。把ECC这一层理解清楚之后,就明白其实很长一段代码都是不太需要理解的。
因此我们直接来看题目核心的加密任务:
1 | def leak(start, bits, k): |
可以看到,给出了一个leak函数,它可以泄露一个数k的中间130-136比特,共7比特。然后题目给出了如下加密过程:
- 随机生成一个数sk,并以其作为私钥生成一个TSM2的类对象sm2
- 进行50次基于ECC的变种签名操作,并给出每一次的临时密钥(nonce)k的leak,以及每一次的签名值(R,S)
- 用私钥sk作为AES密钥,对flag进行加密,并给出密文
那么思路就是从50次签名操作的泄露信息中,还原出私钥sk,并解AES得到flag。因此我们主要关注签名操作:
- 计算kg的横坐标作为x,其中g为给定的生成元,k为该次签名的临时密钥(nonce)
- 计算
- 计算(n是一个素数,所以可以由费马小定理推出下式)
- 计算
然后将(R,S)作为本次签名值返回,并每次泄漏k的中间7比特。
其实看到比特泄漏就能想到应该是构造HNP求解,那么什么是我们需要的短向量呢?
首先注意到,每一次的临时密钥k可以写成如下形式:
也就是把k分成三部分,高位、leak位和低位,其中高位和低位是未知的,其比特数大概为119比特和130比特。而这就是我们构造HNP的关键,短向量中要有每个临时密钥的高位和低位!而我们只要能恢复任何一个临时密钥k,我们就可以通过S的签名式,计算出sk的值。
而显然我们需要根据S的这个等式构造格:
如何构造呢?我们简单变形一下:
移项,并乘逆元至左端:
再移个项,这样做是为了把k置于一端,其他置于另一端:
然后我们知道,sk是一个256比特的量,我们是不希望他出现在规约后的结果中的,因此想办法联立两个式子把他消去,这里我们取i=0与i!=0的式子做消除,如下:
上下各自乘上对应系数:
作差:
左边是个已知的数字,为了表示方便,我们把这个先写成一个简单形式bi:
即:
然后为了找到HNP的形式,我们继续移一下项:
此时我们把k展开为三部分形式:
然后由于kleak已知,那么又可以剔出一部分常量:
移项:
然后为了表示方便,更新一下bi:
那么又有:
然后我们把(S0+R0)乘到右边去:
此时,应该可以发现HNP形式了,我们令:
则有:
把模n去掉,写成等式形式:
那么我们就可以根据这个等式构造格,请注意我们构造格的目的是想要kh与kl在规约出的向量中,因此构造如下格:
这样写可能有点不太清楚,直接上分块矩阵的代码好明白一点:
1 | length = 50 |
把刚才的格记为L,对该格有如下线性关系:
如此一来规约出的向量数量级相当,均为2^130比特左右,因此对L进行规约有机会找到这个向量,从而恢复k,进而解密出sk。
事实上,采用BKZ算法,该向量会出现在每项均非0的第一行,问题得解
exp:
1 | from Crypto.Util.number import * |
e2D1p
这一题我是跟着maple的wp复现的,原wp指路:
N1CTF 2023 Writeups | 廢文集中區 (maple3142.net)
主要详细记录一下自己的思考。
题目来源:N1CTF 2023
题目:
1 | from Crypto.Util.number import * |
题目内容比较简短,其加密流程如下:
- 生成一个素数p,满足p=2q+1,且q是一个512比特的大素数
- 生成200个与flag同比特(159比特)的message,作为之后加密的mask
- 用这200个message分别进行encrypt加密,并给出这200个message与对应密文,要求还原flag
其中,encrypt函数加密如下:
一些预备
首先肯定要恢复p,而如果指数上没有异或这个未知的flag的话,那么问题变成已知多组如下形式的等式,恢复模数p:
这个问题其实就是NSS二周年的一道题,具体分析过程可以看我的这篇:
NSSCTF-2nd-wp-crypto | 糖醋小鸡块的blog (tangcuxiaojikuai.xyz)
不过这里我仍然简单写一下构造格的思路,我们期望找到一组a,满足:
这样我们就有:
如果我们能找到多组满足条件的较小的a,我们就能在较短时间内算出多组整数域上的下式:
然后求解gcd即可恢复模数。而为了找到这样的多组满足条件的较小的a,我们构造了如下格:
他满足下面的关系式:
所以我们对格进行规约,取规约出的前几行短向量作为我们需要的a,然后按上述方法计算出多个kp并求解gcd即可恢复p。
恢复p
回到本题,这个题目在指数上异或了未知的flag:
那么我们就没法知道每一组密文对应的幂次具体是多少,所以上面问题中的格也就无从构造。但问题依然是相似的,我们仍然是想找到多组满足条件的a,去求解gcd从而恢复p。那么就要想办法将问题转化成较为简单的形式。
首先要理解一点,指数上的异或究竟指什么?我们不妨拆开这个问题,先只关注异或:
我们把mi拆成159个二进制位,记作mij(j从0到158),同理flag的159个比特位也记作flagj,那么其实异或就是对flag进行了如下的改变得到ti:
一眼看上去是有点复杂,但其实他表达的意思很简单,我们取mi的任意一个二进制位mij做如下说明:
- mij有0、1两个取值。取0,说明这一位不发生翻转,也就是对ti没有影响;取1,说明这一位发生了翻转
- 而发生翻转又有两种情况:0->1和1->0,这要视flagj的取值而定,flagj同样有0、1两种取值。因此可以以(-1)^(flagj)来表示翻转情况
- 2^j表示的是翻转的具体量
举个简单的例子:假设flag二进制为101,message为011,那么异或其实也就是:
- 最后一位1->0
- 倒数第二位0->1
- 最高位不翻转
那么这样计算出ti其实就等于:
这就是说,我们可以把异或转化为一种加法形式表示,那么我们再把这种形式放回指数上,又会如何呢?
也就可以写成:
而正如刚才讲的预备题目中所说,我们要恢复模数p,就要找到多组满足如下条件的a,然后求解gcd:
代入到本题中的式子,就会有如下形式:
又因为右边底数部分e是相同的,因此累乘可以变成指数上的累加:
所以我们其实希望有:
那么其实也就是有:
所以现在我们的目标就是靠这个式子,去找到若干组较小的a满足上式,然后就可以求gcd得到p了。而想要达到这个目的其实也很容易,注意到这个等式其实由可以拆成两部分组成:
那么我们不妨使两部分均为0,那么其和也肯定为0,也就是:
而如何使两部分分别为0呢?首先,对于第一部分,显而易见只要使得:
也就是ai的和为0就可以。而对于第二部分,列式观察就会发现,我们也只需要满足下式即可:
由这两个关系我们可以构造如下矩阵:
这个矩阵具有如下线性关系:
其中,L的最后一列是满足刚才写的第一部分,而L的前面的列是满足第二部分。
这样一来,我们能够看出,我们想要的满足条件的a其实就是L的左核空间中的向量,而为了让他更小,我们对左核空间的基向量组进行规约即可。
规约后我们取几组a按照上面所说的办法就能得到p。
求解flag
有了p之后,我们仍然要继续想办法用已知的如下式子恢复flag:
显然如果我们能求解DLP得到指数部分的话,就能再异或mi得到flag。不过由于题目中,p由2q+1的方式生成,所以p-1肯定不光滑,并且也没有别的好的方式直接求解这个离散对数。
不过之前翻春哥文章看到过一篇,用调cado求解这种p-1只有一个大素因子的离散对数问题:
[调包侠] 利用cado-nfs计算离散对数的调包记录 暨 第四届“强网”拟态防御国际精英挑战赛 Crypto - speedrun writeup - 知乎 (zhihu.com)
不过这个要使用好像挺麻烦,不知道在这里是否可行
回到正题,既然直接求解这个离散对数问题是难解的,那么就要另寻办法。这个时候再观察我们刚才的式子:
里面的这一部分是由flag的每一个二进制比特计算得到的:
那么我们是否可以利用这个式子逐比特恢复flag?首先仍然注意刚才的矩阵:
刚才我们求解这个矩阵的左核空间并规约,求gcd并去除小因子得到了p。之所以能这么做,是基于我们拆出了下面这个式子,并令两部分分别为0的缘故:
这个时候我们找到的ai满足:
而如果我们改变右边的解为:
这时候如果我们能解矩阵方程得到a,那么向量a应该满足:
也就是说,我们分割出的两部分和,此时第二部分仍为0,而第一部分变成了1,也就是:
此时我们再求c的累乘,得到的就应该是e的flag次方。但是我们好像仍然没有办法求解离散对数得到flag,并且更绝望的是,这个矩阵方程无解。
然后看maple的思路,发现大致相同,但是他发现如果target向量是如下形式的话都有解:
而这其实就给我们逐比特还原flag带来了机会,比如我们第一次以第一个向量作为目标向量求解矩阵方程,会得到一个向量a,然后我们用这个向量a进行累乘,得到的结果应该是:
k在这里是最高比特位,flagk也就是flag的最高比特位,也就是1。然后我们把这次累乘的结果记作y1:
然后,我们选择第二个向量求解矩阵方程,并且同样用结果进行累乘得到第二组结果y2:
可以发现y1、y2具有如下关系:
然后你会发现,由于flag的每一比特只有两种取值,因此这个部分也只有两种取值:
我们算出这两种取值,然后分别与y1相乘看哪个等于y2,就得到了flag的这一比特位。往后也同理,由此我们就可以逐比特还原flag的所有比特位了。
这里有一点需要注意,虽然解矩阵方程的矩阵和目标向量都定义在ZZ域上,但是实际解出来并不是整数,所以计算累乘时需要额外处理一下。我这里就直接用了maple的field_exp函数。并且由于最后一步模的计算较多我就用了powmod加快,虽然好像效果并不是非常明显。
exp:
1 | from Crypto.Util.number import * |
总的来说这题对我来说难度还是很大,路还很长。
vector
题目来源:暂不明确
题目:
1 | from base64 import b64encode, b64decode |
是一个师傅问我的一道靶机题,但是我没有环境,所以就讲讲思路。
题目任务如下:
- 生成一个256bit的素数x,同时生成一个X,满足:
- 生成一个2500bit数量级的p并发送给我们,然后限时30s,交互开始
交互开始后,我们在时间允许范围内有无限次交互机会,每次交互我们可以:
- 输入”reset1”,题目会随机生成yi、ai,并计算出对应bi后,发送(ai,bi)给我们。满足(X,yi)在下面的椭圆曲线上:
- 输入”reset2”,题目会更新X的值
- 输入”check”,我们可以给靶机核对小写x(就是最初生成的256bit的素数)的值,正确则可以得到flag
那么首先就要明白reset1、reset2这两个交互功能各有什么作用。首先我们知道有:
那么如果能知道多组X的值,这就变成了一个AGCD问题,这也就是reset2的作用。
因此,我们的想法就是:
- 每次reset2,得到一个未知的Xi
- 想办法用reset1来解出Xi的值
- 得到多组Xi后,解AGCD得到x去通过核验,拿到flag
那么接下来的任务就落在了如何利用reset1上。我们已知每次reset1会提供给我们一组满足如下等式的(ai,bi):
看上去是引导人往椭圆曲线想,就会自然想到groebner,但是稍微再想一想就知道变量多了,肯定groebner不出来。
而实际上,既然我们可以多次reset1,那么这其实也是一个HNP问题。我们把模等式展开:
那么我们完全可以同样用HNP构造格的思路,构造下面的格来求解X:
这个格具有如下线性关系:
然后,我们配平一下这个格并进行规约就有机会得到目标向量。经过测试,申请四组(ai,bi),就可以在第三行得到我们需要的目标向量,并且所用时间也很短,符合要求。
得到一个Xi后我们就reset2更新下一个Xi,并继续按这个方法求解即可。然后有多组Xi就可以AGCD求x了。
由于没有靶机,这里我就放一下我本地验证HNP问题的exp:
1 | from Crypto.Util.number import * |
HNP
题目来源:暂不明确
题目:
1 | from Crypto.Util.number import * |
有两个师傅今天都问到了我这个题目,乍一看是很经典的HNP,但是做的过程中会发现这题没有那么简单,需要做不少优化处理,因此还是写一写。
基本HNP构造格的思路可以参考:
[HNP] 一类基于各种DSA的HNP问题求解 - 知乎 (zhihu.com)
简而言之,题目任务为:
- 三部分HNP,每部分分别对应一个待求数x、y、z,用x^y^z组成AES密钥并对flag进行加密
- 第一部分HNP,给了32组数据,泄漏高8位
- 第二部分HNP,给了33组数据,泄漏低8位
- 第三部分HNP,给了103组数据,泄漏高四位
那么接下来就逐个部分说明。每部分还是会简单写一写题目内容和其最基本的格构造方法。
泄露高8位
a、b具有如下关系:
然后,题目给出32组ai以及bi的高八位,要求解出x。由于bi只有高八位知道,所以我们把bi拆成以下形式:
为了转化为HNP形式,我们将第i组数据与第0组数据两两作差消除x:
即得到:
ai以及b的高位bhi均为已知值,进行移项:
然后把a0的逆元乘至右侧(注意q是2^256,所以需要选一个逆元存在的当作a0,不过这一题正好a0逆元存在),即得到HNP形式:
其中:
将模等式展开得到:
因此我们可以构造如下形式的格:
这个格具有如下线性关系:
那么一般来说我们对这个格进行LLL规约就能得到我们的目标向量。但是试一试会发现完全不行,规约出的向量最后一列几乎全为0,而不是我们期望的2^248。
然后我就按照上面参考文章那样,试了试BKZ,结果同样是不乐观的,没有办法得到我们的目标向量。
然后计算格的高斯启发式,会发现数量级与我们的目标向量非常相近,这说明我们的方法并没有错,而是被卡了界。给32、33、103组数据的目的估计就在于此,经过我自己测试,给的数据多几组,BKZ确实是可以得到结果的。
那么接下来就是如何优化这个格,而显然这个格的行列式基本变不了多少,所以不能显著提高其高斯启发式,因此只能从缩短目标向量入手。
而要缩短目标向量,就要改变bhi的数量级,因此我采用的办法是改变最初的拆分策略。最初的拆分策略是:
这样拆分是因为题目给了我们bhi,所以会这么思考也很正常。这样其实也保证了bli是一个正数。而现在需要思考:我们好像不一定需要bhi是一个正数,我们想要的是缩小他的数量级,那么有没有什么拆分方式可以使他的数量级从2^248缩小一些?
所以我采用了如下方式:
此时,用这种拆分去构造格,规约出的向量的每一项就变成了如下的形式:
其预期的平均数量级就成功下降了1比特。事实证明这就是压垮骆驼的最后一根稻草,因为这样我们就能用BKZ算法规约出我们需要的向量了。
当然,我测了几组数据发现目标向量并不一定就在第一行,因此还需要加一个核验。核验思路也很简单,直接用求出的x与给定的32组ai去计算bi高位,看看与题目给的是不是相同即可。
不过我这里应该还是有点小bug的,因为bli-2^247应该有正有负,求x0时需要把两种情况都算一遍,但是既然能出结果我也就不调了,以后遇到再说。
第一部分exp:
1 | from Crypto.Util.number import * |
泄露低8位
与泄漏高八位异曲同工,我们仍然按刚才的方式,消除x转化到HNP。然后也和预期一样,直接LLL或BKZ都出不了,界依然被卡了,所以还是要改变拆分方式。
已知低八位时,正常应该如下拆分:
同样的道理,我们现在规约出来的目标向量的每一项应该是bhi,要想办法降低他的数量级。所以处理手段也基本一样,我们拆成如下形式就好:
最要命的是这样规约依然出不了,但是没想到其他优化方式了,于是就只能不断抬高BKZ算法的blocksize,发现由于格的维数并不高,所以blocksize提高很多也不会有多慢,抬到30后总算是出结果了。
依然也需要核验,不过道理相同,同样也应该会存在bug。
这一部分exp:
1 | from Crypto.Util.number import * |
泄露高4位
这一部分和第一部分就没什么差别了,优化手段也是一样,但是blocksize要适当提高,我这里是抬到22后出了结果。而且由于数据也更多,耗时也会相对更久。
这一部分exp:
1 | from Crypto.Util.number import * |
完整exp
1 | from Crypto.Util.number import * |
pack
题目来源:JQCTF 2023
题目描述:
1 | hint:实测LLL后剔除不满足向量再做BKZ效果有提升 |
题目:
1 | from Crypto.Util.number import * |
output较长这里不放出,需要的师傅可以找我要或者去NSSCTF上下载附件。
简单阐述一下题目内容:
- 将flag转化为二进制序列,并将长度补齐至8的整数倍
- 对flag的二进制序列进行两次如下加密
每一次加密的具体加密流程为:
1.先进行pack
- 随机生成长度为127的列表a,每一个元素为256bit的素数
- 随机生成长度为128的列表s,每一个元素为-1或1
- 通过计算a的最后一个元素,来保证a和s列表具有如下关系:
- 返回a和s列表,用于epack加密
2.进行epack
epack在这里我认为就是error-pack的意思,具体来说,他将flag的二进制序列当作error,然后按如下流程加密:
- 先将pack得到的列表s转换成01列表s1,具体为:-1->0,1->1
- 将s1与error逐比特异或得到s2
- 最后一步就是个普通的01背包
然后,题目提供给我们两次加密的列表a1、a2,和对应的子集和sum1、sum2,需要我们还原error。
首先这个背包的密度只有0.5左右,因此我觉得可以直接LLL求解出两次的s2,但是事实上不行。(这是我直到做完这个题目也没有想通的一点,密度这么小为什么不行?开始觉得可能是因为a的最后一个元素是负数的缘故,但自己生成了几组全是正数的数据也规约不出来。)
然后就照着题目提示尝试了一下先LLL再BKZ(如果目标向量够短,这肯定是可行的,因为这实际上就是在LLL得到的一组格基中,继续用BKZ寻找更短的格基),但是block_size提升到24也出不来结果,提升到26又一直跑不完,太慢了,但是按照题目提示来说肯定就是这个方法没错,因此要么就是继续硬跑更大的block_size,要么就是做一点格的优化。
因此我用上了一个我之前考虑过的优化背包格的方法,正常来说背包格是如下形式:
对这个格,01向量s满足下式:
目标向量也是0、1向量,虽然是短向量,但是它内部的数量级在这个小范围内其实并不相当(0是0bit,1是1bit)。而我们可以改变格为如下形式:
对于同一个0、1向量s,线性关系变成了:
而此时,s’是一个由-1,1组成的向量,因此数量级相当,更有利于对目标向量的规约。事实上改成这样后,LLL+block_size=24的BKZ就可以规约出两次epack用的s2了。
有了两个s2后,要解出error可能有以下两个方向:
- 想办法解出两个s,然后与对应s2异或即得到error
- 直接规约出error
对于第一个方向,实际上跟刚才的背包差不多,区别在于s本身就是个由-1、1组成的向量,因此格可以直接用最原始的背包格去掉最后一行就好,然后依然LLL+block_size=24的BKZ莽使一波——并没有想要的结果。
那么就走第二个路子:直接规约出error。而对于error我们有如下信息可用,首先是两组:
然后我们知道,s到s1经历了如下变换:
那么代入原式就有:
而对于s1,我们又知道:
因此有:
那么继续代入求和式:
由于s2、error都是按比特取值,所以一共只有四种组合,很轻松就能找到一个同态来把异或运算同态到乘法:
然后把这个式子组合一下:
这就又变成了一个背包问题,但是加密了两次,就对2errori-1组成的向量有两组约束,因此可以写到同一个格中进行规约,规约出2errori-1后,将-1,1组成的向量映射回01向量就好。
然后需要注意的是规约出的向量与本身目标向量的正负可能是相反的,因此把两种情况都考虑到更好(de一次bug太久了)。
exp:
1 | from Crypto.Util.number import * |
Lattice
题目来源:强网杯 2022
题目:
1 | from sage.modules.free_module_integer import IntegerLattice |
题目基于如下一个矩阵等式:
其中,A是一个由小于N的随机数组成的矩阵,B是一个-2^15到2^15的随机数组成的矩阵。题目用矩阵B进行LLL后的第一行当作AES密钥加密flag,并给出密文和矩阵C,要求还原flag。
先说结论:这是一个比较典型的正交格攻击(Orthogonal Lattice Attack),在当时应该是一篇论文题,论文为:
不过他的核心思路还是能够理解的,这里就介绍一下。
首先,由于矩阵B本身元素就很小,再LLL一遍第一行应该会更小,所以是有希望用格找到他的。首先转置一下得到:
然后假设可以找到这样一个矩阵M,使得他和C^T正交,也就是:
那么对于原式就会有:
再转置回来就有:
那么B的向量应该都在M^T的左核空间中,且由于B中向量都较短,所以对左核空间的基向量进行规约就有机会能找到B了。
而为了找到这样的M,方式就是构造一个如下形式的格:
那么对于满足如下条件的m向量:
那么这样的m向量就在M中。论文也提到了这样预期规约出的前m-n-1行会是需要的向量,不过感觉理论上来说B^T的左核会有m-n个向量,事实上也有m-n个向量符合条件。
找到这样的M矩阵后对其转置矩阵M^T求左核后LLL,其第一行也就是矩阵B进行LLL后的第一行,然后分正负两个情况去尝试解密AES得到flag就好。
不过实际操作会发现,sage求左核真的有点慢,而细想一下我们其实也就是想找到短向量b使得bM^T=0而已,所以依然可以用前一个格来解决,只是需要在最后一些列配上一个大系数(一般配N就合适)。这样就一分钟之内就能搞定。
同时,由于M和B都是小量,所以模不模N其实没差,因此最后的格还可以优化掉一些行,进一步减少耗时,虽然减少的并不多。
规约出来不是需要的向量也是正常的,多来几次就好。
exp:
1 | from Crypto.Util.number import * |
crys
题目来源:强网杯 2022
题目:
1 | import hashlib |
题目首先生成一个3217bit数量级的素数p,使得它是2t+1的形式,其中t是一个难以分解的合数(这样做的目的应该是难以求模p下的DLP)。然后题目生成了一个2^3217以内的padding,并将其与flag异或,并模(p-1)//2得到x,在下文为了方便表示,把(p-1)//2记作q。
之后题目做的事情就比较奇怪了,首先取g=4作为底数,然后生成两个长为168的列表a、b。其中,a中元素ai是小于q的随机数,b是模p下g的对应ai次幂。之后进行了14500次操作,这一过程可以写成矩阵形式:
其中k又可以写为:
X是一个仅由0、1组成的矩阵。那么整个式子就可以写作:
后面的assert其实并不重要,这也侧面说明r的值以及列表b其实没什么用。因此我们的目的就是根据上面这个式子去求出x来。
而我们拥有的所有信息是:
- p、s、e的值
- X是一个01矩阵
- e是一个较小的量
由第二条可以看出应该还是与正交格有关,事实上上面论文中也有提及过这个问题,叫做ahssp(Affine Hidden Subset Sum Problem)。而了解了上一个题目原理的话,这个题目就应该会有些思路,如下:
先是找到一个矩阵M,使得:
那么就有:
转置一下得到:
you因为T是01矩阵,非常小,所以在M^T的左核里LLL应该就能找到T矩阵来。找到T之后再找M’矩阵,使得:
代回原式就有:
就可以解出向量a,然后再代回去就可以取一个值算出x了。如何找到这样的M、M’矩阵在上面的Lattice一题已经提到过。
但是有一个很严峻的问题就是,这一题的向量长度达到了14500。而一般来说格的维度达到几百的话规约已经相当慢了,所以全部用上并不现实。
事实上14500这个数值和论文附录H里提及到的数字$\frac{n^2 + 3n}{2}$很接近,但是我没有看太明白
然后我的思路就是取用其中一些数据来做,综合考虑了一下耗时与规约效果决定最终取700条数据。跑了一个多小时后跑完了,发现很悲剧,应该是数据组数不太够,导致求X的那一步并不对,求出来的不是01矩阵。
今晚挂一下900组数据看看,再高的话感觉就是做法有点问题了。
(2.27更新)
挂了一晚上还是没跑出正确结果,再多选数据应该也跑不出来了。今早上就自己生成了一些数据做观察,发现两个有点古怪的地方:
- q是不是素数,会对规约的正确性有影响
- e的大小会对规约的正确性有影响
试了一下发现当e比较小,且q不是素数的时候(也就是题目的情况),上面的方法就会出错。然后发现一个事实:要按照论文的要求,严格取前面的m-n-1行来组成M(论文里也限制了e的取值范围在2^t以内,也就是一个较小值),进而求X。多取一行规约结果就会很差。
不过道理我也没有想的太明白,也许是因为e比较小,所以对于他来说没有必要模q?
然后测试出来也是取2n组数据一般就够了,保险起见我多取了一些,sage10.2差不多要做20min左右。得到x后还有一个小问题就是padding是略大于q的,所以要用带余除法算出原本没有模q的x,再去求flag。
exp:
1 | from Crypto.Util.number import * |