2021第二届祥云杯Crypto部分Write-Up全

admin 2021年12月29日01:25:42评论734 views字数 19355阅读64分31秒阅读模式

前言

芜湖,这次祥云杯又是神仙打架,密码学一共有四道题,个人觉得最后一道题目有意思一些。

 

2021第二届祥云杯Crypto部分Write-Up全

Random_RSA

from Crypto.Util.number import *
import gmpy2
import libnum
import random
import binascii
import os

flag=r'flag{}'

p=getPrime(512)
q=getPrime(512)
e=0x10001
n=p*q
ct=pow(flag,e,n)
print("n="+ n)
print("ct="+ ct)

dp=r''
seeds = []
for i in range(0,len(dp)):
seeds.append(random.randint(0,10000))

res = []
for i in range(0, len(dp)):
random.seed(seeds[i])
rands = []
for j in range(0,4):
rands.append(random.randint(0,255))

res.append(ord(dp[i]) ^ rands[i%4])
del rands[i%4]
print(str(rands))

print(res)
print(seeds)

题目不长,也意外的简单,值得一提的是题目介绍:一把梭,好像不行哦

然而好像就是一把梭的题目吖,没get到出题人的点喔,而且很奇怪的是,题目用的python2的环境,却直接在给flag字符串做整型pow操作,属实奇怪。

回到题目本身,倒也没有什么好说的,给了seeds,我们利用每个seeds生成四个随机数,用第四个随机数异或res的输出,然后ord一下,就能得到dp的一个数字,最后拼起来就能获得dp了。至于已知e,n,dp三个参数解密c获得明文,这里就不再赘述了,一把梭的事。

from Crypto.Util.number import *
import gmpy2

import random
import binascii
import os


seeds=[...]
dps=[]
res = [...]

for i in range(0, len(res)):
random.seed(seeds[i])
rands = []
for j in range(0,4):
rands.append(random.randint(0,255))
dps.append(res[i]^rands[i%4])


dp = int(''.join(chr(i) for i in dps))
n=...
ct=...
e=0x10001

def rsa_nedp(n,e,dp):
for i in range(1,e):
if (dp*e-1)%i == 0:
if n%(((dp*e-1)/i)+1)==0:
p=((dp*e-1)/i)+1
q=n/(((dp*e-1)/i))+1
return p,q

p,q = rsa_nedp(n,e,dp)
d = inverse(e,p-1)
print(long_to_bytes(pow(ct,d,p)))

最后这里解密flag直接用p了,(单纯懒,少写一个字符是一个字符了)

 

Guess

from Crypto.Util.number import (
bytes_to_long,
getPrime,
long_to_bytes,
getRandomNBitInteger,
)
import random
import hashlib
from math import gcd
import socketserver


KEYSIZE = 512
WELCOME = "welcome to my funny challenge !!! Can you guess right 32 times in a row? "
String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz"

