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

后续更新

posted @ 2025-03-25 19:59  ink599  阅读(305)  评论(0)    收藏  举报