# bob_enc

# 知识点

  • 格密码难题 CVP
  • 域上解线性方程组

# Problem

from secret import *
import random
prime =  2141
print len(flag)
flag = map(ord,flag)
flag1 = flag[:21] #前 21 个
flag2 = flag[21:] #后面部分
row = 64
def add(msg1,msg2):
    return [(x+y)%prime for x,y in zip(msg1,msg2)]
def multi(msg1,msg2):
    out = []
    for l in msg1:
        s = 0
        for x,y in zip(l,msg2):
            s += (x*y)%prime
            s %= prime
        out.append(s)
    return out
def genkey(leng):
    l = [[] for i in range(row)]
    for x in range(row):
        for i in range(leng):
            l[x].append(random.randint(0,511))
    return l
key = genkey(len(flag1))#64 组长度为 len (flag1) 的 (0,511) 之间的随机数
print key
cipher1 = multi(key,flag1)
print cipher1
cipher2 = multi(key,flag2)
noise = [random.randint(0,6) for i in range(row)]
print add(noise,cipher2)

# Solve

已知 keycipher1cipher2

根据题目,发现 key 是根据 flag1 生成的一个64×2164\times 21​大小的矩阵,记作KK​

flag1 看做一个21×121\times 1 的列向量X1X_1,那么 cipher1 就是KX1KX_1 两个矩阵的乘积,得到一个64×164 \times 1 的列向量AA)

那么解 flag1 就是解方程组KX1=AKX_1=A,得到X1X_1 即可

key=[]
chiper1=[]
prime=2141
B=Martix(Zmod(prime),chiper1) #定义一个 prime 域上的 1x24 的矩阵
K=Matrix(Zmod(prime),key)
B=B.T #转换为 64x1
flag1=''
X1=K.solve_right(B)
for i in range(0,21):
    flag1+=chr(X1[i,0])
print(flag1)
#flag1=flag{14e6f236-9eb9-46

chiper2 基本上和 chiper1 的处理过程是一样的,不过在得到 chiper2 的列向量后,对列向量每个元素加入了一定的噪音。即每个元素在原来的基础上偏移了一点点,所以我们需要在给定的加了噪音的向量的基础上,还原出原来的列向量,并且这个加了噪音的列向量和原来的列向量之间的距离要尽可能小。这就变成了Lattic\text{Lattic} 中的CVP\text{CVP} 问题。

具体地,记加了噪音后的列向量为BB^{'},原来的列向量为BBflag2 记作X2X_2,则KX2=BKX_2=BBBB\rightarrow B^{'}

然后这类问题也叫做LWE\text{LWE} 容错学习问题,具体就是给一个矩阵An×mA_{n\times m},一个向量Xm×1X_{m\times 1} 和一个噪音向量En×1E_{n\times 1}LWE\text{LWE} 系统输出B=AX+EB=AX+E,现在给出B,A,EB,A,E,要求还原XX

这类问题构造格的形式是

AEB0A \quad E\\ B \quad 0

而在本题中构造一个单位矩阵 $ pI_{64\times64}$ 作为基向量,那么格的矩阵就是

[pIKT](3)\left[ \begin{matrix} pI\\ K^T \end{matrix} \right] \tag{3}

