DASCTF2024最后一战Crypto
数论的香氛
DASCTF题目质量都非常高
task.py
from sympy import isprime
from sympy.ntheory import legendre_symbol
import random
from Crypto.Util.number import bytes_to_long
k=79 #<-- i couldn't stress more
def get_p():
global k
while True:
r=random.randint(2**69,2**70)
p=2**k*r+1
if isprime(p):
return p
else:
continue
def get_q():
while True:
r=random.randint(2**147,2**148)
q=4*r+3
if isprime(q):
return q
else:
continue
def get_y():
global n,p,q
while True:
y=random.randint(0,n-1)
if legendre_symbol(y,p)==1:
continue
elif legendre_symbol(y,q)==1:
continue
else:
return y
flag=b'DASCTF{redacted:)}'
flag_pieces=[flag[0:10],flag[11:21],flag[22:32],flag[33:43],flag[44:]]
#assert int(bytes_to_long((flag_pieces[i] for i in range(5)))).bit_length()==k
p=get_p()
q=get_q()
n=p*q
print(f'{n=}')
y=get_y()
print(f'{y=}')
def encode(m):
global y,n,k
x = random.randint(1, n - 1)
c=(pow(y,m,n)*pow(x,pow(2,k),n))%n
return c
cs=[]
for i in range(len(flag_pieces)):
ci=encode(bytes_to_long(flag_pieces[i]))
cs.append(ci)
print(f'{cs=}')
'''
n=542799179636839492268900255776759322356188435185061417388485378278779491236741777034539347
y=304439269593920283890993394966761083993573819485737741439790516965458877720153847056020690
cs=[302425991290493703631236053387822220993687940015503176763298104925896002167283079926671604, 439984254328026142169547867847928383533091717170996198781543139431283836994276036750935235, 373508223748617252014658136131733110314734961216630099592116517373981480752966721942060039, 246328010831179104162852852153964748882971698116964204135222670606477985487691371234998588, 351248523787623958259846173184063420603640595997008994436503031978051069643436052471484545]
'''
这题n比较小,可以分解,然后算勒让德符号发现都是-1观察一下这题形式Glodwasser-Micali公钥加密有点相似,但不同的是,有个pow(2,k),而且GM每次只加密1bit。检索会发现这是一道论文题,GM的plus版本435.pdf

