# 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
已知 key
、 cipher1
和 cipher2
根据题目,发现 key
是根据 flag1
生成的一个大小的矩阵,记作。
将 flag1
看做一个 的列向量,那么 cipher1
就是 两个矩阵的乘积,得到一个 的列向量)
那么解 flag1
就是解方程组,得到 即可
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
的列向量后,对列向量每个元素加入了一定的噪音。即每个元素在原来的基础上偏移了一点点,所以我们需要在给定的加了噪音的向量的基础上,还原出原来的列向量,并且这个加了噪音的列向量和原来的列向量之间的距离要尽可能小。这就变成了 中的 问题。
具体地,记加了噪音后的列向量为,原来的列向量为, flag2
记作,则,。
然后这类问题也叫做 容错学习问题,具体就是给一个矩阵,一个向量 和一个噪音向量, 系统输出,现在给出,要求还原
这类问题构造格的形式是
而在本题中构造一个单位矩阵 $ pI_{64\times64}$ 作为基向量,那么格的矩阵就是
然后求这个矩阵的格规约基,再用格规约基和 算法从 得到
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
的第 位是 且长度为,则会生成 组同余方程,第 组是
由于连乘不好处理,考虑利用对数的形式,把上式变成和式的形式。这里就用到了离散对数的知识
离散对数问题:当模 有原根的时候,设 为模 的一个原根,即,则当 时:
此处的为以整数为底,模时的离散对数值
常见的性质有
注意到,这是一个在有限域上的运算,那么这个域的阶是,并且如果我们把最后利用离散对数,就转换到下的有限域下的运算。
假设 是 的一个生成元,则原式可以表示为
考虑将 分解,得到,因为 中元素的取值只有 和,所以我们考虑直接取 来运算,实际上如果,那么,两边同时模 可以得到,所以 可以得到 B\equiv AX \mod
域包含群的性质,如果从群即上的乘法群方面考虑,假设这个群的生成元是,,则这个群中其他的元素,假设,则有
那么如果两边同时乘,那么这个群就可以转化为阶为的子群,因为。
那么方程两边同时乘以后取离散对数就变成了求解线性方程组的问题,令,则方程变为
和可以利用勒让德符号求出
计算 有一个简单的公式
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 的解答