然后求这个矩阵的格规约基,再用格规约基和Babai\text{Babai} 算法从BB^{'} 得到BB

from sage.modules.free_module_integer import IntegerLattice
key = []
chiper2 =[]
chiper1=[]
p=2141
A=Matrix(Zmod(p),chiper1)
A=A.T
K=Matrix(Zmod(p),key)
X1=K.solve_right(A)
flag1=''
for i in range(0,21):
    flag1+=chr(X1[i,0])
print(flag1)
#flag{14e6f236-9eb9-46
def Babai_closest_vector(basis, v):
    """Returns an approximate CVP solution using Babai's nearest plane algorithm.
    """
    BL = basis.LLL()
    # 施密特正交化基
    G, _ = BL.gram_schmidt()
    _, n = BL.dimensions()
    small = vector(ZZ, v)
    for i in reversed(range(n)):
        c = QQ(small * G[i]) / QQ(G[i] * G[i])
        c = c.round()
        small -= BL[i] * c
    return (v - small).coefficients()
base=matrix.identity(64)*p
KK=Matrix(ZZ,key)
KK=KK.T
C=block_matrix([[base],[KK]])
print(C.nrows())
lattice = IntegerLattice(C, lll_reduce=True)
print("LLL done")
BB=vector(ZZ,chiper2)
B = Babai_closest_vector(lattice.reduced_basis,BB)
print("Closest Vector: {}".format(B))
X2=K.solve_right(B)
print("X2: {}".format(X2))
flag2=''
for i in range(len(X2)):
    flag2+=chr(X2[i])
print(flag2)
#fc-b636-4c54c3732e5f
#flag{14e6f236-9eb9-46fc-b636-4c54c3732e5f

# notKnapsack

# 知识点

  • 群换阶
  • 离散对数
  • 同余方程组

# Problem

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import random
from Crypto.Util.number import bytes_to_long
from secret import FLAG
assert FLAG.startswith(b'flag{') and FLAG.endswith(b'}')
q = 210767327475911131359308665806489575328083
#bin () 返回一个整数 int 或者长整数 long int 的二进制表示
#bytes_to_long (ab)=24930  发现 97 * 2^8 + 98 = 24930
flag_bin = bin(bytes_to_long(FLAG[5:-1]))[2:]
l = len(flag_bin)
n = random.randint(l, 2*l)
cipher = []
for _ in range(n):
    r = [random.randint(2, q-2) for _ in range(l)]
    s = 1
    for i in range(l):
        s = s * r[i] ** int(flag_bin[i]) % q
    cipher.append([r, s])
with open('output.txt', 'w') as f:
    f.write(str(cipher))

# Solve

flag 的第ii 位是mim_i 且长度为lenlen,则会生成nn 组同余方程,第ii 组是

si=j=1lenrijmjmodqmj{0,1}s_i=\prod_{j=1}^{len}r_{ij}^{m_j}\mod{q}\\ m_j\in \left\{0,1\right\}

由于连乘不好处理,考虑利用对数的形式,把上式变成和式的形式。这里就用到了离散对数的知识

离散对数问题:当模mm 有原根的时候,设ll 为模mm 的一个原根,即lϕ(m)1modml^{\phi(m)}\equiv 1\mod{m},则当xlkmod(m)x\equiv l^k\mod(m) 时:

Indlxkmod(ϕ(m))\text{Ind}_l x\equiv k\mod(\phi(m))

此处的Indlx\text{Ind}_lx​xx​以整数ll​为底,模ϕ(m)\phi(m)​时的离散对数值

常见的性质有

IndlxyIndlx+Indlymodϕ(m)IndlxyyIndlxmodϕ(m)\text{Ind}_lxy\equiv \text{Ind}_lx+\text{Ind}_ly\mod{\phi(m)}\\ \text{Ind}_lx^y\equiv y\text{Ind}_lx\mod{\phi(m)}

注意到,这是一个在有限域Zq\mathbb{Z}_q​上的运算,那么这个域的阶是qq​,并且如果我们把最后利用离散对数,就转换到ϕ(q)=q1\phi(q)=q-1​下的有限域Zq1\mathbb{Z}_{q-1}​下的运算。

假设ggZq\mathbb{Z}_{q} 的一个生成元,则原式可以表示为

gbigj=1lenaijmjmodϕ(q)modqgbisimodq,gaijmjrijmodqbij=1lenaijmjmodq1BAXmodq1g^{b_i}\equiv g^{\sum_{j=1}^{len}a_{ij}m_j\mod{\phi(q)}}\mod{q}\\ g^{b_i}\equiv s_i \mod{q}, \quad g^{a_{ij}m_j}\equiv r_{ij} \mod{q}\\ b_i\equiv \sum_{j=1}^{len}a_{ij}m_j\mod{q-1}\\ B\equiv AX \mod{q-1}

考虑将q1q - 1 分解,得到q1=2×331×318379648755152766403789525387446488411q-1=2 \times331\times318379648755152766403789525387446488411,因为XX 中元素的取值只有0011,所以我们考虑直接取mod2\mod{2} 来运算,实际上如果abmodpqa\equiv b\mod{pq},那么ab=kpqa-b=kpq,两边同时模pp 可以得到abmodpa\equiv b\mod{p},所以BAXmodq1B\equiv AX \mod{q-1} 可以得到 B\equiv AX \mod

域包含群的性质,如果从群即Zq1\mathbb{Z}_{q-1}​上的乘法群方面考虑,假设这个群的生成元是gg​g=q1|g|=q-1​,则这个群中其他的元素xx​,假设x=gpx=g^p​,则有

x=gp=q1gcd(p,q1)|x|=|g^p|=\frac{q-1}{\gcd(p,q-1)}

那么如果两边同时乘q12\frac{q-1}{2}​,那么这个群就可以转化为阶为22​的子群<gq12><g^{\frac{q-1}{2}}> ​,因为q1gcd(q12,q1)=2\frac{q-1}{\gcd(\frac{q-1}{2},q-1)}=2​

那么方程两边同时乘以q12\frac{q-1}{2}​后取离散对数就变成了求解线性方程组的问题,令q12=t\frac{q-1}{2}=t​,则方程变为

Indgtsij=1lenmiIndgtrijmod2\text{Ind}_{g^t}s_i\equiv \sum_{j=1}^{len}m_i\text{Ind}_{g^t}r_{ij} \mod{2}

Indgtsi\text{Ind}_{g^t}s_i​Indgtrij\text{Ind}_{g^t}r_{ij}​可以利用勒让德符号求出

计算(ap)\left(\frac{a}{p}\right) 有一个简单的公式

(ap)ap12modp\left(\frac{a}{p}\right)\equiv a^{\frac{p-1}{2}}\mod{p}

from Crypto.Util.number import *
def legendre(a,p):
    if pow(a,(p-1)//2,p)==1:
        return 1
    else:
        return 0
f=open('output.txt','rb')
q = 210767327475911131359308665806489575328083
out=eval(f.read())
A=Matrix(GF(2),len(out))
v=vector(GF(2),len(out))
for i in range(len(out)):
    r,s=out[i]
    for j in range(len(r)):
        A[i,j]=legendre(r[j],q)
        v[i]=legendre(s,q)+1
x=A.solve_right(v)
x=''.join(map(str,x))
print(long_to_bytes(int(x,2)))
# flag{4cc78358-ce69-4539-a33a-2c44433414ab}

感谢 ToverPomelo 的解答