Pairing与2024 XCTF Final的Cu2ve
和S1uM4i打了今年的XCTF Final,首先恭喜S1uM4i获得了季军,师傅们太强了,被狠狠地带飞了
我做了其中的Cu2ve,理论上这题需要用Pairing的方法去解,但这题有一种非预期的解法,所以比赛的时候就偷鸡了
赛后复现了一下预期的解法,学到了挺多的东西,于是在这里记录一下
以下内容在S1uM4i的WP中有简版,虽然WP好像没发。。。发了再补链接
Cu2ve·
首先看题目:Cu2ve.zip
题目分为两部分,在task.sage
中需要解决一个椭圆曲线上的DDH问题,然后获得utils.py
中prp
的每次的next
输出,根据这些输出解出初始的state
,最后使用state
获得密钥,做一次OTP的解密得flag
prp与解密·
prp
的部分会相对简单一点,所以先看prp
的部分,其中的next
函数其实就是输出state
表中的每一位状态
每当state
表中的每位(一共100
位)状态都输出过后,就使用twsit_state
表对state
表进行置换,即题目的twist
函数的操作
由于twsit_state
表是已知的,所以可以根据twsit_state
表写一种twist
的逆操作,比如下面的unTwisit
(当然这样的写法好像有点粗暴,应该有更好看的写法)
1 | tmp = list(range(100)) |
而加密所用的key
做一次twist
后(注意__init__
中有一次twist
)就是前100
位state
,那么其实如果知道前100
位state
的话,也就可以知道key
,就可以对密文解密得到flag
但事情肯定没有这么简单的,因为看task.sage
的话发现给了615
位的state
,这些state
大概可以分为6(或7)组,其中
我们需要的是,所以如果知道后面的的话,也可以通过求得到,或者的某些比特,这个后面用到的时候再细说
Pohlig-Hellman与非预期解法·
那么现在题目的难点就是如何通过task.sage
的输出得到那615
位的state
审题可以发现task.sage
里其实是个ECC的DDH(Decisional Diffie–Hellman)问题,即给定随机的和,给点、、和,判断究竟还是也是随机选取的数
在题目中,如果,则当前的state
为1
,否则为0
首先一个事实是,如果能够解决DLP问题,那么DDH问题是简单的,因为DLP问题可以解出、和,那么通过对比是否等于就好了
另一个事实是ECC的DLP问题也是难解的,但对题目的群阶n
分解后发现它有个小因子500
(注:无论用factorDB还是yafu都可以找到这个小因子),所以这条曲线上就会存在一个阶为500
或者500
的因子的小阶子群
熟悉Pohlig-Hellman算法的都会知道,在小阶子群上的DLP问题是容易的,因为直接枚举子群上所有的点就好了,那么题目的问题就可以转化为:先在小阶子群中解DLP问题,然后在中对比是否来解决中的DDH问题(其中为500
的因子),那么这个中DDH的解就大概率是原DDH问题的解
理论如此,然后就开干,第一步是先对点、、和都乘上,把这几个点都转到小阶子群中
1 | s = n // ns[0] |
第二步就是枚举,解子群的DLP
1 | for j in range(ns[0]): # ns[0] == 500 |
在这一步中,可能会出现DLP有多解的情况,其实这是正常的情况,因为这四个点乘上后的阶可能并不是500
,假设点的阶是100
的话,那么、、、和都是的解
于是,为了解决多解的问题,一种方法是先找到这几个点的准确阶,然后再在中去解
我用的另一种方法是,把中得到的所有解的z
和xy
分别存在两个集合中,然后对比两个集合的交集是否为空,如果为空则说明z
大概率是随机选择的
具体的参考代码如下:
1 | #!/usr/bin/env sage |
如果把以上输出的前100
比特拿去解密的话,会发现解出来的是乱码,原因是在以上操作中会出现因群阶太小导致的误差
举个栗子,假设的阶为1,即,那么自然地会有、和,在这种情况下DDH的结果永远是,就造成误差
所以最后还需要对这样的结果进行除杂,除杂的方式是利用前面提到的,即把冗余几组的state
先unTwist
到,然后对比所得到的是否相同
注意上面说的误差只会把0
误判为1
,所以如果在某个位置中出现分歧的话,只需要把这个位置置为0
即可
参考的除杂与解密代码:
1 | from hashlib import shake_128 |
Pairing与预期解·
上面的解法肯定是一种非预期的解法,因为题目中还给了点和,而上面的解法根本就没用到这两个点
预期的解法会比非预期的复杂很多,其中用到一种Pairing的方法
Bilinear Pairing·
双线性配对(Bilinear Pairing),一般简称为Pairing,所谓双线性就是它是一种二到一的映射,可以把两个群元映射到一个群中
令是一种Pairing的话,它会满足
把这个性质扩展一下的话,可以得到
那么在题目中,我们就可以通过计算
然后对比和来解DDH问题
但这种解法也没用到和这两个点,所以实际测试中也并没搞出来
另一种更好的方法是,计算
然后对比和
Tate Pairing·
Tate Pairing是一种经典的Pairing,先看定义
在题目中其实有很明显的提示说明要使用Tate Pairing
首先从定义中看,如果要使用Tate Pairing,则需要一个因子和一个嵌入度,其中因子是曲线阶(或者说Cardinality,题目中的n
)的大于的素因子,而且同时也是的因子,其中是满足该性质的最小
然后由于Tate Pairing映射后的结果落在域中,所以这个肯定不能太大,不然域中的运算需要很大的开销
所以一种找到和的方法是,遍历小的,然后找和的公因子,如果遍历到某个时出现大的公因子,即说明这个就是满足要求的,而这个公因子中就包含满足要求的
1 | for i in range(1, 100): |
对应题目中的数据,得到时有大的公因子,顺势分解群阶得到(注意是中大于的素因子)
1 | ns = [500, 158465746173819028262477785344468517, 2232123796482243553563388394642252242447624758532104909445052499370196473] |
到这里已经基本可以确定题目的曲线满足Tate Pairing的要求,如果曲线是随机生成的话其实只有极小的概率会满足这样的要求,所以可以确定这个曲线一定是出题人故意选择的,见下面引文
虽然已经确定了要用Tate Pairing,但如果用对比和的方法求解的话,会发现结果都是1
,就无法求解
而如果用对比和的方法求解的话,也会有问题,因为和分别是和这两条不同曲线上的点,而看回Tate Pairing的定义,则需要和是同一条曲线上的点
Twist与映射·
为了可以用和做Pairing,一种方法是可以构造一种同态映射,把点映射到曲线上,或者把点映射到曲线上
在寻找曲线和曲线的关系的时候,我发现曲线其实是曲线的Twist
如果把
代入上面的公式中,就可以得到以下的同态映射,注意:图中是,而题目中的是,需要进行转换,且代入题目中是、
可以大概验证一下,首先满足,于是等式两边乘上一个有
稍微整理一下得到
令就是
满足曲线
除此之外,映射还需要满足同态性,即
这个我还是用代码验证…
1 | p = 176857581480948244867604802349863732783663856225560523858099969581030268141874483416875345636657439749959951621 |
注意这里映射的结果是落在上,所以这里的与都应该是上的运算,而本来是上的元素,所以需要先把映射到上
注意这里并不能直接强行把转到上,因为和模的不可约多项式并不一样,我从春乎上直接抄了个映射的构造代码,反正 @春哥 也是直接抄SageMath文档的(
1 | phiF_2_k = Hom(F2, Fk)(F2.gen().minpoly().roots(Fk)[0][0]) |
另外还有一个很坑的地方是,本来开四次方只需要(1/a).sqrt().sqrt()
就好了,但是正常来说这里的开平方应该有两个解,而SageMath的sqrt()
函数居然随机地返回这两个解中的一个,于是如果写(1/a).sqrt().sqrt()
的话就会导致映射的同态性质不能被保证
所以我的一个修正方法是,使用排序强制开方返回最小的解
1 | def sqrt4(a): |
调库与相同群阶·
赛后 @沛公 问 @dbt 拿了份WP,我去偷瞄了一眼,发现其实这个映射还可以直接用SageMath内置的isomorphism_to
函数构造,所以代码也可以写成:
1 | Fk.<v> = GF(p^k) |
注意,isomorphism_to
函数需要映射的原像和像的阶相同,显然和的群阶不相同,所以不能直接把映射到中,而需要先把映射到中,再用isomorphism_to
函数把映射到
的方法也不难,直接借助phiF_2_k
映射即可
1 | def phiE_2_k2(Q, Ek2): |
到这里好像一切都理所当然,但有个问题是,为什么与的群阶相同?
PS:下面是理论部分,可跳过:)
首先肯定和Twist有关,翻了一下曲线和其Twist的阶的关系,发现有
代入d = 4
的情况,代码验证一下,注意:
- 上图是,对应到题目中就是
- 只有不可约的情况才是Twist(图中的是题目中的)
- 的两个根分别是和
1 | p = 176857581480948244867604802349863732783663856225560523858099969581030268141874483416875345636657439749959951621 |
但这两个阶明显不等,所以看一下(也即)的情况
代入求根公式的话,即在中,有
根据和的关系,有
即
代回,即
二项式定理展开一下,可得
到这里可以分奇偶两种情况
于是去掉奇数的情况就是
怼个代码验证一下
1 | def getOrder(t, p, k=1): |
再来看,因为是Twist,所以有
结合
也就是上面推出来的结果中,把换成,把换成就好了,即
回到题目中,由于,所以应该把上式的换成,且换成
把这些替换完成后,即要证明的是(由于式子已经和无关,所以换就好了)
消去后,等号左右相减得
如果是奇数的话,这个式子没什么特别的,也很难证是否相等
但如果是偶数的话就有点意思了,因为这个时候根据组合的性质会有
而且在时(假设有这一项,因为需要能够整除)
于是处于中间的的项可以被消去
综合可得,如果是偶数的话,式子可以继续化简为
到这里可以发现有个两两抵消的关系
所以如果和相等的话,利用这个抵消关系即可得到结果为
而要这两个东西相等,即要与的奇偶性相同,也即
进而推出
也即要是个偶数才满足情况,或者说要能被整除,即
所以综合上面的结果可得,如果的话,有
回到题目中可以发现,题目中恰好就是的情况,所以有
而
所以可得和的阶相等
最终EXP1·
现在映射已经构造完成了,所以题目中最难的一步已经解决了
剩下的思路是,
- 利用同态映射把点和都映射到上
- 把点和都映射到上,这一步是简单的,直接映射就好了
- 计算Tate Pairing 和,并对比是否相同,如果相同的话则这个
state
记为1
,否则记为0
- 对
615
组数据做以上操作
参考代码(PS:测试了一下,如果在Sage10以上版本运行的话会快很多)
1 | #!/uer/bin/env sage |
运行完后发现有很多的state
都无法做non-trivial 的Tate Pairing,但没关系,把能做的state
拿去做unTwist
,发现前100
个state
中只有16
个比特是未知的,所以爆破未知比特即可
参考代码2:
1 | from hashlib import shake_128 |
最终EXP2·
上面也说了,映射也可以调isomorphism_to
实现,所以给出另一个版本的参考代码:
1 | #!/uer/bin/env sage |
另外,关于为什么会出现不能做Tate Pairing的原因,也稍微研究了一下
首先一个是,如果要能做同态映射的话,就需要是的Twist,上面的引文中其实有说到,就需要不可约,对应到题目中即不可约
这个其实可以跟二次剩余类比,差不多就是是非二次剩余,而且扩域开方后依然是非二次剩余,于是如果要把开四次方的话,就要把域扩到上
个人感觉这样做是为了让点映射后与点无关,有点像Distortion Maps的感觉
然后另一个原因是需要,盲猜这个点的选取有关,可能需要能够把弄到阶的子群中?
关于这两点,可以结合代码中的
1 | checker = PR2(x2^4-1/a).is_irreducible() and E2.order() % r == 0 |
反正我自己测试是要符合这两点的才有解
外传与Distortion Maps·
在查资料的时候,我还发现了另一种偷鸡方法
大概意思是,如果,可以搞一个Distortion Maps ,把映射到,然后和就不再相关,于是就有
继续搜索下去,发现针对曲线,有这样的一种Distortion Maps
不过这样的映射需要,因为它需要在中的为非二次剩余
这样把开方后,就会扩域到中,而这样也会把中的坐标扩到,最终就可以使得与不相关
不过我也不知道这样说对不对,可以参考一下这个Map的另一个引文
附一下测试代码:
1 | p = 177777779 |
不过因为题目中的是,所以就偷鸡失败了
挖坑与Miller算法·
最后,我还是搞不懂具体在什么情况下Pairing的结果才会是,所以就挖个坑了
参考这里的话,好像只需要研究Tate Pairing就好了,因为如果Tate为的话那么Weil和Ate好像都是1
然后从数据上看的话,如果
1 | xP._miller_(yP, r)^(p-1) == 1 |
或
1 | P._miller_(Q, r)^(p^2-1) == 1 |
的话,Pairing的结果就是,因为Tate Pairing的结果是
1 | P._miller_(Q, r)^((p^k-1) / r) |
而p - 1
和p^2 - 1
都是p^k - 1
的因子
不过至于为什么这两种情况会出现,我就不知道了
估计得什么时候有空深入一下Rational Function和Miller’s Algorithm才能知道了
总结·
哎,dbt!
参考文献·
- Silverman J H, Pipher J, Hoffstein J. An introduction to mathematical cryptography[M]. Springer New York, 2014.
- Guide to pairing-based cryptography[M]. CRC Press, 2017.
- Miret J M, Sadornil D, Tena J. Elliptic curves with j= 0, 1728 and low embedding degree[J]. International Journal of Computer Mathematics, 2016, 93(12): 2042-2053.