exp
from sympy import mod_inverse
from Crypto.Util.number import *
def decrypt(c, p, k, D):
m = 0
B = 1
C = pow(c, (p - 1) // (2**k), p)
for j in range(1, k):
z = pow(C, 2**(k-j), p)
if z != 1:
m += B
C = (C * D) % p
B <<= 1 # B = 2 * B
D = (D * D) % p
if C != 1:
m += B
return m
q = 863327174253852394776516978368858092781662547
p = 628729403897154553626034231171921094272614401
n = 542799179636839492268900255776759322356188435185061417388485378278779491236741777034539347
y = 304439269593920283890993394966761083993573819485737741439790516965458877720153847056020690
k = 79
k = 79
D = pow(y, -(p - 1) // 2**k, p)
print(D)
cs = [
302425991290493703631236053387822220993687940015503176763298104925896002167283079926671604,
439984254328026142169547867847928383533091717170996198781543139431283836994276036750935235,
373508223748617252014658136131733110314734961216630099592116517373981480752966721942060039,
246328010831179104162852852153964748882971698116964204135222670606477985487691371234998588,
351248523787623958259846173184063420603640595997008994436503031978051069643436052471484545
]
decrypted_messages = [decrypt(ci, p, k, D) for ci in cs]
for i, m in enumerate(decrypted_messages):
print(f"Decrypted message {i}: {m}")
print(long_to_bytes(m))
矩阵创造师
task.py
from sympy import isprime
import time
import math
import random
from sympy import GF, invert
import logging
import numpy as np
from sympy.abc import x
from sympy import ZZ, Poly, degree
from sympy.polys.polyerrors import NotInvertible
# 设置打印选项以显示完整数组
np.set_printoptions(threshold=np.inf)
# 自定义的素数检查函数
def is_prime(n):
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True
# 检查一个数是否为2的幂
def is_2_power(n):
return n != 0 and (n & (n - 1) == 0)
# 随机生成多项式
def random_poly(length, d, neg_ones_diff=0):
return Poly(np.random.permutation(
np.concatenate((np.zeros(length - 2 * d - neg_ones_diff),
np.ones(d), -np.ones(d + neg_ones_diff)))),
x).set_domain(ZZ)
# 多项式求逆函数
def invert_poly(f_poly, R_poly, p):
log = logging.getLogger("mathutils")
inv_poly = None
if is_prime(p):
log.debug("Inverting as p={} is prime".format(p))
inv_poly = invert(f_poly, R_poly, domain=GF(p))
elif is_2_power(p):
log.debug("Inverting as p={} is 2 power".format(p))
inv_poly = invert(f_poly, R_poly, domain=GF(2))
e = int(math.log(p, 2))
for i in range(1, e):
log.debug("Inversion({}): {}".format(i, inv_poly))
inv_poly = ((2 * inv_poly - f_poly * inv_poly ** 2) % R_poly).trunc(p)
else:
raise Exception("Cannot invert polynomial in Z_{}".format(p))
log.debug("Inversion: {}".format(inv_poly))
return inv_poly
# 生成循环移位矩阵
def shift_matriks(arr):
F = np.zeros((len(arr), len(arr)))
for i in range(len(arr)):
for j in range(len(arr)):
if i == 0:
F[i,j] = arr[j]
else:
F[i,j] = F[(i-1)%len(arr)][(j-1)%len(arr)]
return np.matrix(F)
# 矩阵模运算
def matrikskalimod(f, g, b):
result = g @ f
n = len(result)
for i in range(n):
for j in range(n):
result[i,j] = np.remainder(result[i,j], b)
if result[i,j] > b/2:
result[i,j] = result[i,j] - b
return result
# 向量模运算
def vektorkalimod(f, x, b):
result = x @ f
n = result.size
for i in range(0, n):
result[0,i] = np.remainder(result[0,i], b)
if result[0,i] > b/2:
result[0,i] = result[0,i] - b
return result
# 向量相加并模运算
def vektortambahmod(x, y, b):
result = np.add(x, y)
n = result.size
for i in range(n):
result[0,i] = np.remainder(result[0,i], b)
if result[0,i] > b/2:
result[0,i] = result[0,i] - b
return result
# 生成私钥
def gen_keypriv(n, p, q):
log = logging.getLogger("mathutils")
R_poly = Poly(x**n - 1, x, domain='ZZ') # 定义多项式环
tester = None
g_poly = random_poly(n, int(math.sqrt(q))) # 随机生成多项式 g
deg_g = degree(g_poly, gen=x) # 获取多项式 g 的次数
g_arr = g_poly.all_coeffs()[::-1] # 获取多项式 g 的系数并反转
if deg_g != n-1:
for _ in range(n-1 - deg_g ):
g_arr.append(int(0))
g = shift_matriks(g_arr) # 生成 g 的移位矩阵
while tester is None:
f_poly = random_poly(n, n // 3, neg_ones_diff=-1) # 随机生成多项式 f
try:
f_p_poly = invert_poly(f_poly, R_poly, p) # 求 f 在模 p 下的逆
f_q_poly = invert_poly(f_poly, R_poly, q) # 求 f 在模 q 下的逆
tester = f_p_poly * f_q_poly # 计算测试值
except NotInvertible as ex:
log.debug(ex) # 记录调试信息
if tester is None:
raise Exception("无法生成多项式 f")
deg_f = degree(f_poly, gen=x) # 获取多项式 f 的次数
f_arr = f_poly.all_coeffs()[::-1] # 获取系数并反转
if deg_f != n-1:
for _ in range(n-1 - deg_f):
f_arr.append(int(0))
f = shift_matriks(f_arr) # 生成 f 的移位矩阵
deg_f_p = degree(f_p_poly, gen=x) # 获取 f_p 的次数
f_p_arr = f_p_poly.all_coeffs()[::-1] # 获取 f_p 的系数并反转
if deg_f_p != n-1:
for _ in range(n-1 - deg_f_p):
f_p_arr.append(int(0))
f_p = shift_matriks(f_p_arr) # 生成 f_p 的移位矩阵
deg_f_q = degree(f_q_poly, gen=x) # 获取 f_q 的次数
f_q_arr = f_q_poly.all_coeffs()[::-1] # 获取 f_q 的系数并反转
if deg_f_q != n-1:
for _ in range(n-1 - deg_f_q):
f_q_arr.append(int(0))
f_q = shift_matriks(f_q_arr) # 生成 f_q 的移位矩阵
np.savez('privkey.npz', f=f, g=g, f_p=f_p, f_q=f_q) # 保存私钥
return f, g, f_p, f_q
# 生成公钥
def gen_keypub(priv, q):
f_q = priv[3] # 从私钥中获取 f_q
g = priv[1] # 从私钥中获取 g
h = matrikskalimod(f_q, g, q) # 计算公钥
return h
# 随机生成 r
def randomrgenerator(n, p):
arr_r = []
l = [-p, 0, p] # 随机取值的选项
for i in range(n):
x = random.choice(l) # 随机选择
arr_r.append(x)
return np.array(arr_r)
# 生成多项式 r
def poly_r(n, p):
r = randomrgenerator(n, p) # 生成随机 r
return r
# 加密函数
def enkripsi(pub, m, r, q):
size = m.shape[0] # 获取消息大小
c = np.copy(m) # 创建消息的副本
for i in range(size):
c_temp = vektorkalimod(pub, r, q) # 计算临时向量
c_cipherteks = vektortambahmod(m[i], c_temp, q) # 加密
c[i] = c_cipherteks # 更新密文
c = np.ravel(c) # 拉平密文数组
return c
# 字符串转二进制
def strToBinary(s):
bin_conv = []
arr_m = []
byte_array = s.encode() # 编码为字节
binary_int = int.from_bytes(byte_array, "little") # 转换为整数
binary_string = bin(binary_int) # 转换为二进制字符串
for c in binary_string[2:]: # 去掉 "0b" 前缀
bin_conv.append(c)
for i in bin_conv:
for j in i:
j = int(j)
arr_m.append(j)
return arr_m
# 二进制转字符串
def bintostr(result):
arr = ''
size = len(result)
for i in range(size):
int_ = int(result[i])
str_ = str(int_)
arr += str_
binary_int = int(arr, 2) # 转换为整数
byte_number = binary_int.bit_length() + 7 // 8 # 计算字节数
binary_array = binary_int.to_bytes(byte_number, byteorder="little") # 转换为字节数组
ascii_text = binary_array.decode() # 解码为ASCII文本
return ascii_text.replace('\x00','') # 去掉空字节
# 自定义消息大小
def custom_message_size(m, n):
m_size_temp = len(m) % n
if m_size_temp == 0:
m_add_size = 0
else:
m_add_size = n - m_size_temp # 计算填充大小
for _ in range(m_add_size):
m.append(0) # 填充0
m_row_size = len(m) // n # 计算行数
m_arr = np.array(m).reshape((m_row_size, n)) # 重塑数组
return m_arr, m_add_size, m_row_size
# 从文件读取消息
def readmessage(text_file):
message=''''''
with open(text_file) as file:
line = file.readlines()
for text in line:
message += text # 读取所有行
return message
# 主函数
def main(params, message):
n, p, q = map(int, params) # 解析参数
print(f"The public parameters are: n={n}, p={p}, q={q}")
m, m_add_size = custom_message_size(strToBinary(message), n)[:2] # 转换消息为二进制并调整大小
start_time = time.time()
keypriv = gen_keypriv(n, p, q) # 生成私钥
keypub = gen_keypub(keypriv, q) # 生成公钥
r = poly_r(n, p) # 生成随机多项式 r
start_time_enkripsi = time.time()
e = enkripsi(keypub, m, r, q) # 加密消息
np.save('e.npy', e) # 保存密文
print("--- encryption time : %s miliseconds ---\n" %
((time.time() - start_time_enkripsi) * 1000))
# 运行主程序
if __name__ == '__main__':
params = list((503, 3, 251)) # 公共参数
message = readmessage('./flag.txt') # 读取消息
main(params, message) # 执行主功能
NTRU问题,可以参考NTRU密码体制笔记 | 独奏の小屋
这题目已经写的非常明显了,公钥就是\(pub=f_q*g\)然后私钥f在这个私钥生成的函数里面就告诉你发f,f_p,f_q
然后解密的话你只需要计算\(m=f_p*f*m(mod\ p)\)就行了
下面我们具体分析关键函数,注意看
def custom_message_size(m, n):
m_size_temp = len(m) % n
if m_size_temp == 0:
m_add_size = 0
else:
m_add_size = n - m_size_temp
for _ in range(m_add_size):
m.append(0)
m_row_size = len(m) // n
m_arr = np.array(m).reshape((m_row_size, n))
return m_arr, m_add_size, m_row_size
这里规定如果m的位数%n不是为0,那么就要填充n-m_size,我们提取完测定m长度是1006,n是503,于是m_add_size=471,等下我们在解密的时候要把这个去掉,因为这是无效的。同时注意到m_row_size是m的长度除以维度n。我们待加密的m就是m_row_siez行,n列的矩阵
加密后的c也是一样,所以我们知道c的矩阵是多少行多少列的。
再看
def shift_matriks(arr):
F = np.zeros((len(arr), len(arr)))
for i in range(len(arr)):
for j in range(len(arr)):
if i == 0:
F[i,j] = arr[j]
else:
F[i,j] = F[(i-1)%len(arr)][(j-1)%len(arr)]
return np.matrix(F)
我们私钥经过这样的变化,也就是说第0行是私钥,第2行是\(f_p\),我们解密过程是先求\(a\equiv fe\ mod\ p\)
然后再乘法\(f_p\)求解。
for i in range(c_row_size):
m_temp1 = vektorkalimod(priv[0], c[i], q)
m_plainteks = vektorkalimod(priv[2],m_temp1,p)
d[i] = m_plainteks
这几行代码等于说去掉后面填充的
d = np.ravel(d)
if size !=0:
d = d[slice(-size)]
d = bintostr(d)
exp
from sympy import isprime
import time
import math
import random
from sympy import GF, invert
import logging
import numpy as np
from sympy.abc import x
from sympy import ZZ, Poly, degree
from sympy.polys.polyerrors import NotInvertible
def is_prime(n):
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True
def is_2_power(n):
return n != 0 and (n & (n - 1) == 0)
def random_poly(length, d, neg_ones_diff=0):
return Poly(np.random.permutation(
np.concatenate((np.zeros(length - 2 * d - neg_ones_diff),
np.ones(d), -np.ones(d + neg_ones_diff)))),
x).set_domain(ZZ)
def invert_poly(f_poly, R_poly, p):
log = logging.getLogger("mathutils")
inv_poly = None
if is_prime(p):
log.debug("Inverting as p={} is prime".format(p))
inv_poly = invert(f_poly, R_poly, domain=GF(p))
elif is_2_power(p):
log.debug("Inverting as p={} is 2 power".format(p))
inv_poly = invert(f_poly, R_poly, domain=GF(2))
e = int(math.log(p, 2))
for i in range(1, e):
log.debug("Inversion({}): {}".format(i, inv_poly))
inv_poly = ((2 * inv_poly - f_poly * inv_poly ** 2) % R_poly).trunc(p)
else:
raise Exception("Cannot invert polynomial in Z_{}".format(p))
log.debug("Inversion: {}".format(inv_poly))
return inv_poly
def shift_matriks(arr):
F = np.zeros((len(arr), len(arr)))
for i in range(len(arr)):
for j in range(len(arr)):
if i == 0:
F[i,j] = arr[j]
else:
F[i,j] = F[(i-1)%len(arr)][(j-1)%len(arr)]
return np.matrix(F)
def matrikskalimod(f,g,b):
result = g @ f
n = len(result)
for i in range(n):
for j in range(n):
result[i,j] = np.remainder(result[i,j], b)
if result[i,j] > b/2:
result[i,j] = result[i,j] - b
return result
def vektorkalimod(f,x,b):
result = x @ f
n = result.size
for i in range(0,n):
result[0,i] = np.remainder(result[0,i],b)
if result[0,i] > b/2:
result[0,i] = result[0,i] - b
return result
def strToBinary(s):
bin_conv = []
arr_m = []
byte_array = s.encode()
binary_int = int.from_bytes(byte_array, "little")
binary_string = bin(binary_int)
for c in binary_string[2:]:
bin_conv.append(c)
for i in bin_conv:
for j in i:
j = int(j)
arr_m.append(j)
return arr_m
def bintostr(result):
arr = ''
size = len(result)
for i in range(size):
int_ = int(result[i])
str_ = str(int_)
arr += str_
binary_int = int(arr, 2)
byte_number = binary_int.bit_length() + 7 // 8
binary_array = binary_int.to_bytes(byte_number, byteorder="little")
ascii_text = binary_array.decode()
return ascii_text.replace('\x00','')
def custom_message_size(m, n):
m_size_temp = len(m) % n
if m_size_temp == 0:
m_add_size = 0
else:
m_add_size = n - m_size_temp
for _ in range(m_add_size):
m.append(0)
m_row_size = len(m) // n
m_arr = np.array(m).reshape((m_row_size, n))
return m_arr, m_add_size, m_row_size
def readmessage(text_file):
message=''''''
with open(text_file) as file:
line = file.readlines()
for text in line:
message += text
return message
def dekripsi(c, priv, size, n, p, q):
c_row_size=len(c)//n
c=c.reshape(c_row_size,n)
d=np.copy(c)
for i in range(c_row_size):
m_temp=vektorkalimod(priv[0],c[i], q)
m_plaintext=vektorkalimod(priv[2],m_temp, p)
d[i]=m_plaintext
d = np.ravel(d)
if size !=0:
d = d[slice(-size)]
d = bintostr(d)
return d
m_add_size=471
n=503
p=3
q=251
e = np.load('e.npy')
def load_privkey():
privkey_data = np.load('privkey.npz')
return np.matrix(privkey_data['f']), np.matrix(privkey_data['g']), np.matrix(privkey_data['f_p']), np.matrix(privkey_data['f_q'])
privkey = load_privkey()
start_time_dekripsi = time.time()
d = dekripsi(e, privkey, m_add_size, n, p, q)
print(f"{d=}")
#d='DASCTF{h0w_bri1li3nt!_y0u_c@n_t0lt@ly_b3c0m3_a_crypt0gr@ph3r!}'
the_last_challenge_attack
后续更新

浙公网安备 33010602011771号