Hgame2023复现

  1. Week1
    1. RSA
    2. 神秘的电话
    3. Be Stream
    4. 兔兔的车票
  2. Week2
    1. Rabin
    2. RSA 大冒险1
    3. 包里有什么
  3. Week3
    1. ezDH
    2. RSA ⼤冒险2
      1. Challenge 1
      2. Challenge 2
      3. Challenge 3
    3. ezBlock
  4. week4
    1. LLLCG
    2. LLLCG Revenge
    3. ECRSA

Week1

RSA

基础的RSA题,题目也提示了整数分解,分解n得到p、q就能解了。

神秘的电话

一道令我无语的脑洞题。

附件:

encrypted_message:

5Yeg5Liq5pif5pyf5YmN77yM5oiR5Lus5pS25Yiw5LiA5Liq56We56eY55qE5raI5oGv44CC5L2G5piv6L+Z5Liq5raI5oGv6KKr6YeN6YeN5Yqg5a+G77yM5oiR5Lus5LiN55+l6YGT5a6D55qE55yf5q2j5ZCr5LmJ5piv5LuA5LmI44CC5ZSv5LiA55+l6YGT55qE5L+h5oGv5piv5YWz5LqO5a+G6ZKl55qE77ya4oCc5Y+q5pyJ5YCS552A57+76L+H5Y2B5YWr5bGC55qE56+x56yG5omN6IO95oq16L6+5YyX5qyn56We6K+d55qE57uI54K54oCd44CC

还有一段摩斯音频。

上面的密文能看出来是base64加密,解密得到:

⼏个星期前,我们收到⼀个神秘的消息。但是这个消息被重重加密,我们不知道它的真正含义是什

么。唯⼀知道的信息是关于密钥的:“只有倒着翻过⼗⼋层的篱笆才能抵达北欧神话的终点”。

这里我以为引号里的信息都是关于密钥的,没想到还涉及道密文的加密,属实被恶心了。

倒着:逆序加密;十八层的篱笆:W型栅栏(后面再说为什么是W型栅栏);北欧神话的终点:密钥vidar,对应维吉尼亚密码(航电工作室的名称)

用Audacity打开摩斯音频,写下摩斯密码再去解密得到:

0223e_priibly__honwa_jmgh_fakcqaoqtmfr

发现其长度为38,按照普通栅栏的思路无法将每一组分为18个字符,故猜测是W型栅栏。

对密文先逆序,在栅栏,最后维吉尼亚得到flag。

Be Stream

题目:

from flag import flag
assert type(flag) == bytes

key = [int.from_bytes(b"Be water", 'big'), int.from_bytes(b"my friend", 'big')]

def stream(i):
    if i==0:
        return key[0]
    elif i==1:
        return key[1]
    else:
        return (stream(i-2)*7 + stream(i-1)*4)

