格(Lattice)

前置知识

线性代数:向量、矩阵、线性方程、线性空间、线性相关、基

什么是

格与格基

\(\mathbf{b_0},\mathbf{b_1}, \cdots ,\mathbf{b_{n-1}}\)为空间\(\mathbb{R}^m\)上一组线性无关的向量(\(m \ge n\)),则集合 \[ \mathcal{L}(\mathbf{b_0},\mathbf{b_1}, \cdots ,\mathbf{b_{n-1}}) := \{\sum_{i = 0}^{n-1}x_i\mathbf{b_i}:x \in \mathbb{Z}\} \] 被称作一个

其中矩阵\(\mathbf{B} := [\mathbf{b_0},\mathbf{b_1}, \cdots ,\mathbf{b_{n-1}}]\)称为该格的格基,所以格又可以表示为: \[ \mathcal{L}(\mathbf{B}) := \{\mathbf{Bx} : \mathbf{x} \in \mathbb{Z}^n\} \]

基本域

\(B = [\mathbf{b_0},\mathbf{b_1}, \cdots ,\mathbf{b_{n-1}}]\)为一组格基,则该组格基的基本域为: \[ \mathcal{F}(\mathbf{b_0},\mathbf{b_1}, \cdots ,\mathbf{b_{n-1}}) := \{\sum_{i=0}^{n-1}t_i\mathbf{b_i}:0 \le t_i < 1\} \]

高斯启发式(The Gaussian Heuristic)

