以生日悖论为基础的生日攻击,实际上是碰撞攻击的一种,其主要思想就是在一个随机集合中找到两个相同点(给定一个点的大小找是否有另一个点)的概率一般来说很大(有一个近似计算公式),以此概率来判断是否遍历爆破整个集合找出两个相同点(概率很大的情况下,很可能不止两个点相同)
近似计算公式
P = 1 − e − n ( n − 1 ) 2 N P= 1- e^{-\frac{n(n-1)}{2N}} P=1−e−2Nn(n−1)
其中 P P P是指存在两个相同点的概率,对应的, e − n ( n − 1 ) 2 N e^{-\frac{n(n-1)}{2N}} e−2Nn(n−1)即是整个集合中不存在两个相同点的概率; n n n是指该随机集合目前实际的总个数, N N N是指在该随机情况的条件下最多有 N N N种不同的可能
生日攻击一般都用作哈希碰撞,构造彩虹表等等,这里我们用浅显的生日悖论的原理来在给定一个点的情况下,来判断是否可以找到一个随机集合中另一个相同点
P r o b l e m Problem Problem
来源于:第五空间CTF 2022 vgcd
from random import getrandbits, seed
from Crypto.Util.number import getPrime, inverse, bytes_to_long, isPrime
from os import urandom
from secret import flag
def sample(rho, eta, gamma, p):
seed(urandom(8))
return p * getrandbits(gamma - eta) + getrandbits(rho)
def gen_vector(rho, eta, gamma, m, p, K):
v = vector([sample(rho, eta, gamma, p) for i in range(m)])
return K*v
rho = 6
m = 3
eta = 288
gamma = 512
p = getPrime(gamma)
q = getPrime(gamma)
n = p * q
e = 65537
phi = (p - 1) * (q - 1)
c = pow(bytes_to_long(flag), e, n)
print(n)
print(c)
pp = p >> (gamma - eta)
assert isPrime(pp)
K = Matrix.random(Integers(pp), m, m).lift()
t1 = [gen_vector(rho, eta, gamma, m, pp, K) for i in range(1024)]
print(t1)
for i in range(6):
t2 = [gen_vector(rho, eta, gamma, m, pp, K) for i in range(512)]
print(t2)
A n a l y s i s Analysis Analysis
题目关键是找到pp
,而围绕pp
题目生成了一大堆由随机数作为基底的数(但是这里不能用随机数内置的算法逆向求解,因为每次生成几个随机数都会重新设置seed
)
我们把打印出来的t1, t2
的生成过程(这两个列表的生成过程是一样的)用数学符号表达出来
K ⋅ V = ( k 0 k 1 k 2 k 3 k 4 k 5 k 6 k 7 k 8 ) ( v 0 v 1 v 2 ) = ( k 0 v 0 + k 1 v 1 + k 2 v 2 k 3 v 0 + k 4 v 1 + k 5 v 2 k 6 v 0 + k 7 v 1 + k 8 v 2 ) \begin{array}{ll}\pmb{K}\cdot \pmb{V} &= \begin{pmatrix} k_0 \qquad k_1 \qquad k_2 \\ k_3 \qquad k_4 \qquad k_5\\ k_6 \qquad k_7 \qquad k_8 \end{pmatrix} \begin{pmatrix} v_0 \quad v_1 \quad v_2 \end{pmatrix} \\ &= \begin{pmatrix} k_0v_0 + k_1v_1 +k_2v_2\qquad k_3v_0+k_4v_1+k_5v_2 \qquad k_6v_0+k_7v_1+k_8v_2 \end{pmatrix} \end{array} KK⋅VV=⎝ ⎛k0k1k2k3k4k5k6k7k8⎠ ⎞(v0v1v2)=(k0v0+k1v1+k2v2k3v0+k4v1+k5v2k6v0+k7v1+k8v2)
这里的 K , V \pmb{K,V} K,VK,V分别是矩阵和向量,这里进行的并不是矩阵乘法,当前类型的矩阵和向量左乘后得到的就是如上表达式
其中 K \pmb{K} KK在有所有的t1, t2
中的生成过程是一样的,而 V \pmb{V} VV是在随机生成的;
我们仔细查看 V \pmb{V} VV的生成过程(由于随机数生成大小的参数是给定了的,这里直接用数值表示随机数大小)
V = ( v 0 v 1 v 2 ) = ( p p ⋅ r 224 + r 6 p p ⋅ r 224 ′ + r 6 ′ p p ⋅ r 224 ′ ′ + r 6 ′ ′ ) \pmb{V} = \begin{pmatrix} v_0 & v_1 & v_2 \end{pmatrix} =\begin{pmatrix} pp \cdot r_{224} + r_6 \qquad pp \cdot r'_{224} + r'_6 \qquad pp \cdot r''_{224} + r''_6 \end{pmatrix} VV=(v0v1v2)=(pp⋅r224+r6pp⋅r224′+r6′pp⋅r224′′+r6′′)
其中 r i r_i ri表示 i i i bits大小的随机数,将 V \pmb{V} VV的表达式代入 K ⋅ V \pmb{K}\cdot \pmb{V} KK⋅VV 中
K ⋅ V = ( k 0 ( p p ⋅ r 224 + r 6 ) + k 1 ( p p ⋅ r 224 ′ + r 6 ′ ) + k 2 ( p p ⋅ r 224 ′ ′ + r 6 ′ ′ ) ⋯ ⋯ ) \pmb{K}\cdot \pmb{V}= \begin{pmatrix} &k_0(pp \cdot r_{224} + r_6) + k_1(pp \cdot r'_{224} + r'_6) +k_2( pp \cdot r''_{224} + r''_6) \qquad \cdots \qquad \cdots& \end{pmatrix} KK⋅VV=(k0(pp⋅r224+r6)+k1(pp⋅r224′+r6′)+k2(pp⋅r224′′+r6′′)⋯⋯)
单独看第一项
k 0 ( p p ⋅ r 224 + r 6 ) + k 1 ( p p ⋅ r 224 ′ + r 6 ′ ) + k 2 ( p p ⋅ r 224 ′ ′ + r 6 ′ ′ ) = ( k 0 ⋅ r 224 + k 1 ⋅ r 224 ′ + k 2 ⋅ r 224 ′ ′ ) ⋅ p p + ( k 0 r 6 + k 1 r 6 ′ + k 2 r 6 ′ ′ ) k_0(pp \cdot r_{224} + r_6) + k_1(pp \cdot r'_{224} + r'_6) +k_2( pp \cdot r''_{224} + r''_6) \qquad \\ = (k_0\cdot r_{224} + k_1 \cdot r'_{224} + k_2 \cdot r''_{224})\cdot pp + (k_0r_6+k_1r'_6+k_2r''_6) k0(pp⋅r224+r6)+k1(pp⋅r224′+r6′)+k2(pp⋅r224′′+r6′′)=(k0⋅r224+k1⋅r224′+k2⋅r224′′)⋅pp+(k0r6+k1r6′+k2r6′′)
此时我们就可以应用生日攻击的思想了,单独看上式的 ( k 0 r 6 + k 1 r 6 ′ + k 2 r 6 ′ ′ ) (k_0r_6+k_1r'_6+k_2r''_6) (k0r6+k1r6′+k2r6′′) ,如果我们能在t1, t2
的所有输出中找到两个相同的 r 6 , r 6 ′ , r 6 ′ ′ r_6, r'_6, r''_6 r6,r6′,r6′′,那么我们就可以将两者相减,得到一个这样的表达式 x ⋅ p p x\cdot pp x⋅pp ,同时,使用相同的 r 6 , r 6 ′ , r 6 ′ ′ r_6, r'_6, r''_6 r6,r6′,r6′′ 总共有三组( K ⋅ V \pmb{K}\cdot \pmb{V} KK⋅VV ),每组之间相减都可以得到表达式 x ′ ⋅ p p x'\cdot pp x′⋅pp,将三组之间的相减结果取最大公因数即可得到 p p pp pp (很大概率 p p pp pp 应该是 x ⋅ p p x\cdot pp x⋅pp 中最大的素因子)
实际寻找相同的 r 6 , r 6 ′ , r 6 ′ ′ r_6, r'_6, r''_6 r6,r6′,r6′′过程中,当然是先确定一个t1, t2
的结果,再遍历剩余的t1, t2
中的每一个结果
而t1, t2
总共有4096
组结果,每组结果中有三项数值,我们来计算一下生日攻击的成功概率
r 6 , r 6 ′ , r 6 ′ ′ r_6, r'_6, r''_6 r6,r6′,r6′′的所有可能应该有 2 18 2^{18} 218种,而当前的结果有4096
种;相当于 N = 2 18 , n = 4096 N = 2^{18},n = 4096 N=218,n=4096 ,代入生日攻击概率的近似计算公式
P = 1 − e − 4096 ( 4096 − 1 ) 2 ⋅ 2 18 = 1 − 1.2763491830534272 e − 14 \begin{array}{ll} P &= 1 - e^{-\frac{4096(4096-1)}{2\cdot 2^{18}}}\\ &=1 - 1.2763491830534272e^{-14} \end{array} P=1−e−2⋅2184096(4096−1)=1−1.2763491830534272e−14
sage: float(e^(-(4096*(4096-1)) / (2^19)))
1.2763491830534272e-14
所以几乎是百分百会有碰撞情况发生
得到 p p pp pp 之后,将其按照高位攻击的思路恢复原始的 p p p 即可,具体使用small_roots
函数的一种方法可以看 (10条消息) 2022 川渝网安比赛_初赛Crypto复现_M3ng@L的博客-CSDN博客 里的一道例题
S o l u t i o n Solution Solution
# type:ignore
from Crypto.Util.number import *
from tqdm import trange
import gmpy2
with open("output3.txt") as f:
input = [line.strip() for line in f.readlines()]
n = int(input[0])
c = int(input[1])
cipher = []
for i in input[2:]:
for j in eval(i):
cipher.append(j)
# print(len(cipher))
# for i in trange(len(cipher)):
# for j in range(i, len(cipher)):
# temp = [cipher[i][k] - cipher[j][k] for k in range(3)]
# gcd = [gmpy2.gcd(temp[0], temp[1]), gmpy2.gcd(temp[0], temp[2]), gmpy2.gcd(temp[1], temp[2])]
# for k in gcd:
# if k.bit_length() == 288:
# print(k)
# 356748247030693416847133214972226346960895831956739561317797640603898384278276333472137
pp = 356748247030693416847133214972226346960895831956739561317797640603898384278276333472137
eta = 288
gamma = 512
e = 0x10001
R.<x> = PolynomialRing(Zmod(n))
f = (pp << (gamma - eta)) + x
p = int((pp << (gamma - eta)) + f.small_roots(beta=0.49, epsilon=0.02)[0])
assert n % p == 0
q = n // p
phi = (p-1) * (q-1)
d = gmpy2.invert(e, phi)
m = pow(c, d, n)
print(long_to_bytes(m).decode())
(10条消息) 【2022 第五空间】5_vgcd WriteUP_Mr_AgNO3的博客-CSDN博客