enc = b""
for i in range(len(flag)):
    water = stream((i//2)**6) % 256
    enc += bytes([water ^ flag[i]])

print(enc)
# b'\x1a\x15\x05\t\x17\t\xf5\xa2-\x06\xec\xed\x01-\xc7\xcc2\x1eXA\x1c\x157[\x06\x13/!-\x0b\xd4\x91-\x06\x8b\xd4-\x1e+*\x15-pm\x1f\x17\x1bY'

进行分析可知,当i从4开始,代入到函数里进行递归,会变得非常复杂,这时就需要我们简化这个过程–利用矩阵解方程的思想。

首先通过函数我们可以得到递推式:$S_{i}=4S_{i-1}+7S_{i-2}$,用矩阵相乘的思想来将这个方程转化为矩阵相乘的样子(表述不太标准)。关于这个思想可以参考这篇文章:斐波那契数列 矩阵求法 优化 - 旭东的博客 - 博客园 (cnblogs.com)

递推式转为矩阵相乘后:$\begin{bmatrix} S_{i} & S_{i-1} \end{bmatrix}=\begin{bmatrix} S_{i-1} & S_{i-2} \end{bmatrix}\begin{bmatrix} 4 & 1 \ 7 & 0 \end{bmatrix}$,$\begin{bmatrix} S_{i-1} & S_{i-2} \end{bmatrix}$这部分初始值即为$\begin{bmatrix} key[1] & key[0] \end{bmatrix}$

代码实现:

import numpy

key = [int.from_bytes(b"Be water", 'big'), int.from_bytes(b"my friend", 'big')]

enc = b'\x1a\x15\x05\t\x17\t\xf5\xa2-\x06\xec\xed\x01-\xc7\xcc2\x1eXA\x1c\x157[\x06\x13/!-\x0b\xd4\x91-\x06\x8b\xd4-\x1e+*\x15-pm\x1f\x17\x1bY'
flag = b''

A = numpy.matrix([[4,1],[7,0]])
M = numpy.matrix([[key[1],key[0]]])
print(A)
print(M)

for i in range(len(enc)):
    index = (i//2)**6
    if index < 2:
        water = key[index]%256
    else:
        S = M*A**(index-1)
        print(S[0][0,0] % 256)
        water = S[0][0,0] % 256
        # print(S%256)
    #print(water)
    flag += bytes([water ^ enc[i]])

print(flag)

兔兔的车票

题目:

from PIL import Image
from Crypto.Util.number import *
from random import shuffle, randint, getrandbits

flagImg = Image.open('flag.png')
width = flagImg.width
height = flagImg.height

def makeSourceImg():
    colors = long_to_bytes(getrandbits(width * height * 24))[::-1]
    img = Image.new('RGB', (width, height))
    x = 0
    for i in range(height):
        for j in range(width):
            img.putpixel((j, i), (colors[x], colors[x + 1], colors[x + 2]))
            x += 3
    return img

def xorImg(keyImg, sourceImg):
    img = Image.new('RGB', (width, height))
    for i in range(height):
        for j in range(width):
            p1, p2 = keyImg.getpixel((j, i)), sourceImg.getpixel((j, i))
            img.putpixel((j, i), tuple([(p1[k] ^ p2[k]) for k in range(3)]))
    return img
"""
source文件夹下面的图片生成过程:
def makeImg():
    colors = list(long_to_bytes(getrandbits(width * height * 23)).zfill(width * height * 24))
    shuffle(colors)
    colors = bytes(colors)
    img = Image.new('RGB', (width, height))
    x = 0
    for i in range(height):
        for j in range(width):
            img.putpixel((j, i), (colors[x], colors[x + 1], colors[x + 2]))
            x += 3
    return img

for i in range(15):
    im = makeImg()
    im.save(f"./source/picture{i}.png")
"""
n1 = makeSourceImg()
n2 = makeSourceImg()
n3 = makeSourceImg()
nonce = [n1, n2, n3]

index = list(range(16))
shuffle(index)
e=0


"""
这里flag.png已经提前被保存在source文件夹下了,文件名也是picture{xx}.png
"""

for i in index:
    im = Image.open(f"source/picture{i}.png")
    key = nonce[randint(0, 2)]
    encImg = xorImg(key, im)
    encImg.save(f'pics/enc{e}.png')
    e+=1

随机⽣成3张密钥图⽚作为nonce,然后把一张flag.png和15张picture{xx}.png打乱与nonce进⾏异或。因为只有三张nonce,⽽picture有15张,所以⼤概率有picture跟flag.png重⽤了同⼀张nonce。就可以⽤这两张加密图⽚进⾏XOR,得到:$flag.png ⊕picturex.png$,⽽再根据picture{x}.png的⽣成函数可以看到picture{xx}.png中有很多位等于0的点,那么就可以暴露出flag.png的特征。题⽬本⾝图⽚不多,可以进⾏爆破。

代码:

from PIL import Image
width = 379
height = 234

def xorImg(keyImg, sourceImg):
    img = Image.new('RGB', (width, height))
    for i in range(height):
        for j in range(width):
            p1, p2 = keyImg.getpixel((j, i)), sourceImg.getpixel((j, i))
            img.putpixel((j, i), tuple([(p1[k] ^ p2[k]) for k in range(3)]))
    return img

for i in range(16):
    for j in range(16):
        img1 = Image.open(f'pics/enc{i}.png')
        img2 = Image.open(f'pics/enc{j}.png')
        img = xorImg(img1, img2)
        img.save(f'res/img_{i}_{j}.png')

Week2

Rabin

雷宾加密,套用解密模板就行。

RSA 大冒险1

四部分RSA加密。

part1:

from Crypto.Util.number import *
from challenges import chall1_secret
class RSAServe:
    def __init__(self) -> None:
        self.e = 65537
        self.p = getPrime(128)
        self.q = getPrime(100)
        self.r = getPrime(100)
        self.m = chall1_secret

    def encrypt(self):
        m_ = bytes_to_long(self.m)
        c = pow(m_, self.e, self.p*self.q*self.r)
        return hex(c)

    def check(self, msg):
        return msg == self.m

    def pubkey(self):
        return self.p*self.q*self.r, self.e, self.p

我看到n由三部分组成,直接用yafu工具去解,倒是能分解出来,然后就能直接解出来了。

官方wp里说到,p的位数为128位,qr都是100位。可以合理猜测m <p,于是原先在ZN上的运算就可以直接转移到Zp上。解出来的答案确实这个意思,或许我误打误撞了。

part2和part4放一块,给自己提个醒,代码审计这块太不行了:

from Crypto.Util.number import *
from challenges import chall2_secret

class RSAServe:
    def __init__(self) -> None:
        self.p = getPrime(512)
        self.q = getPrime(512)
        self.e = 65537
        self.m = chall2_secret
    
    def encrypt(self):
        m_ = bytes_to_long(self.m)
        c = pow(m_ ,self.e, self.p*self.q)
        self.q = getPrime(512)
        return hex(c)

    def check(self, msg):
        return msg == self.m

    def pubkey(self):
        return self.p*self.q, self.e
from Crypto.Util.number import *
from challenges import chall4_secret

class RSAServe:
    def __init__(self) -> None:
        self.p = getPrime(512)
        self.q = getPrime(512)
        self.e = getPrime(17)
        self.m = chall4_secret
    
    def encrypt(self):
        m_ = bytes_to_long(self.m)
        c = pow(m_, self.e, self.p*self.q)
        self.e = getPrime(17)
        return hex(c)

    def check(self, msg):
        return msg == self.m

    def pubkey(self):
        return self.p*self.q, self.e

我压根就没注意到part2里的q和part4里的e每次都变化,导致我还在想到底是RSA里的哪种类型,蠢到家了。

part2因为q每次都变,p不变,所以获取两n,求gcd,p就出来了;part4里e每次变,导致c也每次变,但n不变,就是共模攻击了。对自己有点无语😶。

part3

from Crypto.Util.number import *
from challenges import chall3_secret

class RSAServe:
    def __init__(self) -> None:
        self.p = getPrime(512)
        self.q = getPrime(512)
        self.e = 3
        self.m = chall3_secret
    
    def encrypt(self):
        m_ = bytes_to_long(self.m)
        c = pow(m_, self.e, self.p*self.q)
        return hex(c)

    def check(self, msg):
        return msg == self.m

    def pubkey(self):
        return self.p*self.q, self.e

e = 3,低解密指数攻击。

包里有什么

背包密码,题目:

from random import randint
from libnum import gcd, s2n

from secret import flag

plain = flag[6:-1]
assert flag == 'hgame{' + plain + '}'
v = bin(s2n(plain))[2:]
l = len(v)
a = [2 << i for i in range(l)]
m = randint(sum(a), 2 << l + 1)
w = randint(0, m)
assert gcd(w, m) == 1
b = [w * i % m for i in a]

c = 0
for i in range(l):
    c += b[i] * int(v[i])

print(f'm = {m}')
print(f'b0 = {b[0]}')
print(f'c = {c}')

# m = 1528637222531038332958694965114330415773896571891017629493424
# b0 = 69356606533325456520968776034730214585110536932989313137926
# c = 93602062133487361151420753057739397161734651609786598765462162

第一次写遇到背包密码的题

代码分析:

v为二进制形式的明文,后面接了个切片[2:]是为了去掉开头的’0b’

print(bin(s2n('helloworld')))

#0b1101000011001010110110001101100011011110111011101101111011100100110110001100100

l为二进制明文的长度,a为私钥,b为公钥,m为模数(大于序列中的所有数的和),w是与m互素的数(小于m)。

因为第一次做背包密码,我开始以为要想办法逐一推出每一个v[i]出来,因为如果知道明文二进制的长度,公钥,私钥就都知道了。显然不现实,位数太长,且没办法推出每一个v[i]来,题目只给了所有部分的和c。

其实通过加密后密文的和c就能直接推出明文的和v,直接就能求flag了,整体解密和部分解密的方法是一样的,求出w的逆元就行,这样一来,公钥私钥都用不到。

代码如下:

from libnum import *

w = 34678303266662728260484388017365107292555268466494656568963
m = 1528637222531038332958694965114330415773896571891017629493424#模数
b0= 69356606533325456520968776034730214585110536932989313137926#公钥列表里的第一个数
c = 93602062133487361151420753057739397161734651609786598765462162#密文

#w_ni = invert(w,m)
w_ni = 277527593585833632092135078681588573398259359217914482475243
v = c * w_ni % m >> 1

print("hgame{"+long_to_bytes(int(bin(v)[2:][::-1],2)).decode()+"}")

Week3

ezDH

题目:

from sage.all import *
from Crypto.Util.number import *
from secret import Alice_secret, Bob_secret, FLAG
import random

f = open('output', 'w')

N=0x2be227c3c0e997310bc6dad4ccfeec793dca4359aef966217a88a27da31ffbcd6bb271780d8ba89e3cf202904efde03c59fef3e362b12e5af5afe8431cde31888211d72cc1a00f7c92cb6adb17ca909c3b84fcad66ac3be724fbcbe13d83bbd3ad50c41a79fcdf04c251be61c0749ea497e65e408dac4bbcb3148db4ad9ca0aa4ee032f2a4d6e6482093aa7133e5b1800001
g = 2

A = power_mod(g, Alice_secret, N)
f.write("Alice send to Bob: {{ 'g': {g}, 'A': {A} }}\n".format(g=g, A=hex(A)))
B = power_mod(g, Bob_secret, N)
f.write("Bob send to Alice: {{'B': {B} }}\n".format(B=hex(B)))

shared_secret = pow(A, Bob_secret, N)

p=6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151
a=-3
b=1093849038073734274511112390766805569936207598951683748994586394495953116150735016013708737573759623248592132296706313309438452531591012912142327488478985984
E = EllipticCurve(GF(p), [a, b])
G = E.random_point()
Pa = shared_secret * G
f.write(f"Alice send to Bob: {{ 'E': {E}, 'G': {G.xy()}, 'Pa': {Pa.xy()} }}\n")

k = random.randint(2, p)
m = E.lift_x(Integer(bytes_to_long(FLAG)))
P1 = k * G
P2 = k * Pa
c = m + P2
f.write(f"Bob send to Alice: {{ {P1.xy()}, {c.xy()} }}\n")

涉及到Diffie-Hellman密钥交换算法,还未涉足,所以这道题单独拿出来说明。

RSA ⼤冒险2

Challenge 1

from Crypto.Util.number import *
from math import isqrt
from challenges import chall1_secret

class RSAServe:
    def __init__(self) -> None:
        def create_keypair(size):
            while True:
                p = getPrime(size // 2)
                q = getPrime(size // 2)
                if q < p < 2*q:
                    break
            N = p*q
            phi = (p-1)*(q-1)
            max_d = isqrt(isqrt(N)) // 3
            max_d_bits = max_d.bit_length() - 1
            while True:
                d = getRandomNBitInteger(max_d_bits)
                try:
                    e = int(inverse(d, phi))
                except ZeroDivisionError:
                    continue
                if (e * d) % phi == 1:
                    break
            return N, e, d
        self.N, self.e, self.d = create_keypair(1024)
        self.m = chall1_secret
    
    def encrypt(self):
        m_ = bytes_to_long(self.m)
        c = pow(m_ ,self.e, self.N)
        return hex(c)

    def check(self, msg):
        return msg == self.m

    def pubkey(self):
        return {"N":self.N, "e":self.e}

q < p < 2*q和max_d = isqrt(isqrt(N)) // 3,满足Wiener-attack的条件,套用Boneh_Durfee’s attack板子解出p和q来。

def factor_rsa_wiener(N, e):
    N = Integer(N)
    e = Integer(e)
    cf = (e / N).continued_fraction().convergents()
    for f in cf:
        k = f.numer()
        d = f.denom()
        if k == 0:
            continue
        phi_N = ((e * d) - 1) / k
        b = -(N - phi_N + 1)
        dis = b ^ 2 - 4 * N
        if dis.sign() == 1:
            dis_sqrt = sqrt(dis)
            p = (-b + dis_sqrt) / 2
            q = (-b - dis_sqrt) / 2
            if p.is_integer() and q.is_integer() and (p * q) % N == 0:
                p = p % N
                q = q % N
                if p > q:
                    return (p, q)
                else:
                    return (q, p)

N = ...
e = ...
c = ...
p, q = factor_rsa_wiener(N, e)
f = (p-1)*(q-1)
d = inverse_mod(e,f)
m = pow(c,d,N)
print(bytes.fromhex(hex(m)[2:]))
# b'wiener_attack_easily!!!'

参考:HGAME 2023 Week 3 | Lazzaro (lazzzaro.github.io)

Challenge 2

from Crypto.Util.number import *
from challenges import chall2_secret

def next_prime(p):
    k=1
    while True:
        if isPrime(p+k):
            return p+k
        k+=1

class RSAServe:
    def __init__(self) -> None:
        def creat_keypair(nbits, beta):
            p = getPrime(nbits // 2)
            q = next_prime(p+getRandomNBitInteger(int(nbits*beta)))
            N = p*q
            phi = (p-1)*(q-1)
            while True:
                e = getRandomNBitInteger(16)
                if GCD(e, phi) == 2:
                    break
            d = inverse(e, phi)
            return N, e, d
        self.N, self.e, self.d = creat_keypair(1024, 0.25)
        self.m = chall2_secret

    def encrypt(self):
        m_ = bytes_to_long(self.m)
        c = pow(m_, self.e, self.N)
        return hex(c)

    def check(self, msg):
        return msg == self.m

    def pubkey(self):
        return {"N":self.N, "e":self.e}

p = getPrime(nbits // 2)

q = next_prime(p+getRandomNBitInteger(int(nbitsbeta)))

由这两行可以知道p、q相近,在$p − q = N^{β}$ ,且β < 0.25的时候费⻢分解可以很有效的分解N(或者直接用yafu进行分解)。

from gmpy2 import *
from Crypto.Util.number import *
from sympy.ntheory.primetest import is_square
from math import isqrt

def factorize(N):
    """
    Recovers the prime factors from a modulus using Fermat's factorization metho
    :param N: the modulus
    :return: a tuple containing the prime factors, or None if the factors were n
    """
    a = isqrt(N)
    b = a * a - N
    while b < 0 or not is_square(b):
        a += 1
        b = a * a - N

    p = a - isqrt(b)
    q = N // p
    if p * q == N:
        return p, q
N= 68414473664421192639324860086304622601462638941429703900601142401666806262479832339441486761549640560858310048110541758196980937681745306684526118613694088585953109432823793467906611480953093877867597017269693809377188442523548160628479089952031515541299395812090799992365148053327937407652550818853770241959
p,q = factorize(N)
print(p,q)

还有一种思路是从已知p高位攻击入手,参考la佬的脚本(还搞不懂):

import gmpy2

N = 68414473664421192639324860086304622601462638941429703900601142401666806262479832339441486761549640560858310048110541758196980937681745306684526118613694088585953109432823793467906611480953093877867597017269693809377188442523548160628479089952031515541299395812090799992365148053327937407652550818853770241959
e = 62830
c = 0x520ba82a569ea52f16b84c4415186cd616680131e3ac6eda886fea046b18b0e1326d4acbbfb8840dc8064211cd44c30b91148836048434a3f57dd4ec5a5cdaf005a0f8f2c95ee7d9a739505b01e407acd3441dd088f64a2a0c36e43c520e30bd90bb32ec1125e4bb1f4e7d522596898da22c9a7921ac5da96808ee39c98e131f
pbits = 512

find = 0
for i in range(10):
    for j in range(2**i):
        p4 = Integer(gmpy2.iroot(N,2)[0])>>256
        p4 = (p4<<i) + j
        kbits = pbits - p4.nbits()
        p4 = p4 << kbits
        PR.<x> = PolynomialRing(Zmod(N))
        f = x + p4
        roots = f.small_roots(X=2^kbits, beta=0.5)
        if roots:
            p = p4+int(roots[0]) 
            if N%p == 0:
                print(p)
                find = 1
                break
    if find == 1:
        break
        
q = N // p
f = (p-1)*(q-1)
d = inverse_mod(e//2,f)
cc = pow(c,d,N)

P.<a>=PolynomialRing(Zmod(p),implementation='NTL')
f=a^2-cc
mps=f.monic().roots()

P.<a>=PolynomialRing(Zmod(q),implementation='NTL')
g=a^2-cc
mqs=g.monic().roots()

for mpp in mps:
    x=mpp[0]
    for mqq in mqs:
        y=mqq[0]
        solution = hex(CRT_list([int(x), int(y)], [p, q]))[2:]
        if len(solution) % 2 == 0:
            print(bytes.fromhex(solution))

接着是e和欧拉函数不互素的问题。

e = 62830
c = 0x520ba82a569ea52f16b84c4415186cd616680131e3ac6eda886fea046b18b0e1326d4acbbfb8840dc8064211cd44c30b91148836048434a3f57dd4ec5a5cdaf005a0f8f2c95ee7d9a739505b01e407acd3441dd088f64a2a0c36e43c520e30bd90bb32ec1125e4bb1f4e7d522596898da22c9a7921ac5da96808ee39c98e131f
e //= 2

d = invert(e,(p-1)*(q-1))
m = iroot(pow(c,d,N),2)[0]
print(long_to_bytes(m))

Challenge 3

from Crypto.Util.number import *
from challenges import chall3_secret

class RSAServe:
    def __init__(self) -> None:
        def create_keypair(nbits):
            p = getPrime(nbits // 2)
            q = getPrime(nbits // 2)
            N = p*q
            phi = (p-1)*(q-1)
            e = 65537
            d = inverse(e, phi)
            leak = p >> 253
            return N, e, d, leak
        self.N, self.e, self.d, self.leak = create_keypair(1024)
        self.m = chall3_secret
    
    def encrypt(self):
        m_ = bytes_to_long(self.m)
        c = pow(m_, self.e, self.N)
        return hex(c)

    def check(self, msg):
        return msg == self.m

    def pubkey(self):
        return {"N":self.N, "e":self.e, "leak":self.leak}

已知 p 高位攻击,但泄露位数过少,位爆破+调节参数扩大格范围(扩大运算域)。记录下la佬的脚本:

from tqdm import *

n = ...

pbits = 512

for i in trange(2**5):
    p4 = 531320819410375258952658395582915285878636410772332266245849790153420724865787<<(253-248)
    p4 = p4 + i
    kbits = pbits - p4.nbits()
    p4 = p4 << kbits
    PR.<x> = PolynomialRing(Zmod(n))
    f = x + p4
    roots = f.small_roots(X=2^kbits, beta=0.4, epsilon=0.01)
    if roots:        
        p = p4+int(roots[0]) 
        if n%p==0:
            print(i,p)
            break
            
q = n//p
e = 65537
c = ...
f = (p-1)*(q-1)
d = inverse_mod(e,f)
m = pow(c,d,n)
print(bytes.fromhex(hex(m)[2:]))

ezBlock

题目:

from secret import flag


def s_substitute(m):
    c = 0
    s_box = {0: 0x6, 1: 0x4, 2: 0xc, 3: 0x5, 4: 0x0, 5: 0x7, 6: 0x2, 7: 0xe, 8: 0x1, 9: 0xf, 10: 0x3, 11: 0xd, 12: 0x8,
             13: 0xa, 14: 0x9, 15: 0xb}
    for i in range(0, 16, 4):
        t = (m >> i) & 0xf
        t = s_box[t]
        c += t << i
    return c


def enc(m, key):
    n = len(key)
    t = m
    for i in range(n - 1):
        t = t ^ key[i]
        t = s_substitute(t)
    c = t ^ key[n - 1]
    return c


f = flag[6:-1]
assert flag == 'hgame{' + f + '}'
key = [int(i, 16) for i in f.split('_')]
print(len(key))
m_list = [i * 0x1111 for i in range(16)]
c_list = [enc(m, key) for m in m_list]
print(c_list)

# 5
# [28590, 33943, 30267, 5412, 11529, 3089, 46924, 59533, 12915, 37743, 64090, 53680, 18933, 49378, 23512, 44742]

涉及到差分攻击,还是单独拿出来写篇Bold记录一下吧。

week4

实在太菜,暂时先把题目记着,强了再来。。。

1‎月‎18‎日贴的,今天4月29日,再整理博客的时候看到了这几个题,想着应该能写了吧?😭

LLLCG

from Crypto.Util.number import *
from random import randint
from sage.all import next_prime
from flag import flag

class LCG():
    def __init__(self) -> None:
        self.n = next_prime(2**360)
        self.a = bytes_to_long(flag)
        self.seed = randint(1, self.n-1)

    def next(self):
        self.seed = self.seed * self.a + randint(-2**340, 2**340) % self.n
        return self.seed

lcg = LCG()

outputs = []
for i in range(40):
    outputs.append(lcg.next())

with open('output.txt', 'w') as f:
    f.write(str(outputs))

要求a(即flag),我还在想没n怎么算,看了佬们的wp后才知道代码存在问题,看到下面这句:

self.seed = self.seed * self.a + randint(-2**340, 2**340) % self.n

前面的部分并没有括起来,又$r < seed < n$,所以随机选取一组outputs进行整除就得到了a。

预期应该是用LLL算法,涉及到格,准备这几天学一学

LLLCG Revenge

>from Crypto.Util.number import *
from random import randint
>from sage.all import next_prime
from flag import flag

 class LCG():
       def __init__(self) -> None:
           self.n = next_prime(2**360)
           self.a = bytes_to_long(flag)
           self.seed = randint(1, self.n-1)
   
       def next(self):
           self.seed = (self.seed * self.a + randint(-2**340, 2**340)) % self.n
           return self.seed
   
   lcg = LCG()
   
   outputs = []
   for i in range(40):
       outputs.append(lcg.next())
   
   with open('output.txt', 'w') as f:
       f.write(str(outputs))

可以看到除了加了括号以外,其他部分和上面的题一样。

涉及到格的相关问题,准备这几天学一学。

ECRSA

from sage.all import *
from sage.all_cmdline import *
from Crypto.Util.number import *
from secret import flag

Nbits = 512
x = bytes_to_long(flag)
f = open('./output', 'w')

def gen_pubkey(Nbits):
    p = getPrime(Nbits // 2)
    q = getPrime(Nbits // 2)
    n = p*q
    while True:
        a = getRandomInteger(Nbits // 2)
        b = getRandomInteger(Nbits // 2)
        if gcd(4*a**3 + 27*b**2, n) == 1:
            break
    E = EllipticCurve(Zmod(n), [a, b])
    e = getPrime(64)
    f.write(f"p={p}\nq={q}\n")
    return n, E, e

n, E, e = gen_pubkey(Nbits)
pt = E.lift_x(Integer(x))
ct = pt * e
f.write(f"n = {n}\na = {E.a4()}\nb = {E.a6()}\ne = {e}\n")
f.write(f"ciphertext = {long_to_bytes(int(ct.xy()[0]))}\n")

我觉得这道题对于我学习ECC有很大的帮助,所以单独拿出去做记录了。(所以当时干什么去了?唉,太jier懒了,过了这么久没大长进。)


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1666739907@qq.com
github