def exgcd(a, b):
if b == 0:
return 1, 0, a
else:
x, y, q = exgcd(b, a % b)
x, y = y, (x - (a // b) * y)
return x, y, q


def invert(a,p):
x, y, q = exgcd(a,p)
if q != 1:
raise Exception("No solution.")
else:
return (x + p) % p

def lcm(a,b):
return a*b // gcd(a,b)

def proof_of_work():
STR = "".join([String[random.randint(0, len(String) - 1)] for _ in range(16)])
HASH = hashlib.sha256(STR.encode()).hexdigest()
return STR[:4], STR[4:], HASH


def keygen():
# part 1
p, q = getPrime(KEYSIZE), getPrime(KEYSIZE)
n = p * q
g = n + 1
LAMBDA = lcm(p - 1, q - 1)

# part 2
_key = open("key", "r").read()
key = []
for i in _key.split("n"):
for j in i[1:-1].split(" "):
if int(j) not in key:
key.append(int(j))
assert len(key) == 80
#assert key[0] == 119 and key[1] == 241 and key[2] == 718 and key[3] == 647
return n, g, LAMBDA, key


def enc(n, g, m):
while 1:
r = random.randint(2, n - 1)
if gcd(r, n) == 1:
break
c = (pow(g, m, n ** 2) * pow(r, n, n ** 2)) % (n ** 2)
return c


def dec(n, g, LAMBDA, c):
L1 = (pow(c, LAMBDA, n ** 2) - 1) // n
L2 = (pow(g, LAMBDA, n ** 2) - 1) // n
m = (invert(L2, n) * L1) % n
return m


class server(socketserver.BaseRequestHandler):
def _recv(self):
data = self.request.recv(1024)
return data.strip()

def _send(self, msg, newline=True):
if isinstance(msg, bytes):
msg += b"n"
else:
msg += "n"
msg = msg.encode()
self.request.sendall(msg)

def handle(self):
# print("Service start.")
# START, END, HASH = proof_of_work()
# self._send("SHA-256(?+{}) == {}".format(END, HASH))
# RCV = self._recv().decode()
# if RCV != START:
# return
# flag = open("flag", "rb").read()
# self._send(WELCOME)
# step 1. keygen
for _ in range(32):
self._send("round " + str(_+1))
n, g, LAM, KEY = keygen()
self._send("Step 1 - KeyGen. This is my public key.")
self._send("n = " + str(n))
self._send("g = " + str(g))
# step 2. Phase 1
self._send(
"Step 2 - Phase 1. Now, you can give me one ciphertexts,I will return the corresponding plaintext."
)

self._send("Please give me one decimal ciphertext.")
cipher = int(self._recv().decode())
print(cipher)
plaintext = str(dec(n, g, LAM, cipher))
self._send("This is the corresponding plaintext.")
self._send(plaintext)

# step 3. challenge
self._send(
"Step 3 - Challenge. Now, you must give me two decimal plaintexts(m0,m1), I will encry them and return a ciphertext randomly"
)
self._send("Give me m0.")
plaintext1 = int(self._recv().decode())
self._send("Give me m1.")
plaintext2 = int(self._recv().decode())

if (
plaintext1 <= 2
or plaintext2 <= 2
or len(bin(plaintext1)) != len(bin(plaintext2))
):
return
R = 2 * random.randint(0, 39)
I = random.randint(0, 1)
cipher1 = enc(n, g, plaintext1 * plaintext2 * KEY[R])
cipher2 = enc(n, g, plaintext1 * plaintext2 * KEY[R + 1])
self._send("This is a ciphertext.")
self._send(str([cipher1, cipher2][I]))

# step 4. Phase 2

self._send(
"Step 4 - Phase 2. Now, you can give me some ciphertexts,I will return the corresponding plaintext.But you can not give me the ciphertext that I give you in step 3."
)
self._send("Please give me one decimal ciphertext ")
cipher = int(self._recv().decode())
plaintext = str(dec(n, g, LAM, cipher))
if int(plaintext) == plaintext1 * plaintext2 * KEY[R] or int(plaintext) == plaintext1 * plaintext2 * KEY[R+1]:
print(plaintext)
return
self._send("This is the corresponding plaintext.")
self._send(plaintext)

# step.5 Guess
self._send(
"Step 5 - Guess. You must tell me which ciphertext was I give you in step 3, 0 or 1(m0 -> c0 , m1 -> c1)?"
)
Guess = int(self._recv().decode())

if Guess == I:
self._send("Good! You are right")
else:
self._send("Sorry!")
return
self._send(flag)

class ForkedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass


if __name__ == "__main__":
HOST, PORT = "0.0.0.0", 10001
server = ForkedServer((HOST, PORT), server)
server.allow_reuse_address = True
server.serve_forever()

这道题代码还挺长的,不过并不难看懂。

首先server类上面的是定义的enc和dec,其实就是paillier密码系统,不熟悉的读者可以移步我的这一片文章。

然后这道题目不知道是不是非预期了,因为题目给的hint我并没有到。step2的交互我也没有利用。(太奇怪了,预期该是啥样呢)

由于题目用的pailliar密码系统,具有同态(竟然说不上是乘法同态还是加法同态,只能说两个明文之和的密文,是两个明文分别的密文之积)

我们看到step4-5

2021第二届祥云杯Crypto部分Write-Up全

题目向我们要两个明文plaintext1和plaintext2,然后加密的内容为

cipher1 = enc(n, g, plaintext1 * plaintext2 * KEY[R])
cipher2 = enc(n, g, plaintext1 * plaintext2 * KEY[R + 1])

由于plaintext1和plaintext2可控,而两条密文的唯一区别是KEY的内容不一样。然后题目加密后随机返回一条,让我们猜返回的是哪个。

我们可以看到,这加密的明文唯一的区别就是用的是KEY[R]和KEY[R+1],而R = 2 * random.randint(0, 39),是一个偶数。

那么这里我们选择“炼丹”:

首先我们可以利用同态获取到实际的KEY:题目在step3发送密文cipher后,在step4会帮我们解密一条数据,但是这条数据不能是服务器加密的那两条密文之一,那么,我们就给他cipher * enc(5),这样他就会解密后并返回plaintext1 * plaintext2 * KEY[R] + 5 或者 plaintext1 * plaintext2 * KEY[R+1] + 5, 我们再处理一下(不处理问题也不大),减掉5,除掉plaintext1 * plaintext2,就可以获取一个KEY_i 了。

然后我们到step5,我们只知道了一个KEY_i,但是不知道它具体的位置,我们直接发送0,如果返回正确,那么我们知道,这个KEY_i 在偶数位,如果返回错误,服务断掉,那么我们知道,这个KEY_i 在奇数位。那么,由于服务端的KEY序列是固定的,那么我们就开始炼丹咯。

我们构造两个数组,一个存奇数位,一个存偶数位。每次连上去,我们解密得到一个KEY_i,如果这个KEY_i 在我们的数组里,我们就能够直接返回正确答案,如果不在,我们就”炼“,猜对了,放进数组,继续猜,猜错了,放进数组,重新连。(再非不过80次连接)

from Crypto.Util.number import (
bytes_to_long,
getPrime,
long_to_bytes,
getRandomNBitInteger,
)
import random
import hashlib

KEYSIZE = 512
WELCOME = "welcome to my funny challenge !!! Can you guess right 32 times in a row? "
String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz"
from math import gcd
def exgcd(a, b):
if b == 0:
return 1, 0, a
else:
x, y, q = exgcd(b, a % b)
x, y = y, (x - (a // b) * y)
return x, y, q


def invert(a,p):
x, y, q = exgcd(a,p)
if q != 1:
raise Exception("No solution.")
else:
return (x + p) % p

def lcm(a,b):
return a*b // gcd(a,b)

def proof_of_work():
STR = "".join([String[random.randint(0, len(String) - 1)] for _ in range(16)])
HASH = hashlib.sha256(STR.encode()).hexdigest()
return STR[:4], STR[4:], HASH



def enc(n, g, m):
while 1:
r = random.randint(2, n - 1)
if gcd(r, n) == 1:
break
c = (pow(g, m, n ** 2) * pow(r, n, n ** 2)) % (n ** 2)
return c


def dec(n, g, LAMBDA, c):
L1 = (pow(c, LAMBDA, n ** 2) - 1) // n
L2 = (pow(g, LAMBDA, n ** 2) - 1) // n
m = (invert(L2, n) * L1) % n
return m

from pwn import *
from pwnlib.util.iters import mbruteforce
from hashlib import sha256

#context.log_level = 'debug'

def proof_of_work(sh):
sh.recvuntil("?+")
suffix = sh.recvuntil(')').decode("utf8")[:-1]
log.success(suffix)
sh.recvuntil("== ")
cipher = sh.recvline().strip().decode("utf8")
proof = mbruteforce(lambda x: sha256((x + suffix).encode()).hexdigest() == cipher, string.ascii_letters + string.digits, length=4, method='fixed')
sh.sendline(proof)
vanish=[]

#奇
s=[]
#偶
ss=[]


for _ in range(80):
sh=remote("47.104.85.225","57811")
proof_of_work(sh)

while True:
tmp = sh.recvuntil("n = ")
n = int(sh.recvuntil("n")[:-1])
sh.recvuntil("g = ")
g = int(sh.recvuntil("n")[:-1])
sh.recvuntil("decimal ciphertext.n")
sh.sendline("123")
sh.recvuntil("Give me m0.n")
sh.sendline("5")
sh.recvuntil("Give me m1.n")
sh.sendline("5")
sh.recvuntil("This is a ciphertext.n")
c = int(sh.recvuntil("n")[:-1])
sh.recvuntil("Please give me one decimal ciphertext n")
sh.sendline(str((enc(n,g,5)*c)%(n**2)))
sh.recvuntil("This is the corresponding plaintext.n")
m = (int((sh.recvuntil("n")[:-1]))-5)//25
sh.recvuntil("0 or 1(m0 -> c0 , m1 -> c1)?n")
if m in s:
sh.sendline('1')
tmp = sh.recvuntil("n")
elif m in ss:
sh.sendline('0')
tmp = sh.recvuntil("n")
else:
sh.sendline('1')
tmp = sh.recvuntil("n")
if b"Good! You are right" in tmp:
s.append(m)
elif b"Sorry" in tmp:
ss.append(m)
sh.close()
break
print(s)
print(ss)

 

myRSA

# myRSA
from Crypto.Util.number import getPrime,bytes_to_long as b2l
from math import gcd
import hashlib
import random
import socketserver


KEYSIZE = 512
alpha = 2.0314159265358979
WELCOME = 'Welcome to use my better RSA!!!!!!So, what do you want now?'
menu = '1. encry n2. getflagn3. exit'
String = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz'

def proof_of_work():
STR = ''.join([String[random.randint(0,len(String)-1)] for _ in range(16) ])
HASH = hashlib.sha256(STR.encode()).hexdigest()
return STR[:4],STR[4:],HASH

def key_gen():
while True:
p,q = getPrime(KEYSIZE),getPrime(KEYSIZE)
e = 0x10001
if gcd(e,(p-1)*(q-1)):
break
key = [getPrime(int(KEYSIZE*alpha)) for _ in range(2)]
return (p,q,e),key

# encrypto
def encry(message,key,p,q,e):
k1,k2 = key[0],key[1]
x = p**2 * (p + 3*q - 1 ) + q**2 * (q + 3*p - 1)
y = 2*p*q + p + q
z = k1 + k2
c = pow(b2l(message),e,p*q)
return x * c + y * c + z


# get flag
def getflag(flag,key,p,q,e):
return encry(flag,key,p,q,e)



class server(socketserver.BaseRequestHandler):
def _recv(self):
data = self.request.recv(1024)
return data.strip()

def _send(self, msg, newline=True):
if isinstance(msg , bytes):
msg += b'n'
else:
msg += 'n'
msg = msg.encode()
self.request.sendall(msg)

def handle(self):
START,END,HASH = proof_of_work()
self._send('SHA-256(?+{}) == {}'.format(END,HASH))
RCV = self._recv().decode()
if RCV != START:
return
self._send("I'm a CryptoRookie,so my Crypto system take time, please wait a minute XD!")
(p,q,e),key = key_gen()
flag = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
self._send(WELCOME)
self._send('This is my public key:nn = {}ne = {}'.format(str(p*q),str(e)))
for _ in range(16):
self._send(menu)
COI = int(self._recv().decode())
if COI == 1 :
self._send('Give me your message')
message = self._recv()
self._send('Your encry message:')
self._send(str(encry(message,key,p,q,e)))
elif COI == 2:
self._send('This is your favourite:n')
self._send(str(encry(flag,key,p,q,e)))
elif COI == 3:
self._send('Bye~')
break
class ForkedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass

if __name__ == "__main__":
HOST, PORT = '0.0.0.0', 10001
server = ForkedServer((HOST, PORT), server)
server.allow_reuse_address = True
server.serve_forever()

这道题,emmm,也是奇怪的加密方式,

def encry(message,key,p,q,e):
k1,k2 = key[0],key[1]
x = p**2 * (p + 3*q - 1 ) + q**2 * (q + 3*p - 1)
y = 2*p*q + p + q
z = k1 + k2
c = pow(b2l(message),e,p*q)
return x * c + y * c + z

题目提供提供十六次交互,它可以帮你加密,但每次加密用的z随机

x * c + y * c + z =( x+y )* c + z

其中( x+y ) = ( p+q )^3 – ( p+q )^2 + ( p+q ) – 4 * n

那么我们发送明文 ’x01’ 过去,就能得到enc = x + y + z,所以 enc + 4 * n = (p+q)^3 – (p+q)^2 + (p+q) + z

我们可以将其看作关于(p+q)的方程 f(x) ,由于z不知道,没法根据返回值解一个具体的值x。

但是算一下长度,(p+q)^3 有 513 * 3 = 1539 bit,z 才 1024bit左右,相对比较小。那么我们直接不管z,去解方程(这里我是用2分法去逼近的),然后我们可以得到一个大概的值。

有了大概的 x ≈ (p+q),再利用n,就能得到一个大概的p值,

2021第二届祥云杯Crypto部分Write-Up全

有了大概的p值,我们可以本地起一组数据看看和真正的p值差多少,可以发现就差小几万,那么我们直接一手small_roots恢复p。p,q都恢复了,我们直接交互拿到flag的密文 ( x + y) * pow(flag, e, n) + z

直接整除 (x + y) 得到pow(flag, e, n),(z太小了,整除就给消没了),后面,rsa解密,得到flag。

P.S.不懂出题人干嘛搞一个genkey浪费时间,有啥必要么,还是说,又又又又又又又又又又又又非预期了?确实没用到16次交互。

#交互拿到数据a = x + y + z; c = pow(flag,e,n) * (x + y) + z

from Crypto.Util.number import *

def f(x):
return x**3 - x**2 + x + 4*n

n = ...
e = 65537

a = ...

c = ...



floor = 0
sky = 2**1041
while floor+1 < sky:
mid = (floor + sky) // 2
if f(mid) < a:
floor = mid
else:
sky = mid

import gmpy2

p_sub_q = gmpy2.iroot(int(mid**2-4*n),int(2))[0]
pw = (mid-p_sub_q)//2

N = n
pbar = pw
ZmodN = Zmod(N)
P.<x> = PolynomialRing(ZmodN)
ff = int(pbar) + x
x0 = ff.small_roots(X=2^40, beta=0.4)[0]
p = int(int(pbar) + x0)
n = int(n)
q = n // p

tmp = f(p+q)
c //= tmp
print(long_to_bytes(pow(c,inverse(e,(p-1)*(q-1)),n)))

ok,终于来到最有意思的一题了,也是足足做了我快5个小时(虽然中途思路断了的时候去把XMAN结营赛的密码学赛题AK了下)

 

secret_share

#! /usr/bin/env python
from libnum import n2s, s2n
from random import getrandbits
from hashlib import sha256
import SocketServer
from secret import flag

p, g = 0xb5655f7c97e8007baaf31716c305cf5950a935d239891c81e671c39b7b5b2544b0198a39fd13fa83830f93afb558321680713d4f6e6d7201d27256567b8f70c3,
0x85fd9ae42b57e515b7849b232fcd9575c18131235104d451eeceb991436b646d374086ca751846fdfec1ff7d4e1b9d6812355093a8227742a30361401ccc5577


def h2(m):
return int(sha256(m).hexdigest(), 16)


def key_gen(nbits):
s = getrandbits(nbits) % p
while s.bit_length() < nbits - 2:
s = getrandbits(nbits) % p
pk = pow(g, s, p)
return pk, s


def enc(m, pk):
m = s2n(m)
e, v = getrandbits(256), getrandbits(256)
E, V = pow(g, e, p), pow(g, v, p)
s = v + e * h2(n2s(E) + n2s(V))
c = m * pow(pk, e + v, p) % p
cap = (E, V, s)
return c, cap


def rk_gen(sk, pki, group=9):
x, r = getrandbits(512) % p, getrandbits(512) % p
prefix = n2s(pow(g, x * sk, p)).rjust(64, 'x00')
encoder = [1, -pow(pki, x * sk, p) % p]
for i in range(1, group + 1):
pkj = getrandbits(512)
new_encoder = [1]
cur = pow(pkj, x * sk, p)
for j in range(1, i + 1):
new_encoder.append((encoder[j] + (-1) * cur * encoder[j - 1]) % p)
new_encoder.append(encoder[i] * cur * (-1) % p)
encoder = new_encoder
encoder[-1] += r
dd = h2(prefix + n2s(r).rjust(64, 'x00')) | 1
rk = sk * dd
return rk, encoder[1:], prefix


def re_enc(rk, cipher):
c, (E, V, s) = cipher
E_ = pow(E, rk, p)
V_ = pow(V, rk, p)
s_ = s * rk % p
return c, (E_, V_, s_)


class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
pass


class EncHandler(SocketServer.BaseRequestHandler):
def handle(self):
self.request.sendall("Welcome to our netdisk system! Our system store only users' ciphertextn")
self.request.sendall("Now you can choose what you wanna don")
self.request.sendall("1. generate your keyn2. start challengen2. get the ciphertext")
pk_of_one_user, sk_of_one_user = key_gen(512)
cipher = enc(flag, pk_of_one_user)
pk, sk = key_gen(512)
while 1:
mul = 1
self.request.sendall('Input your choicen')
self.request.sendall("choice>")
choice = self.request.recv(16).strip()
if choice == '1':
self.request.sendall('Please take good care of it!n' + hex(pk) + ',' + hex(sk) + 'n')
elif choice == '2':
group_list = [32, 64, 128, 256]
for group in group_list:
m = getrandbits(200)
plaintext = n2s(m)
cur_cipher = enc(plaintext, pk_of_one_user)
rk, encoder, prefix = rk_gen(sk_of_one_user, pk, group=group)
mul *= rk
mul %= p
new_cipher = re_enc(rk, cur_cipher)
self.request.sendall('The cipher shared to youn' + str(new_cipher) + 'n')
self.request.sendall('prefix, encoder = ' + str((encoder, prefix.encode('hex'))) + 'n')
ans = self.request.recv(1024).strip()
if int(ans, 16) != m:
exit(1)
self.request.sendall('You are a clever boy! Now I can share you some other information!n' + hex(mul) + 'n')
elif choice == '3':
self.request.sendall(str(cipher) + 'n')
exit(1)
else:
continue


if __name__ == "__main__":
HOST, PORT = "0.0.0.0", 1213
server = ThreadedTCPServer((HOST, PORT), EncHandler)
server.serve_forever()

还好,也就百来行,代码量不大,

我们以目标驱使,找到获取flag的地方,cipher = enc(flag, pk_of_one_user)

看到这个pk_of_one_user,

def key_gen(nbits):
s = getrandbits(nbits) % p
while s.bit_length() < nbits - 2:
s = getrandbits(nbits) % p
pk = pow(g, s, p)
return pk, s

pk_of_one_user, sk_of_one_user = key_gen(512)

基于离散对数问题的公私钥对 pk ≡ g^{sk} (mod p)

然乎看到enc的加密方式

def enc(m, pk):
m = s2n(m)
e, v = getrandbits(256), getrandbits(256)
E, V = pow(g, e, p), pow(g, v, p)
s = v + e * h2(n2s(E) + n2s(V))
c = m * pow(pk, e + v, p) % p
cap = (E, V, s)
return c, cap

可以化为式子:

2021第二届祥云杯Crypto部分Write-Up全

那么想获取flag,就要搞到这个私钥sk咯,看看怎么能搞到。

第一目标:拿到sk

可以看到交互提供三个功能,第一个,获取你自己的公私钥对 pk,sk

再看下第三个,返回flag的密文,

然后第二个比较长,核心功能也就在这里了,

group_list = [32, 64, 128, 256]
for group in group_list:
m = getrandbits(200)
plaintext = n2s(m)
cur_cipher = enc(plaintext, pk_of_one_user)
rk, encoder, prefix = rk_gen(sk_of_one_user, pk, group=group)
mul *= rk
mul %= p
new_cipher = re_enc(rk, cur_cipher)
self.request.sendall('The cipher shared to youn' + str(new_cipher) + 'n')
self.request.sendall('prefix, encoder = ' + str((encoder, prefix.encode('hex'))) + 'n')
ans = self.request.recv(1024).strip()
if int(ans, 16) != m:
exit(1)

流程描述:

他会生成一个随机数m,

然后它的公钥加密m得到cur_cipher,

然后将它的私钥和我们的公钥放进rk_gen这个函数,会得到 rk, encoder, prefix

然后mul = mul * rk % p,

然后用rk,re_enc这个cur_cipher

然后把re_enc后的密文,encoder, prefix 发送给我们。

然后然我们解密m,对了继续,错了拜拜。

循环四次,都通过返回给你mul。

整理逻辑。如果我们都通过了,就能拿到 mul = rk_1 * sk * rk_2 * sk * rk_3 sk * rk_4 % p

有了这个mul,有啥意义么?这里还看不出来,我们继续往前走先,不过有一个问题很明确,既然出题人就这么布置了,我们当然是需要解密m了。

第二目标,解密m

想要解密m,我们相关信息只有re_enc后的cipher,当前轮次的encoder, prefix

看看这个re_enc

def re_enc(rk, cipher):
c, (E, V, s) = cipher
E_ = pow(E, rk, p)
V_ = pow(V, rk, p)
s_ = s * rk % p
return c, (E_, V_, s_)

可以发现,整个函数都没有操作c,就更新了一下E, V, s

那么更新一下我们手里的信息,

2021第二届祥云杯Crypto部分Write-Up全

就这么多了,我们再去看看返回encoder, prefix 的 rk_gen函数,

def rk_gen(sk, pki, group=9):
x, r
= getrandbits(512) % p, getrandbits(512) % p
prefix = n2s(pow(g, x * sk, p)).rjust(64, 'x00')
encoder = [1, -pow(pki, x * sk, p) % p]
for i in range(1, group + 1):
pkj
= getrandbits(512)
new_encoder = [1]
cur = pow(pkj, x * sk, p)
for j in range(1, i + 1):
new_encoder.append((encoder[j] + (-1) * cur * encoder[j - 1]) % p)
new_encoder.append(encoder[i] * cur * (-1) % p)
encoder
= new_encoder
encoder[-1] += r
dd = h2(prefix + n2s(r).rjust(64, 'x00')) | 1
rk = sk * dd
return rk, encoder[1:], prefix

先不管其他的,看到最下面,rk = sk * dd

我们回想到 mul = rk_1 * sk * rk_2 * sk * rk_3 sk * rk_4 % p ,

那么可以转化为 mul = sk^4 * dd1 * dd2 * dd3 * dd4 % p ,

有了dd的product,那么我们再在模p下给sk^4开个四次根就能拿到sk了!

而 dd = h2(prefix + n2s(r).rjust(64, ‘x00’)) | 1,其中prefix已知,所以目标很明确

第三目标,获取r

我们能够注意到,返回给我们的encder与r相关, 其中encoder[-1] += r,所以我们需要恢复出原来的encoder,

那就得从头看这个函数了,我们首先看到,prefix = n2s(pow(g, x * sk, p)).rjust(64, ‘x00’),其中x是未知随机数,sk是服务端公钥,

然后初始 encoder = [1, -pow(pki, x * sk, p) % p],划重点,需要注意到,这里的pki,用的是再step1中给我们的pk,我们是知道其对应的sk的!我们把自己的sk记为ski,避免搞混,那么式子:

2021第二届祥云杯Crypto部分Write-Up全

就只差一个 ski ,而这个ski我们是已知的,所以我们就获得了初始 encoder = [1, -pow(prefix, ski, p) % p]

核心的迭代来了

for i in range(1, group + 1):
pkj
= getrandbits(512)
new_encoder = [1]
cur = pow(pkj, x * sk, p)
for j in range(1, i + 1):
new_encoder.append((encoder[j] + (-1) * cur * encoder[j - 1]) % p)
new_encoder.append(encoder[i] * cur * (-1) % p)
encoder
= new_encoder

cur = pow(pkj, x * sk, p),pkj是一个随机数,所以cur显然无法直接得到,然后是循环里的 new_encoder.append((encoder[j] + (-1) * cur * encoder[j – 1]) % p)

走出后还来一下 new_encoder.append(encoder[i] * cur * (-1) % p)

这里建议画个美丽的图会稍微清晰一些

2021第二届祥云杯Crypto部分Write-Up全

可以看到,对于cur2那一行,在我们知道 x 的前提下,我们是能够通过第二项获取到 (cur1 + cur2) 的值的,

在我们知道(cur1 + cur2) 的值的前提下,我们是能够获得第三项中 (cur1 * cur2)的值的。

有了(cur1 * cur2)的值,我们再将他乘以x,我们就获得了encoder最后一项的值了。

芜湖,起飞!(如果觉得就分析这么三轮还不够的化,可以再画第四轮,就很清晰了)

那么我们迎来了处理 encoder的核心解法,基本上可以宣布此题告破了。

我们只需要用题目给的 encoder 和 prefix,进行上述操作

encoder,prefix = ...

x = -pow(prefix,sk,p)%p
encoder_i=1
for i in encoder[:-1]:
encoder_i = (i-encoder_i*x)%p
r = (encoder[-1] - encoder_i*x)%p

2021第二届祥云杯Crypto部分Write-Up全

from pwn import * 

from random import getrandbits
from hashlib import sha256
from Crypto.Util.number import *

def n2s(n):
return long_to_bytes(n)

def s2n(n):
return bytes_to_long(n)

def h2(m):
return int(sha256(m).hexdigest(), 16)


def key_gen(nbits):
s = getrandbits(nbits) % p
while s.bit_length() < nbits - 2:
s = getrandbits(nbits) % p
pk = pow(g, s, p)
return pk, s


def enc(m, pk):
m = s2n(m)
e, v = getrandbits(256), getrandbits(256)
E, V = pow(g, e, p), pow(g, v, p)
s = v + e * h2(n2s(E) + n2s(V))
c = m * pow(pk, e + v, p) % p
cap = (E, V, s)
return c, cap


p, g = ...

#context.log_level = 'debug'
sh = remote("47.104.85.225","62351")
#sh = remote("0.0.0.0","1213")
sh.recvuntil("choice>")
sh.sendline("1")
sh.recvuntil("Please take good care of it!n")
tmp = sh.recvuntil("n")[:-1]
#获取自己的pk和sk
pk,sk = eval(tmp)
B=[]
sh.recvuntil("choice>")
sh.sendline("2")
for _ in range(4):
sh.recvuntil("The cipher shared to youn")
tmp = sh.recvuntil("n")[:-1]

#获取到 re_enc(m) 后给的 c, (E_, V_, s_)
c, (E_, V_, s_) = eval(tmp)
sh.recvuntil("prefix, encoder = ")
tmp = sh.recvuntil("n")[:-1]

#利用 encoder,prefix 获取r,从而得到dd
encoder,prefix = eval(tmp)
prefixx = prefix.decode('hex')
prefix = int(prefix,16)
x = -pow(prefix,sk,p)%p
tmp=1
for i in encoder[:-1]:
tmp = (i-tmp*x)%p
r = (encoder[-1] - tmp*x)%p
prefix = n2s(pow(g, x * sk, p)).rjust(64, 'x00')
dd = h2(prefixx + n2s(r).rjust(64, 'x00')) | 1
B.append(dd)
dd_ = inverse(dd,p-1)

#有了dd,利用前面得到的c, E_ * V_ 解密m
m = inverse(pow(E_*V_,dd_,p),p)*c % p
sh.sendline(hex(m)[2:])

sh.recvuntil("You are a clever boy! Now I can share you some other information!n")
tmp = sh.recvuntil("n")[:-1]

#拿着通关后给的mul,待会去开根
mul = eval(tmp)

sh.recvuntil("choice>")
sh.sendline("3")
tmp = sh.recvuntil("n")[:-1]

#获取flag密文相关的参数
c, (E, V, s) = eval(tmp)

#dd求逆乘以mul,把原来mul里的dd去掉,得到sk^4
for i in B:
mul = mul * inverse(i,p) % p
sk_4 = mul

sh.interactive()

拿到 sk^4 后要开根,这里我切到sagemath去直接用nth_root了

a,b = Mod(sk_4,p).nth_root(4,all=True)

tmp = pow(int(E*V),int(a),int(p))
m = c * inverse_mod(int(tmp),int(p)) % int(p)
print(long_to_bytes(m))

tmp = pow(int(E*V),int(b),int(p))
m = c * inverse_mod(int(tmp),int(p)) % int(p)
print(long_to_bytes(m))

b'flag{504d0411-6707-469b-be31-9868200aca95}'
b'x9atx03Oxbd;.xb5x97Tz$t2Vx9bx92xa8x0c.Ox89Vx05xbfxb9x0exfbxfcRCx8ex948qBxeex92yx02xbf|xf6Sqx81xdf;!xd1x9fmJxfbx87#xbb10xa4txfdxd4x9a'

 

结语

整体来看,这次比赛的题目难度中等叭,第一题一把梭没啥好说的,第二题非预期的炼丹,也还行吧,赛后也没问着hint怎么用,不过第一次交互好像是用来获取lamda的,我直接用同态过check好像也是非预期了。第三题化二元方程为一元,然后二分(哦,求一下导可以知道后面是递增的所以能二分)去求一个大概解,也挺有意思的。不过最喜欢的还是最后一题,初看觉得整个代码的处理流程很冗长,但是一点点去将题目解析,将问题一点点规约下去,这种一点点拨开云雾见天日,守得云开见月明的感觉属实不错,而且难度也刚好在我的level,舒服了。


来源:安全客 

https://www.anquanke.com/post/id/251336



本文始发于微信公众号(山石网科安全技术研究院):2021第二届祥云杯Crypto部分Write-Up全

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年12月29日01:25:42
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   2021第二届祥云杯Crypto部分Write-Up全https://cn-sec.com/archives/474106.html

发表评论

匿名网友 填写信息