\(L\)\(n\)维格,高斯所期望的最短长度为 \[ \sigma(L) = \sqrt{\frac{n}{2 \pi e}} (\det{L})^{\frac{1}{n}} \] 高斯启发式表示,在一个“随机选择的格”中的最短非零向量满足 \[ ||v_{shortest}|| \approx \sigma(L) \] 更精确地,如果确定了\(\epsilon > 0\),则当\(n\)足够大时这个随机的\(n\)维格将满足 \[ (1 - \epsilon )\sigma(L) \le || v_{shortest} || \le (1 + \epsilon ) \sigma(L) \] P.S:看不懂一点,直接从An Introduction to Mathematical Cryptography抄来的(逃

最短向量问题(SVP,The Short Vector Problem)

在格\(\mathcal{L}\)中找到最短的非零向量

对于高维格很难解决,但是可以利用\(LLL\)算法在多项式时间内求解近似最短近似非零向量,如在SageMath中:

1
2
3
4
5
B = matrix(ZZ,[[3,9],[1,1]])
L = B.LLL()
# or
L = B.BKZ(block_size=2)
shortestV = L[0]

一般用BKZ效果会比LLL好,其中的block_size设置越大效果越好,但算得越慢,最大是格的维度

格攻击简单实例

例1:ez_NTRU

例题代码

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
from Crypto.Util.number import getPrime, inverse, bytes_to_long, long_to_bytes
import random
import math

FLAG = b'crypto{?????????????????????}'


def gen_key():
q = getPrime(512)
upper_bound = int(math.sqrt(q // 2))
lower_bound = int(math.sqrt(q // 4))
f = random.randint(2, upper_bound)
while True:
g = random.randint(lower_bound, upper_bound)
if math.gcd(f, g) == 1:
break
h = (inverse(f, q)*g) % q
return (q, h), (f, g)


def encrypt(q, h, m):
assert m < int(math.sqrt(q // 2))
r = random.randint(2, int(math.sqrt(q // 2)))
e = (r*h + m) % q
return e


def decrypt(q, h, f, g, e):
a = (f*e) % q
m = (a*inverse(f, g)) % g
return m


public, private = gen_key()
q, h = public
f, g = private

m = bytes_to_long(FLAG)
e = encrypt(q, h, m)

print(f'Public key: {(q,h)}')
print(f'Encrypted Flag: {e}')


'''
Public key: (7638232120454925879231554234011842347641017888219021175304217358715878636183252433454896490677496516149889316745664606749499241420160898019203925115292257, 2163268902194560093843693572170199707501787797497998463462129592239973581462651622978282637513865274199374452805292639586264791317439029535926401109074800)
Encrypted Flag: 5605696495253720664142881956908624307570671858477482119657436163663663844731169035682344974286379049123733356009125671924280312532755241162267269123486523
'''

加解密原理:

密钥选取: \[ p \space \text{is a prime} \\ \text{random: }2 < f < \sqrt{\frac{q}{2}}\space \\ \text{random: }\sqrt{\frac{q}{4}} < g < \sqrt{\frac{q}{2}}\\ \gcd(f,g) = 1\\ h = f_q^{-1}g \mod{q}\\ \text{public key: }(q,h)\\ \text{private key: }(f,g)\\ \] 加密: \[ \text{plaintext: }m < \sqrt{\frac{q}{2}}\\ \text{random: }2 < r < \sqrt{\frac{q}{2}}\\ e = (rh + m) \mod{q} \\ \text{ciphertext: } e \] 解密: \[ a = fe \mod{q}\\ m = af_g^{-1} \mod{g} \] 解密原理: \[ \begin{align} a &= fe \mod{q}\\ &= frh + fm \mod{q}\\ &= frf_q^{-1}g + fm \mod{q}\\ &= rg + fm \mod q\\ &= rg +fm \\ \\ m &= af_g^{-1} \mod{g}\\ &= (rg + fm)f_g^{-1} \mod{g}\\ &= m \end{align} \] 解题:

关键需要知道私钥\((f,g)\)

找关系 \[ h = f_q^{-1}g \mod{q}\\ fh \equiv g \pmod{q}\\ fh = g + kq\\ fh - kq = g \] 两个未知量,一个已知方程,再构造一个方程 \[ f - 0k = f \]\[ h \cdot f - q \cdot k = g \\ 1 \cdot f - 0 \cdot k = f\\ \] 目的是将线性方程分离为已知的矩阵和未知的向量 \[ \begin{pmatrix} f & -k \end{pmatrix} \begin{pmatrix} h & 1\\ q & 0 \end{pmatrix} = \begin{pmatrix} g & f \end{pmatrix} \]

\[ \mathbf{v} \mathbf{B} = \mathbf{w} \]

所求\(\mathbf{w}\),已知\(\mathbf{B}\),在满足The Gaussian Heuristic的条件下(目前还没学明白)

\(\mathrm{B}\)进行LLL格基规约,得到的最短向量即为\(\mathbf{w}\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# sage
q = 7638232120454925879231554234011842347641017888219021175304217358715878636183252433454896490677496516149889316745664606749499241420160898019203925115292257
h = 2163268902194560093843693572170199707501787797497998463462129592239973581462651622978282637513865274199374452805292639586264791317439029535926401109074800
B = matrix(ZZ, [
[h,1],
[q,0]
]) # 构造格基
L = B.LLL() # LLL格基规约
g,f = L[0]
g,f = abs(g), abs(f)

R = GF(q)
assert R(h) == R(f) ^ (-1) * R(g)
print("f =", f)
print("g =", g)
# f = 47251817614431369468151088301948722761694622606220578981561236563325808178756
# g = 43997957885147078115851147456370880089696256470389782348293341937915504254589


# python
m = decrypt(q,h,f,g,e)
print(long_to_bytes(m))
# b'crypto{Gauss_lattice_attack!}'

例2:超递增序列背包加密

例题代码

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
import random
from collections import namedtuple
import gmpy2
from Crypto.Util.number import isPrime, bytes_to_long, inverse, long_to_bytes

FLAG = b'crypto{??????????????????????????}'
PrivateKey = namedtuple("PrivateKey", ['b', 'r', 'q'])

def gen_private_key(size):
s = 10000
b = []
for _ in range(size):
ai = random.randint(s + 1, 2 * s)
assert ai > sum(b)
b.append(ai)
s += ai
while True:
q = random.randint(2 * s, 32 * s)
if isPrime(q):
break
r = random.randint(s, q)
assert q > sum(b)
assert gmpy2.gcd(q,r) == 1
return PrivateKey(b, r, q)


def gen_public_key(private_key: PrivateKey):
a = []
for x in private_key.b:
a.append((private_key.r * x) % private_key.q)
return a


def encrypt(msg, public_key):
assert len(msg) * 8 <= len(public_key)
ct = 0
msg = bytes_to_long(msg)
for bi in public_key:
ct += (msg & 1) * bi
msg >>= 1
return ct


def decrypt(ct, private_key: PrivateKey):
ct = inverse(private_key.r, private_key.q) * ct % private_key.q
msg = 0
for i in range(len(private_key.b) - 1, -1, -1):
if ct >= private_key.b[i]:
msg |= 1 << i
ct -= private_key.b[i]
return long_to_bytes(msg)


private_key = gen_private_key(len(FLAG) * 8)
public_key = gen_public_key(private_key)
encrypted = encrypt(FLAG, public_key)
decrypted = decrypt(encrypted, private_key)
assert decrypted == FLAG

print(f'Public key: {public_key}')
print(f'Encrypted Flag: {encrypted}')

加解密原理

密钥选取:

构造超递增序列,记为向量\(\mathbf{b}\)

选取足够大的\(q,r\)满足\(q>r > \sum{b_i}\),且\(\gcd(r,q) = 1\)

生成公钥$ = r ( a_i = r b_i ) $

生成私钥\(w = r^{-1} \pmod{q}\)并保存超递增序列\(\mathbf{b}\)

加密:

将明文信息\(m\)转为二进制序列\(x_0,x_1,x_2,\cdots \in \{0,1\}\),记为\(\mathbf{x} \in \{0,1\}^n\)

计算密文\(c = \mathbf{x} \cdot \mathbf{a}\)

解密:

计算\(S = wc\)

根据超递增序列\(\mathbf{b}\)容易分离\(S\)\(\mathbf{x}\)

根据\(\mathbf{x}\)解码得到明文\(m\)

解密原理: \[ \begin{align} S &= wc\\ &= w \mathbf{x} \cdot \mathbf{a}\\ &= w \mathbf{x} \cdot r\mathbf{b}\\ &= (wr)\mathbf{x} \cdot \mathbf{b}\\ &= \mathbf{x} \cdot \mathbf{b} \end{align} \] 利用超递增序列特性: \[ \forall i \in \{1,\cdots,n-1\}, b_i > \sum_{j=0}^{i-1}{b_j} \] 可解\(\mathbf{x}\)

解题:

构造格 \[ \begin{pmatrix} 2 & 0 & \cdots & 0 & a_0\\ 0 & 2 & \cdots & 0 & a_1\\ \vdots & \vdots & \ddots & \vdots & \vdots \\ 0 & 0 & \cdots & 2 & a_{n-1}\\ 1 & 1 & 1 & 1 & c \end{pmatrix}_{(n+1) \times (n+1)} \] 和线性方程 \[ \begin{pmatrix} x_0 & x_1 & \cdots & x_{n-1} & -1 \end{pmatrix} \begin{pmatrix} 2 & 0 & \cdots & 0 & a_0\\ 0 & 2 & \cdots & 0 & a_1\\ \vdots & \vdots & \ddots & \vdots & \vdots \\ 0 & 0 & \cdots & 2 & a_{n-1}\\ 1 & 1 & 1 & 1 & c \end{pmatrix} = \begin{pmatrix} 2x_0-1 & 2x_1-1 & \cdots & 2x_{n-1}-1 & 0 \end{pmatrix} \] 由于\(x_i \in \{0,1\}\),可得\(2x_i-1 \in \{1,-1\}\)

据此写脚本

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
# sage
pubkey = [] # 公钥,太长了,略
enc = # 密文,略
nbits = len(pubkey)
B = Matrix(ZZ, nbits+1, nbits+1)
for i in range(nbits):
B[i,i] = 2
B[i,nbits] = pubkey[i]
B[-1,:] = 1
B[nbits,nbits] = enc

L = B.LLL()
for r in L:
# 提取符合要求的基向量
tmp = True
for c in r:
if c != 1 and c != -1 and c != 0:
tmp = False
break
if not tmp:
continue
# 对符合要求的基向量提取明文m
x = r[:-1][::-1]
for i in range(len(x)):
x[i] = (1 - x[i]) // 2
flag = int(''.join(str(xi) for xi in x),2)
print("flag =",hex(flag))

# python
print(long_to_bytes(flag))
# b'crypto{my_kn4ps4ck_1s_l1ghtw31ght}'

基于格规约的题目分类集合

RLWE

[DUTCTF2024] Rolling Lava Waves Erupt

临时把难度降了很多,真是简简又单单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from sage.all import *
from Crypto.Util.number import *
from secret import flag

q = getPrime(128)
P = PolynomialRing(Zmod(q), "x")
x = P.gen()
n = 32
f = x ^ n - 1

A = [randint(0, q) for i in range(n)]
e = [randint(-4, 4) for i in range(n)]
s = [ord(flag[i]) for i in range(len(flag))]

b = (P(A)*P(s)+P(e)) % f
b = b.list()
print(q)
print(A)
print(b)
# 238355018964699036477605770797311983483
# [81001562575806812598625263947608310708, 67663462339970006425162255245771032763, 13911544992517862926597077554173799418, 70083213603267510104406237968793143649, 160226034997336725957012303722600378802, 121951496164295068583299034780718057668, 208553469728728739512150598724342228187, 35318485952332304879637196679455721903, 22714538463725341070448119190289931254, 221304588658454200047941540247361775692, 112424761781213208014673729457183124619, 105680264200650817922743959248252422121, 125768571260244939582961235871974909457, 118198480391943221698043835219635560580, 214244617297840106125668893796872511160, 87106492087067154929405336565856306825, 187960032306741642945407897033192609123, 22726202322486160294430042897142325001, 203059524299842209581084988100190187236, 63020272694124483141075936434312220013, 186251080168915230210124587293294049010, 225705797232526039582108888085944266263, 76063293691042549879364238315611375776, 179628429520550154827890958417971754012, 108001983863222322906569731782757800214, 112433035489677335685402291188381813415, 218180223301918295545394686267221494631, 93773278407573108656592128636536623504, 238307013397154178073159242004463683898, 23881336280146300789551126889875688166, 54502266866805036164460323085866372252, 232281191496537995646948234752281123702]
# [228504063908105525412614261760096017819, 81124758225342777727055611238200844123, 221529360989688925075641421715865881990, 94581704093284652391039972901005826832, 325817175563219265139583434925859179, 61376720464338690752332570540750181063, 233592338494785767656067338668809640189, 163645819666726030166959455529901356084, 60208350718676054890787888760334528877, 201473594096943739348339242697834651580, 99827655947377781053644353039989625749, 115360373222772120894866265852731745339, 101911732529939789371857156723584106115, 72176427231293186211180061967096323976, 107022031608633451044397596932022874462, 100348034173215891890474269791089616852, 230759723819962976308246252656275080901, 147884737442822434231136640694143526801, 185579318965300506065799519816578411014, 110435485452405047534359402172935094694, 237202083226712124503759766619056077329, 85740770839153572140547789090327159712, 157375521655279000903038598327271994130, 130547485304061498221759094030128798455, 142346712879183862037210635357954151681, 63500359629636675686860935166844636444, 10975347244291774911892310688210697385, 58375422867618050035821600511587742693, 15312124465922160852808737885222609790, 224142474208300065196736821112985241689, 133332140200420631166595755765867226530, 102171340332087544878437194137100487710]

高中好友给发来他们的校赛题目,算是我自主解决的第一道格题目

题目玩了个藏头,暗(明)示RLWE问题

定义环 \[ \frac{\mathbb{Z}_{q}[x]}{x^{32}-1} \]flags并生成随机多项式\(A\)和小噪声多项式\(e\),计算出 \[ b = As + e \] 已知\(b,A\)\(s\)

最初拿到这个题目,先弄清楚了所谓"多项式环"的数学含义,用简单的语言表示,就是把高次多项式了一下,类似通常数论那样的模

例如对于模\(q = 7\)\[ x^2 -4x + 4 \equiv x^2 + 3x + 4 \\ x^8 -3x^3 + 9x^2 - x - 7 \equiv x^8 + 4x^3 + 2x^2 + 6x \] 又对于模\(x^4 - 1\)\[ \begin{align} &\space x^5 + 2x^4 +3x^3 + 4x^2 + 5x + 6\\ &\equiv (x^5 - x(x^4 -1)) + (2x^4 - 2(x^4-1)) +3x^3 + 4x^2 + 5x + 6\\ &\equiv 5x + 2 +3x^3 + 4x^2 + 5x + 6\\ &\equiv 3x^3 + 4x^2 + 10x + 8 \end{align} \] 上述两种计算规则合在一起,就是多项式环\(\frac{\mathbb{Z}_{7}[x]}{x^{4}-1}\)的计算方式(应该没错吧

再看题目脚本是如何利用数组生成多项式的,通过sage实践发现,定义环P后,P(arr)可以将数组转为多项式,其中arr[i]映射到多项式P(arr)\(x^i\)项系数;

.list()可将多项式的系数转为数组,和P()互为逆运算,即我们可以得到P(arr).list() == arr # arr空位由0补全。一个小细节:.list()会把系数为\(0\)的也都扔进数组。

解决语法问题,可以来看纯数学问题:

\(As\)是两个多项式相乘:A[0](对应\(x^0\)项)在模\(q\)下乘以s[0](对应\(x^0\)项)的积对应\(x^0\)项,A[31](对应\(x^{31}\)项)在模\(q\)下乘以s[31](对应\(x^{31}\)项)的积对应\(x^{62}\)项,类比卷积乘法可以列出下面的竖式: \[ \begin{matrix} A_{0} s_{0} & A_{1} s_{0} & A_{2} s_{0} & \cdots & \cdots & A_{30} s_{0} & A_{31} s_{0}\\ & A_{0} s_{1} & A_{1} s_{1} & A_{2} s_{1} & \cdots & \cdots & A_{30} s_{1} & A_{31} s_{1}\\ & & \ddots & \ddots & \ddots & \ddots & \ddots & \ddots & \ddots \\ &&&&& A_{0} s_{30} & A_{1} s_{30} & A_{2} s_{30} & \cdots & \cdots & A_{30} s_{30} & A_{31} s_{30}\\ &&&&&& A_{0} s_{31} & A_{1} s_{31} & A_{2} s_{31} & \cdots & \cdots & A_{30} s_{31} & A_{31} s_{31}\\ \end{matrix} \] 将此竖式纵向相加后,可以得到每一项的系数表达式,\(x_k\)项对应的系数是 \(\sum_{i+j = k}A_i s_j\)

由于模\(x^{32}-1\)的存在,对次数过高的项进行处理,在视觉上就是把右面的竖式三角平移到左侧: \[ \begin{matrix} A_{0} s_{0} & A_{1} s_{0} & \cdots & A_{30} s_{0} & A_{31} s_{0}\\ A_{31} s_{1} & A_{0} s_{1}& \cdots & A_{29} s_{1} & A_{30} s_{1} \\ \vdots & \vdots & \ddots & \vdots & \vdots \\ A_{2} s_{30} & A_{3} s_{30} & \cdots & A_{0} s_{30} & A_{1} s_{30}\\ A_{1} s_{31} & A_{2} s_{31} & \cdots & A_{31} s_{31} & A_{0} s_{31}\\ \end{matrix} \] 抽象成矩阵: \[ \begin{bmatrix} s_0 & s_1 & \cdots & s_{30} & s_{31} \end{bmatrix} \begin{bmatrix} A_{0} & A_{1} & \cdots & A_{30} & A_{31}\\ A_{31}& A_{0} & \cdots & A_{29} & A_{30}\\ \vdots & \vdots & \ddots & \vdots & \vdots \\ A_{2} & A_{3}& \cdots & A_{0} & A_{1} \\ A_{1} & A_{2}& \cdots & A_{31} & A_{0} \end{bmatrix} \] 也就是可以得到这样的线性方程: $$ \[\begin{bmatrix} s_0 & s_1 & \cdots & s_{30} & s_{31} \end{bmatrix}\] \[\begin{bmatrix} A_{0} & A_{1} & \cdots & A_{30} & A_{31}\\ A_{31}& A_{0} & \cdots & A_{29} & A_{30}\\ \vdots & \vdots & \ddots & \vdots & \vdots \\ A_{2} & A_{3}& \cdots & A_{0} & A_{1} \\ A_{1} & A_{2}& \cdots & A_{31} & A_{0} \end{bmatrix}\]
  • \[\begin{bmatrix} e_0 & e_1 & \cdots & e_{30} & e_{31} \end{bmatrix} \equiv \begin{bmatrix} b_0 & b_1 & \cdots & b_{30} & b_{31} \end{bmatrix} \pmod{q}\] $$

\[ \mathbf{s} \mathbf{M} + \mathbf{e} = \mathbf{b} + q\mathbf{k} \] 各个参数取值如下: \[ \mathbf{s} = [s_0,s_1,s_2,\cdots,s_{31}] \in \{0,1,\cdots,127\}^{32}\\ \mathbf{e} = [e_0,e_1,e_2,\cdots,e_{31}] \in \{-4,-3,\cdots,4\}^{32}\\ \mathbf{b} = [b_0,b_1,b_2,\cdots,b_{31}]\\ \mathbf{k} = [k_0,k_1,k_2,\cdots,k_{31}] \in \mathbb{Z}^{32} \\ \] 最初的想法是根据上述线性方程构造一个格出来,

下面是最初很天真的想法,毕竟刚学格,不要笑话我QAQ \[ \mathbf{s} \mathbf{M}+ \mathbf{e} = \mathbf{b} +q\mathbf{k}\\ \mathbf{e} = (\mathbf{b} + q\mathbf{k})\mathbf{M}^{-1}\mathbf{M} - \mathbf{s}\mathbf{M}\\ \mathbf{e} = ((\mathbf{b} + q\mathbf{k})\mathbf{M}^{-1} - \mathbf{s})\mathbf{M}\\ \det{\mathbf{M}} \gg \sqrt{4^{n}} > ||\mathbf{e}||\\ \] 据此构造格:

1
2
3
4
5
6
M = matrix(n)
for i in range(n):
for j in range(n):
B[i,j] = A[(j-i)%n]
L = B.LLL()
print(L[0])

然后发现根本规约不出来,究其原因,推测是该格规律性过强,不满足使用LLL时“格足够随机”的条件(参见本页面"高斯启发式"部分)

经过资料查找,构造出这样的格: \[ \mathbf{N} = \begin{bmatrix} \mathbf{M} & & \\ q\mathbf{I} & \mathbf{I} & \\ \mathbf{b} & & \beta \end{bmatrix}_{(2n+1) \times (2n+1)} \] 对应的方程: $$ \[\begin{bmatrix} -\mathbf{s}, \mathbf{k}, 1 \end{bmatrix}\] \[\begin{bmatrix} \mathbf{M} & & \\ q\mathbf{I} & \mathbf{I} & \\ \mathbf{b} & & \beta \end{bmatrix}\]

=

\[\begin{bmatrix} \mathbf{e},\mathbf{k},\beta \end{bmatrix}\]

$$ 据此写脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# sage
n = 32
q = 238355018964699036477605770797311983483
A =
b =
beta = 2 # 仅仅是一个参数,不太影响做题

N = matrix(2*n +1)
for i in range(n):
N[i+n,i] = q
N[-1,i] = b[i]
N[i+n,i+n] = 1
N[-1,-1] = beta
for j in range(n):
N[i,j] = A[(j-i)%n]
L = N.LLL()

res = L[0] * N^(-1)
for i in res[0:n]:
print(chr(abs(i)),end = '')
print()
# DUTCTF{G0od_J06_iN_RLW3_l4tt1c3}

反思

毕竟算是入门的一道题,而且有一次试错的经历,简单记录一下

我先前的构造格的方法过于东拼西凑,本身就是一个NP困难问题,而我在看似巧妙的东拼西凑中无形丢失了很多信息,最后反映出来的是格过于随机,无法规约出预期答案,构造的格应当尽量包含已知信息,然后再交给LLL碰碰运气,从NP的狭缝中找到P的路线

[NSSRound#18 Basic]New Year Ring1

新年用个新环!

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
from Crypto.Util.number import *
from random import *
from secret import flag

p = getPrime(128)
n = 64
assert len(flag) < n

PRp.<x> = PolynomialRing(Zmod(p))
f = x^n+2024
PR = PRp.quo(f)
assert f.is_irreducible()

A = [randint(0, p) for i in range(n)]
E = [randint(-4, 4) for i in range(n)]
S = [ord(flag[i]) for i in range(len(flag))]

B = PR(A)*PR(S)+PR(E)
print(A)
print(B.list())
print(p)

#[203211966212120647994404162543901058385, 113998159211311856163959557092243561040, 300945935078651395200612763362025786190, 289539358962072307485802285781112184506, 266860644401078099268835882620812059236, 109469988130078797414202659254940150397, 142193430695353860553837582452159920561, 163603443131512016589524436620766900399, 125570052004030843134707673404921516235, 289517595415670285587801685152176297797, 260336477592153246815046507549532230446, 89128330175853905837766718032373473126, 187717315035064338496744033397006646838, 47398231438108384695170959715078016751, 320133498675264306580441374518511607677, 289224545427213583554005441616103706530, 48124450498844786580485017131751952117, 139700877104641187048620578708689412417, 71574579434689200165233820726593943746, 138252394765364749010226318178055019086, 261521192680609185598298661488532314373, 226827408720691280529315243806063440819, 312302166514031202715929873278517388968, 41543713499646037109947736777306720313, 265649922622063773566846630839697709881, 243184423040946982395304999253912592156, 206198258022435934039803402480943364253, 26031203447491686152181680360833340987, 267200487804167757054737887061948559784, 301825292179107181769802726785989059100, 245678367957552191949253880949910260990, 171086126608355874606827637031412493750, 238890683861901290139689496391030872360, 22845910272546674819720769022817968767, 28421978727921425902205492605476665131, 171865962533483544755013698922220144571, 133361830829435140268114465077140337526, 86265129464226210912927536339928098681, 68669331909862762270788724935161403154, 201866646039120111937128925580825280486, 13363589230429215865126757180274178298, 17909430443917365585897023486940736171, 91757667623716596753047172893731203404, 46825614193892927425213536328954419018, 131552079476235333764480742173849445520, 235812708672214623030453261917981487844, 128419026868422646823814506658958018620, 165071194644940049087362229894395125949, 119007848732714973887391773573107966930, 142175857554093924103634649494868118501, 324116975329566500720942384202511507819, 145202961579339260969095793970625895397, 302068925092915352636494174062039213344, 204865812301701145536309778960425467197, 73204183646529021271666811846606597696, 305266620775559116657495389805588046145, 38573243820497560644612322159835262762, 204132117511623685134894865125985283485, 152661573492853637425833316355962196973, 234147606390346795925961511757446390699, 101214795511329059676976012743816028858, 45259554101612024332508390665659000501, 69808091348685800959727855379331581323, 96236770545429224235813571394198156104]
#[122276244512812151700561410687880152355, 288902265068638112029210922418017307765, 227157280930411828282688954386270375417, 51392889392439935890454046852827718770, 107818090106926819686128970258855341987, 39797693303511873260100821082668360148, 165165311283389310714977999739406590465, 145742713856100761940986808065029032102, 85681122792038178595571398323505588215, 161750232371529874705027252096297284499, 65492947650590100527727115268504178500, 158980937836523416847183159463389719119, 217622237154991741032187619647917235486, 214403448117831129512734472087177528755, 7203970244866082501778704003244517036, 209327947737263058848347484690330837593, 261471938052725575109694161428073566879, 290548445194039386389333379248698464710, 786777047021934678707520080022646500, 181116047323074255804762916647191259040, 321394759425138484862188864303222149962, 172828377940330639355360387538637264455, 66514389687485973164456706712404934406, 123351805496712587796702291879739918777, 52527849016391552904401277415521541344, 269082045250550515743652451127140175208, 46611178931314336386280465499464556662, 306738233577541073584072095728624359658, 27700046031756446145225137030746217892, 90225963569273234686624144350335934112, 67692467833088163585206113599832005433, 323500338166210082579512376483956531150, 268393735497552347113220159171930201434, 303137349721700995847836323480358301717, 37409490969034515063902941681541879806, 89987553043378360059649171549894177100, 177195833588614025644477178454034861283, 186453862621405866128018280976135392700, 134539827243365382868881776050914249090, 174423939567752183215031097600196039560, 141295420547653230540490506885038874517, 71077155156144579112947495766255667953, 219294250229364145810766028169964010349, 32511078113518753980651829431342417337, 303329577727497287329206502612155346064, 90557253357868638265309134765551619996, 274174517174776162629592599514881337711, 49932861529770838835799830915660452043, 12677314962488846037929508134986771433, 234965385179859810854399287906068885976, 11962701379314564572722140202812617562, 315105728460289255044517314167781474357, 92397698128764550243762952707724608359, 79161472796010129344236399474925315198, 8230717415162461432383488082982662910, 187550346095617606538292501947907284159, 111838535125019998906719357578490542024, 127632981408215865540832181515313916183, 77787230784265032836219137545199240126, 209675082880646980235572000733260128373, 318088800052782828672155510824979199450, 197936332876846440605381880211398578620, 163607618093826671398256369217224613243, 325974106851521509536970685901401508674]
#330464659390823375471800230370495966901

学长给发的题(接下来两道都是),很有趣

本题RLWE问题,换了一个新的环: \[ \frac{\mathbb{Z}_{p}[x]}{x^{64}+2024} \]flags并生成随机多项式\(A\)和小噪声多项式\(e\) \(s\)\(e\)是小的我就要小写!),计算出: \[ B = As + e\\ \] 和上一题一样,列竖式,化矩阵,构造格,不同的是矩阵部分三角要乘以\((-2024)\)

理好逻辑后直接可以写脚本:

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
# sage
n = 64
p =
A =
B =
beta = 2

N = matrix(2*n +1)
for i in range(n):
N[i+n,i] = p
N[-1,i] = B[i]
N[i+n,i+n] = 1
N[-1,-1] = beta
for j in range(n):
N[i,j] = A[(j-i)%n]
if i > j:
N[i,j] = N[i,j] * (-2024) % p
# 我这里模了p,可以不模,不影响,因为该矩阵块下面就是pI矩阵块
L = N.LLL()

res = L[0] * N^(-1)
for i in res[0:n]:
print(chr(abs(i)),end = '')
print()
# NSSCTF{U_c4n_5olv3_th1s_pr0b!em_0nce_u_kn0W_M4trix_0f_Polymu1}

[NSSRound#18 Basic]New Year Ring2

新年用个新新环!

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
from Crypto.Util.number import *
from random import *
from secret import flag

p = getPrime(128)
n = 64
assert len(flag) < n

PRp.<x> = PolynomialRing(Zmod(p))
f = x^n+2*x^3+0*x^2+2*x+4 #welcome to 2024!
PR = PRp.quo(f)
assert f.is_irreducible()

A = [randint(0, p) for i in range(n)]
E = [randint(-4, 4) for i in range(n)]
S = [ord(flag[i]) for i in range(len(flag))]

B = PR(A)*PR(S)+PR(E)
print(A)
print(B.list())
print(p)

#[25628433222756085994492259068849402115, 108192689210265022288173745176216677895, 85974352459523213168503152436203993854, 41828313114844948099739205193013146660, 15961445864161535368425735570577871568, 59860624089211511801244965491114405123, 72238114736535717286419734164194491146, 1731419769773538054984020741260144321, 110926179088722293171658261432626666958, 4729059639835826882559436203284470206, 168495809056757598731123948067040373565, 3338360642630078653320363127167526308, 19389936220034072874232261675901806153, 10438117635879553768611012406691341169, 153667038046207304584441437937384897775, 72166665774655568880543097047667804863, 95319528381704092095798376951073542165, 62898039808274250589921375492358418705, 38256076367009076141369526506747299307, 116682168147974451260364221046574659055, 26917522774848180141649752130328445540, 155767907175579627310170473659502971153, 77475859845928815191730640478115081925, 70014064310214050035280946814599808820, 85993451838284022745989979544017241858, 1559588202521630621698182776746048258, 52424771201426327411234245307354332594, 92380990205273211168316314938871853614, 55868954214861940284812204603983552046, 86766496415006406161245997036503548760, 65631024912623216715877274133399692328, 165552413066518454864366169079315182837, 27090806019996818214310558040178209015, 24884235560487406082425405304334927881, 167131388381252997695719535111304864318, 84309157064049564271714180786273613498, 53226032826639984552629376026393806036, 10043888404273006035387494091731007691, 12579929759225042382751229048796566699, 166363316914893642035801926729741157934, 37839073681950914796852925854557791313, 59823734019834883911363191453005985507, 72713054697995652053446065501322532087, 7355549923971285393506940087861413681, 21734747677920938504945342916533076569, 93933129166049277428941778125233010374, 58955842096425290408052247755008181893, 35959399375270454336325579486716264076, 134512300913738209016228764504703580317, 156877088942396466398635185398252103142, 104513294126348280186621088034948750337, 58139022317938952038809444482141783106, 98565612260156356115401166878515345986, 114416081175175578305854221578018095237, 22901750234260702478992290068664968479, 156611313609023033314989985233189973832, 125083132555321363078288077242345281728, 41404163164349704408020008746598776378, 148785934103979787297708145558354509586, 60576000474984112015781658795707443603, 157378782433123699827402744836263199181, 115808625117495689666174307128964343007, 55532885470248050887252206269085843644, 167226824329071601899518640284153209079]
#[125436276837023032466255967451858640606, 144448119937248956307846718982491347769, 162306129951573761575068204811168998078, 83181011846487027653033036479190112408, 40957533970438134530667298492545012058, 39781894692503876914564462045404705807, 164051006333092368306831348749408921012, 111012742481005666423330275146981180733, 7715720858029882578293974044643808669, 157677217515339873901600902409190036650, 47743593975916243882559558507891093023, 157986164305142738632123943371346719550, 127635891927296917312348404019956617317, 29559276398124624593609203169860820474, 65960871111768788316941346555260753893, 168042316135611110131429808009369471733, 1212659292467585393525194729053590464, 131142075720852427381782478157244639696, 102842995705164719704337121568124710266, 34358829636338044181518280022135929086, 46061675284276849395800943167734760351, 135977825342856793359560567810384022667, 79194609568617048147045548433187673199, 3676419995870098657902754244090307337, 129102404404523795672123957629514020212, 132931883751740337774987369503377603250, 157631873978881249131112881234813720862, 25064634967243118171242164160769943822, 90201135758213533081777343764941590513, 28708219563163506588485189820706388938, 56568253458138625936076514562071708932, 6347413009696777418004642228340087455, 142708918733729617152197380466325161544, 90675635242552386205397936839615479057, 92501808867676816791538575898905495372, 119574898086174634261072854178847743198, 109965735435324316785759245560872940757, 88027969746581556885954470651939385855, 134321591115155640446804238393795645472, 138737181251155600814378949277591209336, 99644145229459444132485749993357552167, 72102411291892187794699247448258325603, 83762370748394317206974170435605895442, 50088402451174667055894866569295371337, 151985189458457003469569692195947019009, 94074842589490698882361618773816024181, 73064286019059695311967281427732071049, 15926695040044471692620032927734856548, 92040182094543438421700664648795356800, 72067816134410856572981520568082148623, 51543145349488111975169803932104264498, 133476358234399233123899184886685568681, 138871089323661273034769987507240180967, 135182088478518211398715618356312799892, 142132493504501637417871042380487888261, 152821195684002682180600788766787854997, 108605984719600234041614912160255546443, 107009285527858918462712915168386920973, 142315287684855833566016316758770217595, 120564701831156855156008640956601967150, 76342952544707200039421682949094541165, 1616883292120605398623492917180224681, 114803483209077151333130433872010751344, 59602734024539592667147877839382863851]
#p = 171384865635734387982308861436753436427

本题RLWE问题,换了一个新新的环: \[ \frac{\mathbb{Z}_{p}[x]}{x^{64} + 2x^3 + 0x^2 + 2x + 4} \] 如果弄明白了RLWE的原理,做这个题只要构造出来那个很有规律的矩阵\(\mathbf{M}\)即可

不过像我这么懒的CTFer肯定不会自己去推

于是我先去做了下一道题的通解脚本,具体推导也在下一题,这里直接放解题脚本:

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
#sage
# 原题给出的数据
n = 64
p =
A =
B =
beta = 2

# 原题的环
PRp.<x> = PolynomialRing(Zmod(p))
f = x^n+2*x^3+0*x^2+2*x+4 #welcome to 2024!
PR = PRp.quo(f)
assert f.is_irreducible()

# 构造方程矩阵
M = []
for i in range(n):
M.append((PR(A) * x**i).list())
M = matrix(M)
# 构造格
N = matrix(2*n+1)
N[-1,-1] = beta
for i in range(n):
N[i,i+n] = 1
N[i+n,i] = p
N[-1,i] = B[i]
for j in range(n):
N[i,j] = M[i,j]
L = N.LLL()

# 提取flag
res = L[0] * N^(-1)
for i in res[:n]:
print(chr(abs(i)),end='')
print()
# NSSCTF{M4st3r_0F_RLWE_h3r3_15_ur_flag_4nD_HHHHappy_niu_Ye4r!!}

[NSSRound#18 Basic]New Year Ring3

新年用个新新新新环!

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
from Crypto.Util.number import *
from random import *
from secret import flag

p = getPrime(128)
n = 64
assert len(flag) < n

PRp.<x> = PolynomialRing(Zmod(p))
newyear = [2,0,2,4]
f = x^n
for i in range(n):
f += newyear[i%4]*x^(n-1-i) #welcome to 2024!
PR = PRp.quo(f)
assert f.is_irreducible()

A = [randint(0, p) for i in range(n)]
E = [randint(-4, 4) for i in range(n)]
S = [ord(flag[i]) for i in range(len(flag))]

B = PR(A)*PR(S)+PR(E)
print(A)
print(B.list())
print(p)

#[143895536039223361852265879930678271849, 49941095619845369403633015749131244252, 192366807276439803341529420280624695341, 41863179329950350668896955448215016801, 161730900763954941459384930538350726304, 69486722690005239063204459423772119076, 150044605995739190031133614780160002164, 53617544566841857383903597080623109339, 74625820568619243297350181792710531670, 16380616502578067837931320551193635969, 161905270710745874415457791187229895491, 60615195134615631027916010677640865675, 160490605829184543601552626825699212302, 67566984475203250552779752218068594160, 213285653211278131505736720165333128065, 64852378214458505102852766762259296220, 127863063181868054387649888847647635563, 173388212610482051840613449798996257379, 107853129453974811439304180468226801619, 47026991726695830686352132714620786077, 54537407094343620242102574881749270054, 213117990243770129927508589374671252215, 188551573436848023902527678127834115095, 184904756222053551643771035280489901913, 130154717708566694574065576508622379407, 169935928816833755787895658897579592244, 25127997681478110567876718932464424139, 66241086523494222064942298262635441275, 128384348198631966247204855175670185827, 186534789734452323940160584274797242101, 44113757786219743282268996106377142498, 161464089943212437590741012855856579087, 152978274982120638112396394811148002893, 127858672640521604574047701048286112335, 24811959746896810879635351294335540800, 139884017165706571446169150175115903276, 162069295856137595644855638161429085830, 103852979898094376585707063387003994190, 209853862047034349714977775246731397993, 83875540721706520814802129116239183141, 229717680288888669127660062951596981300, 142905448436402318840598200380283379791, 97701946983539136207969604175005004177, 132882918810599573425074347748636321411, 30946979906897798248175975370883897061, 227445609344085479411697428271056340360, 226861499760726945354933277553812121082, 34610454530275079356327169999644151033, 86543487884993671802260662621113434518, 193748181739943346975874601639150010426, 34955296746796322689176100212054938608, 78982586205793555689802056285642735691, 3417188406740628320649576312400181913, 165141388407797054166867506278447838336, 12695265941766745754277957144871301850, 89458008041873757788350494973818918496, 17544328835809254417440184162473311528, 17809092337865928270441021151091911120, 190539046594231331045902135957952644092, 54013202950149354717829653296038417693, 182140149560821210676566057705914354798, 182590162902622112992977612842205026523, 226186981126958513892204825722664037031, 96763448317215723992226901770790799160]
#[146131953632371057251386640770029761962, 155126848145561802955686124994407020596, 215548579819339372855889610814620506576, 215378077144333849353671692326217769974, 211651696346814349480371028078398365850, 56211588439881618182356345926007014351, 35792916285189253601060073066755768657, 193834084101163540069238791365615525212, 64185605303590514314207911465024315433, 74011476974910970618385371699236712496, 144080173424901828501825948809304329878, 50478515840382641218068722055014699097, 61424373096897526822902957616641049161, 228135094283701120116040034916450645966, 173676278129085855942014338366292660640, 4958486587405211188374567586180119556, 107653280004443417845206647750419894242, 227948848364624440330554299487592496262, 103344313582462128597769184556262118266, 179090340541635393147453223953633783366, 91157025540035569434535505051126504071, 218301646561306199078249711983197566905, 207984369886452772406666980534904709935, 84848453635637121750244379243550315275, 70455676155616420940430093468129934421, 112082679346753821742720835769195197209, 100096437088582331947465760446007491622, 23148318752965414660522976895342715316, 175024877143863308838121164427112023613, 65455761812920253083377790817920776573, 80719540338474829645398437070034720831, 30265965699771257065456865006701716765, 65059939935789446486144438246062876092, 73412866905686210879034022846184532326, 15496701729614207294798217895713334887, 187927627267834618400725936475600101114, 51596647781094949272750319452641626273, 34936140628305933632043160171328035222, 227636764157434931002269545814362460674, 40310006070105509297521946935690917605, 158199753843142436703851148792841271128, 62042757128547429572711130709028563495, 4994577444829057554913018889328955170, 212120189443587994433256482180523602261, 217895194863190305322702282626368171628, 14347476896110839167629995111874858945, 92969919157086783529099464146480743366, 98421412347381684974090776331896675353, 7670925013164447091797082877392123056, 92948133844692458289793766569490521009, 37843417064278243200690200946062028300, 73246898716604229152337274167003969610, 48576361100747286180365305580004800549, 226777738621945748117749363525255463543, 202653365491161565340111352416610858743, 81343653066789166205981715378171248137, 43128791749740623981661146675626160300, 51978531727836859802588862475571492852, 158673579466427370117557321982403789903, 154526278040551924441198621574328917110, 23520842615720508371775640194790884675, 76597771060168857457987619277375824543, 229138699410621489832761240581614631878, 62849396773221525801893336014307910648]
#229934842599910967421870245339955481121

本题RLWE问题,换了一个新新新新的环: \[ \frac{\mathbb{Z}_p[x]}{x^{64}+2x^{63}+2x^{61}+4x^{60}+...++2x^3+2x+4} \] 如此逆天复杂的环我更不可能去手推关系了

于是我们可以模拟上述过程,构造的矩阵方程要把As多项式的乘法分离开来,也就是让\(s_ix^i\)分别乘以对应的\(A\)多项式,sage能帮我们直接在环上计算出来,最后只需.list()一下就能得到对应的矩阵的一行,把每一行算一遍就能直接得到想要的\(\mathbf{M}\)矩阵,就像这样:

1
2
3
4
M = []
for i in range(n):
M.append((PR(A) * x**i).list())
M = matrix(M)

PR是原题的环,我们直接拿过来用

最后整体的攻击脚本如下:

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
#sage
# 原题给出的数据
n = 64
p =
A =
B =
beta = 2

# 原题的环直接搬来
PRp.<x> = PolynomialRing(Zmod(p))
newyear = [2,0,2,4]
f = x^n
for i in range(n):
f += newyear[i%4]*x^(n-1-i) #welcome to 2024!
PR = PRp.quo(f)
assert f.is_irreducible()

# 构造方程矩阵
M = []
for i in range(n):
M.append((PR(A) * x**i).list())
M = matrix(M)

# 构造格
N = matrix(2*n+1)
N[-1,-1] = beta
for i in range(n):
N[i,i+n] = 1
N[i+n,i] = p
N[-1,i] = B[i]
for j in range(n):
N[i,j] = M[i,j]
L = N.LLL()

# 提取flag
res = L[0] * N^(-1)
for i in res[:n]:
print(chr(abs(i)),end='')
print()
# NSSCTF{!!!Hah4H@,H0p3_Y0U_h@ve_fang_In_y0ur_po1Ynomial_5tudY!!}

[watevrCTF 2019]Baby RLWE

Mateusz carried a huge jar of small papers with public keys written on them, but he tripped and accidentally dropped them into the scanner and made a txt file out of them! D: Note: This challenge is just an introduction to RLWE, the flag is (in standard format) encoded inside the private key. 得到的flag请使用NSSCTF{}格式提交.

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
from sage.stats.distributions.discrete_gaussian_polynomial import DiscreteGaussianDistributionPolynomialSampler as d_gauss

flag = bytearray(raw_input())
flag = list(flag)

n = len(flag)
q = 40961

## Finite Field of size q.
F = GF(q)

## Univariate Polynomial Ring in y over Finite Field of size q
R.<y> = PolynomialRing(F)

## Univariate Quotient Polynomial Ring in x over Finite Field of size 40961 with modulus b^n + 1
S.<x> = R.quotient(y^n + 1)

def gen_small_poly():
sigma = 2/sqrt(2*pi)
d = d_gauss(S, n, sigma)
return d()

def gen_large_poly():
return S.random_element()


## Public key 1
a = gen_large_poly()

## Secret key
s = S(flag)


file_out = open("downloads/public_keys.txt", "w")
file_out.write("a: " + str(a) + "\n")


for i in range(100):
## Error
e = gen_small_poly()

## Public key 2
b = a*s + e
file_out.write("b: " + str(b) + "\n")

file_out.close()

附件给了一大堆数据,最高次为\(103\),易得n = 104

错误向量使用高斯随机产生了一百份,使用统计学原理拟合出\(\mu\)

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
import re
import numpy as np
from scipy import stats

with open(R"E:\Desktop\public_keys.txt",'r') as f:
lines = f.readlines()

a = eval('[' + re.sub(R"(\*x)(\^\d{0,3})? \+ ",',',lines[0][2:-1]) + ']')
b_with_error = []
for line in lines[1:]:
b_i = eval('[' + re.sub(R"(\*x)(\^\d{0,3})? \+ ",',',line[2:-1]) + ']')
b_with_error.append(b_i)

sigma = 2 / np.sqrt(2 * np.pi)

ASinv = []
n = len(b_with_error[0])
for i in range(n):
c = [b_with_error[j][i] for j in range(len(b_with_error))]
mean_estimate = np.mean(c)
z_score = stats.norm.ppf(0.975) # Z分数,双尾检验
margin_of_error = z_score * (sigma / np.sqrt(n))
confidence_interval = (mean_estimate - margin_of_error, mean_estimate + margin_of_error)
print(f"{round(mean_estimate)} -> {confidence_interval}")
ASinv.append(round(mean_estimate))
print(ASinv[::-1])
# [19377, 23165, 37067, 3532, 4901, 27850, 17381, 13994, 14804, 14613, 21987, 32414, 20692, 2804, 2289, 505, 3055, 40921, 39716, 7591, 39442, 19809, 13167, 14626, 17449, 19389, 33938, 6596, 1511, 38417, 36982, 17484, 25307, 7551, 30476, 2877, 28044, 27571, 1680, 6856, 36442, 16447, 23950, 26880, 18973, 37133, 27117, 22903, 24708, 17621, 39322, 13622, 36029, 19608, 18427, 19155, 40445, 32775, 7806, 21743, 22269, 7206, 32819, 10838, 22540, 38329, 32461, 12991, 15913, 11568, 38725, 36126, 35374, 24753, 16062, 21813, 21778, 35299, 28801, 20632, 14871, 28715, 18118, 35386, 37811, 28038, 40385, 11055, 37769, 25534, 38735, 33522, 35690, 541, 14861, 1842, 40431, 10213, 20706, 32260, 25534, 39178, 17956, 322]

检查置信区间没有特别离谱的数据

如此就可以拟合出实际的\(\mathbf{b} = \mathbf{a}*\mathbf{s}\)多项式系数

于是构造格 $$ \[\begin{bmatrix} -\mathbf{s}, \mathbf{k}, 1 \end{bmatrix}\] \[\begin{bmatrix} \mathbf{M} & \mathbf{I} & \\ q\mathbf{I} & & \\ \mathbf{b} & & \beta \end{bmatrix}\]

=

\[\begin{bmatrix} \mathbf{0},\mathbf{s},\beta \end{bmatrix}\]

$$ 发现无法规约出合适的\(\mathbf{w}\),以为是需要配平,也无法得到正常的解,试图配平也无效

找到官解发现

\(\mathbf{b}\)\(\mathbf{a}\)都已知了,直接除过去不就行了……

emmm……

1
2
3
4
5
6
7
8
9
10
11
12
# sage
AS =
n = 104
q = 40961
F = GF(q)
R.<y> = PolynomialRing(F)
S.<x> = R.quotient(y^n + 1)
a_s = S(AS)
a =
s = a_s / a
print(''.join(map(chr,s.list())))
# watevr{rlwe_and_statistics_are_very_trivial_when_you_reuse_same_private_keys#02849jedjdjdj202ie9395u6ky}

[L3hctf2024] badrlwe

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
from Crypto.Util.number import *
from random import *
import random
import numpy as np
from secret import flag, sk

assert len(flag[7:-1]) <= 64
data = [ord(i) for i in list(flag[7:-1])]

for i in range(len(sk)):
assert data[i] == sk[i]

q = 1219077173
R.<x> = PolynomialRing(Zmod(q), 'x')
N = 1024

f = x^N - 1

a = [0] * N
for i in range(N):
a[i] = randint(0, q)

s = sk

e = [0] * N
for i in range(N):
val = np.random.normal(loc=0.0, scale=2.1, size=None)
e[i] = int(val)

a = R(a)
s = R(s)
e = R(e)
b = (a*s + e)%f

print(a)
print(b)
print(q)

乍一看这个 N=1024也太大了整个,这解一下先不说能不能,LLL的时间都够我吃一顿的了。

再仔细看的话,有一句assert len(flag[7:-1]) <= 64,也就是多项式s其实很小,如果还是按照先前通解的思维,可以把多项式s看成高位系数都是01023阶多项式,构造2*1024+1阶矩阵,应该不行。根据先前[DUTCTF2024] Rolling Lava Waves Erupt的反思,列线性方程尽量体现所有的关系,给规约充分的机会,还是列竖式: \[ \begin{matrix} A_{0} s_{0} & A_{1} s_{0} & A_{2} s_{0} & \cdots & \cdots & A_{1022} s_{0} & A_{1023} s_{0}\\ & A_{0} s_{1} & A_{1} s_{1} & A_{2} s_{1} & \cdots & \cdots & A_{1022} s_{1} & A_{1023} s_{1}\\ & & \ddots & \ddots & \ddots & \ddots & \ddots & \ddots & \ddots \\ &&&&& A_{0} s_{1022} & A_{1} s_{1022} & A_{2} s_{1022} & \cdots & \cdots & A_{1022} s_{1022} & A_{1023} s_{1022}\\ &&&&&& A_{0} s_{1023} & A_{1} s_{1023} & A_{2} s_{1023} & \cdots & \cdots & A_{1022} s_{1023} & A_{1023} s_{1023}\\ \end{matrix} \] 由于s是小多项式,从\(s_{64}\)\(s_{1023}\)确定都是\(0\),因此该竖式可以简化到只有前\(64\)行,取多项式模后得到一个\(64 \times 1024\)的矩阵\(M\),任取其中连续的\(64 \times 64\)的子矩阵都可以体现\(s_0\)~\(s_{63}\)的全部信息,于是理同前: $$ \[\begin{bmatrix} \mathbf{s}, \mathbf{k},-1 \end{bmatrix}\] \[\begin{bmatrix} \mathbf{M} & \mathbf{I} & \\ q\mathbf{I} & & \\ \mathbf{b} & & \beta \end{bmatrix}\]

=

\[\begin{bmatrix} \mathbf{e},\mathbf{s},\beta \end{bmatrix}\]

$$

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
q = 1219077173
n = 64
N = 1024
beta = 2

R.<x> = PolynomialRing(Zmod(q), 'x')
f = x^N - 1

a =
b =
A = a.list()
B = b.list()

M = []
for i in range(n):
M.append((a * x**i % f).list())
M = matrix(M)

N = matrix(2*n+1)
N[-1,-1] = beta
for i in range(n):
N[i,i+n] = 1
N[i+n,i] = q
N[-1,i] = B[i]
for j in range(n):
N[i,j] = M[i,j]
L = N.LLL()

# 提取flag
print(''.join(map(chr,map(abs,L[0][n:2*n]))))
# Y0u_R@411Y_Kn0w_CyclOtom1c_Poly!AK@Co!<J>^5#DQ}oDo=o(j7$%<1T8h1r

把flag多余部分剃掉就行了,或者。。都是明文?

参考学习

成功领我入门的文章(本文一开始就是照着这位大佬的写的)

超赞超详细的一本书(就是太贵了)

  • An Introduction to Mathematical Cryptography (2014) - Hoffstein, Pipher, Silverman

很基础很详细的教学(虽然有点不适合我)

超棒的Manim动画演示LLL算法(有做汉化搬运的打算):

NSSCTF的那三道RLWE题目!让我对RLWE攻击原理有了新的认知:

__END__