这个赛很不理想,啥都不会。
拿了WP看了几个题,记录一下
random_RSA
这题不会是正常情况,我认为。对于论文题,不知道就是不知道,基本没有可能自己去完成论文。
题目不长,只有两个菜单,共可交互8次,输入1时会随机数生成p,q,d(生成随机数左移32位再取next_prime)然后给出e(e对应的phi是(p^2-1)*(q^2-1)),输入2时会再生成p,q用e=0x10001生成c
from gmpy2 import next_prime, invert as inverse_mod
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA
from random import getrandbits
from math import lcm
from sys import exit
global_bits = 1024
BANNER = rb"""
.--------.--------.--------.--------.--------.--------.--------.--------.--------.--------.--------.
| N.--. | E.--. | P.--. | C.--. | T.--. | F.--. | H.--. | A.--. | P.--. | P.--. | Y.--. |
| :/\: | (\/) | :(): | :/\: | :/\: | :/\: | (\/) | :(): | :/\: | :/\: | (\/) |
| :\/: | :\/: | ()() | (__) | :\/: | (__) | :\/: | ()() | :\/: | :\/: | :\/: |
| '--'n | '--'e | '--'p | '--'c | '--'t | '--'f | '--'h | '--'a | '--'p | '--'p | '--'y |
`--------`--------`--------`--------'--------`--------`--------`--------`--------`--------`--------`
"""
def generate_prime(bits: int):
p = (getrandbits(bits - 32) << 32)
return next_prime(p)
def generate_private_key(bits: int):
p, q = generate_prime(bits), generate_prime(bits)
n, phi = p * q, lcm(p-1, q - 1)
d = inverse_mod(0x10001, phi)
privateKey = RSA.construct((int(n), int(0x10001), int(d), int(p), int(q)))
return privateKey, p > q
if __name__ == "__main__":
print(BANNER.decode())
print("Welcome to the world of random RSA.")
print("Please make your choice.")
for _ in range(8):
choice = input()
if choice == '1':
p, q = generate_prime(global_bits), generate_prime(global_bits)
N = p*q
d = generate_prime(global_bits-32)
e = inverse_mod(d, (p * p - 1) * (q * q - 1))
print(f"{int(N)}")
print(f"{int(e)}")
elif choice == '2':
privateKey, signal = generate_private_key(global_bits)
Cipher = PKCS1_v1_5.new(privateKey)
c = (Cipher.encrypt(flag.encode()))
print(c)
exit()
else:
exit()
这里菜单2没有给出p,q显然是通过前边的随机数预测。
1,ed = 1 + k(p^2-1)(q^2-1)如果分解的问题,这个关于论文也就不解释了,解释不清,解法就是通过连分式分解
结果是k,d,所以先交换取7个1和1个2然后回来慢慢处理,得到p,q,d的数组。
from gmpy2 import iroot
def contfrac_attack(e,n):
convergents = continued_fraction(ZZ(e) / ZZ(int(n^2 -9/4*n +1))).convergents()
for c in convergents:
k = c.numerator()
d = c.denominator()
if pow(pow(2,int(e),int(n)),int(d),n) == 2:
phi = (e*d - 1)//k
p_add_q = iroot(n^2+1 -phi +2*n,2)[0]
p_sub_q = iroot(n^2+1 -phi -2*n,2)[0]
p = (p_add_q + p_sub_q)//2
q = n//p
#if p<q:
# p,q = q,p
#print('[',p,',',q,',',d,'],')
return p,q,d
N = [data[i] for i in range(0,len(data),2)]
E = [data[i+1] for i in range(0,len(data),2)]
pqd = []
for e,n in zip(E,N):
p,q,d = contfrac_attack(e,n)
pqd.append([p,q,d])
2,通过MT19937来恢复random求p,q
由于给出的p,q是什么顺序未知这里需要一个爆破2^7很小
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA
from extend_mt19937_predictor import ExtendMT19937Predictor
from gmpy2 import invert,next_prime
from math import lcm
import itertools
bits = 1024
e = 0x10001
def gen1(bits: int):
p = (getrandbits(bits - 32) << 32)
return next_prime(p)
def gen2(bits: int):
p = (predictor.predict_getrandbits(bits - 32) << 32)
return next_prime(p)
for o in itertools.product([0,1], repeat=7):
try:
predictor = ExtendMT19937Predictor()
for i,v in enumerate(pqd):
#print(v)
predictor.setrandbits(v[o[i]]>>32, 992) #31+31+30
predictor.setrandbits(v[1-o[i]]>>32, 992)
predictor.setrandbits(v[2]>>32, 960)
except:
continue
p,q = gen2(bits),gen2(bits)
n = p*q
phi = lcm(p-1,q-1)
d = invert(e, phi)
privateKey = RSA.construct((int(n), int(e), int(d), int(p), int(q)))
Cipher = PKCS1_v1_5.new(privateKey)
try:
flag = (Cipher.decrypt(c,'\x00'))
print(flag)
except:
continue
#b'NepCTF{c4e4356067fb3bedc53dde7af59beb1c}'
对于用PKCS1打包的RSA还要用它来解,并且题上用咱就用咱lcm(p-1,q-1)和(p-1)*(q-1)不相等。
MT19937在填入数据的时候,如果重复部分有误会报错,这时候应该就是p,q的顺序不正确了。这题一共得到644组32位随机数有20组,比624多20组这样,在这里通过的后边都正确,但是有几组在后边生成时并未用到,所以有多组相同的解
recover
这题作一半卡了,脑子有点晕了,没看到提示flag的格式。
from math import ceil
from hashlib import md5
from Crypto.Util.number import *
#from secret import key, flag
key = b'bfaxctvpacpfxsrljavvvmsi'
flag = b'flag{flag_is_the_readable_key_whose_md5_starts_with_3fe04}'
def MD5(x): return md5(x).hexdigest()
assert (len(flag) == 58)
assert (len(key) == 24)
P = [[0, 2, 3, 4, 5, 6],
[1, 4],
[0, 3],
[0, 3, 4, 5],
[0, 1, 2, 3, 4, 7],
[2, 3, 4, 5, 6],
[0, 1, 2, 3],
[1, 2, 3, 4, 5, 7],
[8, 12, 13, 14],
[8, 11, 12, 13, 15],
[9, 12, 15],
[11, 13, 15],
[8, 9, 10, 12, 13, 15],
[8, 9],
[11, 13, 14],
[10],
[16, 19, 20],
[16, 18, 19, 20, 21],
[16, 17, 18, 19],
[17, 18, 20, 22, 23],
[17, 19, 23],
[17, 18, 20, 21, 23],
[16, 18, 20, 21, 22, 23],
[16, 17, 18, 20, 21, 22, 23],
[25, 26, 29, 31],
[25, 26],
[26, 28, 30],
[27, 28, 29, 30, 31],
[25, 29],
[25, 26, 30, 31],
[28, 29, 30, 31],
[24, 26, 29, 31],
[35, 36, 39],
[33, 35, 38],
[33, 35, 37, 39],
[32, 33, 34, 35, 37, 38],
[32, 33, 34, 35, 37, 39],
[35, 38],
[33, 34, 38, 39],
[33, 34, 39],
[41, 42, 43, 44, 47],
[40, 41, 42, 45, 47],
[41, 42, 45, 47],
[40, 43, 44, 46],
[41, 46, 47],
[41, 42, 43, 44, 46, 47],
[41, 42, 44, 45],
[40, 41, 42, 45, 46],
[48, 50, 51, 52, 53, 54, 55],
[48, 49, 50, 52, 53, 54, 55],
[49, 55],
[48, 49, 50, 51, 52, 54],
[52, 53],
[48, 49, 53, 54, 55],
[48, 49, 52, 55],
[48, 49, 51, 52, 55],
[58, 59],
[56, 61, 63],
[57, 63],
[56, 59, 60],
[61, 63],
[57, 58, 61, 62, 63],
[57, 58],
[60, 62]]
def enc(v, keys, le):
t = v
for i in keys:
q = []
for j in P:
tmp = 0
for k in j:
tmp ^= t[k]
q.append(tmp)
t = [int(q[j]) ^ int(i[j]) for j in range(le)]
return t
keys = []
for i in range(len(key)//8):
l = bytes_to_long(key[i*8:i*8+8])
m = bin(l)[2:].zfill(8*8)
keys.append([int(i) for i in m])
fb = bin(bytes_to_long(flag))[2:].zfill(ceil(len(flag)/8)*8*8)
ciphertext = ""
for i in range(0, len(fb), 64):
t = enc([int(j) for j in fb[i:i+64]], keys, 64)
ciphertext += "".join([str(j) for j in t])
print(ciphertext)
cipher = '11101100100000110101100101100001100111011100100111000000010110000110011011000100110101110111010000100100001100010011001100010100101000110001011101000000100010101000000110000110011110001101110110110111000000100010011011011011101011101000000000100010000101001110100101011000001110010000000000100110001101110011111010001100101101111010101111101110100110101010011010011010101110100001001101100110010000010000011100100101111010010000011001000110000100110111100010101011000100100111010000101010110110001010110101111111'
print(cipher)
这里P有64组数据,很显然每8个一组,可以看成m*A=c的运算,由于key有24字节,这个运算共进行了3次,也就是对一个字符 ((m*A+key1)*A + key2)*A+key3对一个字节这样加密以后,key可能是多组的。
由于给出了flag的格式flag{...}58个字符前补0成64个分8*8,这样按列每组都给了一两个明文,其它明文提示是小字字母(想错了),所以很容易爆破出一组key来。用这个key求出其它字符。每次改一下idx就能求出一列数据,后来有几组出不来,发现不只是小写字母。说是爆破其实量极小,秒出的那种。
idx = 4
v0 = [0,0,0,0,0,0,ord('f'),ord('l')]
tp = P[idx*8: idx*8+8]
tP = [[i-idx*8 for i in t] for t in tp]
print(tP)
for v1 in 'l':
for v in 'h':
vs = get_v(v0[idx],ord(v1), ord(v))
cs = [ciphers[idx], ciphers[idx + 8], ciphers[idx + 16]]
key0 = get_key(vs,cs, tP)
if key0 == False:
continue
print('Try:', idx, v1, v)
for i in range(8):
ok = False
for v2 in tab:
t = enc([int(i) for i in bin(ord(v2))[2:].zfill(8)], key0, 8, tP)
if all(int(ciphers[idx + i*8][j])==t[j] for j in range(8)):
ok = True
print(i,v2)
if not ok:
break
else:
print(f'key {idx}', key0)
'''
000000fl
ag{flag_
is_the_r
eadable_
key_whos
e_md5_st
arts_wit
h_3fe04}
'''
#flag{flag_is_the_readable_key_whose_md5_starts_with_3fe04}
然后第2步卡了,这里说flag是可读的且md5头是这个。这里一时没想到flag的格式,所以每组可能的key有100多组,8组就有巨多,就没法弄了。后来看WP,原来提示的那一句在这里用。如果真正的key也是有flag{...}格式的话,那的提示的有6组,爆破就没几个了。
每求出每组key的组合
def get_int(aa):
return [int(''.join(map(str, a)),2)for a in aa]
def get_list(k1,k2,k3):
return [[int(i) for i in bin(ord(a))[2:].zfill(8)] for a in [k1,k2,k3]]
def get_key2(vs,cs,tP):
keys = [BitVec(f'key_{i}',1) for i in range(24)]
key_arr = [keys[i:i+8] for i in range(0,24,8)]
print(vs)
print(cs)
print(tP)
s = Solver()
s.add(keys[0] == 0)
s.add(keys[8] == 0)
s.add(keys[16] == 0)
for v,c in zip(vs,cs):
t = enc(v, key_arr,8, tP)
for i,tk in enumerate(t):
s.add(tk == int(c[i]))
allkey = []
while s.check() == sat:
d = s.model()
rkey = [d[keys[i]].as_long() for i in range(24)]
rkey_arr = [rkey[i:i+8] for i in range(0,24,8)]
#print(rkey_arr)
tmp = get_int(rkey_arr)
if all(0x20<=v<0x7f for v in tmp):
allkey.append(tmp)
condition = []
for i in range(24):
condition.append(keys[i] != rkey[i])
s.add(Or(condition))
return allkey
flag = b'flag{flag_is_the_readable_key_whose_md5_starts_with_3fe04}'
vs = [0]*6 + [v for v in flag]
#print(vs)
tab = string.ascii_lowercase
tabs = list('flag{') + [tab]*18 + ['}']
print(tabs)
for idx in range(8):
tp = P[idx*8: idx*8+8]
tP = [[i-idx*8 for i in t] for t in tp]
tv = [[int(i) for i in bin(vs[j*8 + idx])[2:].zfill(8)] for j in range(8)]
tc = [ciphers[idx + j*8] for j in range(8)]
allkey = []
for k1 in tabs[idx]:
for k2 in tabs[idx+8]:
for k3 in tabs[idx+16]:
key0 = get_list(k1,k2,k3)
for i,v2 in enumerate([vs[i*8+idx] for i in range(8)]):
t = enc([int(i) for i in bin(v2)[2:].zfill(8)],key0, 8, tP)
if int(ciphers[idx + i*8],2) == int(''.join(map(str,t)),2):
allkey.append(k1+k2+k3)
print(list(set(allkey)))
然后对这些爆破,然后眼工找到一组能读的。
k = [
['fcw', 'fdo', 'fef'],
['lnd', 'lxn', 'lev'],
['asz', 'are', 'aqi', 'apv'],
['gan', 'gkj', 'gnf', 'gtr', 'gdb'],
['{ok', '{qy', '{bx'],
['cbk', 'ahm', 'ygt', 'joe', 'lio', 'tir', 'arr', 'jxc', 'ypr', 'vnm', 'yjm', 'ldv', 'agi', 'cor', 'nat', 'hre', 'gng', 'ppe', 'vct', 'esg', 'nvr', 'rba', 'lsp', 'pgc', 'tsm', 'tfv', 'vyk', 'tqp', 'vvo', 'pjz', 'vai', 'ajp', 'cum', 'rug', 'lqm', 'nci', 'nyv', 'jmx', 'apo', 'yhp', 'vlp', 'juz', 'aet', 'rwz', 'eix', 'hpx', 'hec', 'tko', 'nlm', 'jwg', 'hjg', 'rzc', 'cxt', 'cwp', 'tdk', 'gve', 'rox', 'eke', 'gya', 'eqz', 'cmo', 'yei', 'phg', 'vtr', 'yro', 'czi', 'nnp', 'hhz', 'rme', 'gac', 'eda', 'lkr', 'glz', 'lfk', 'gtx', 'nto', 'prx'],
['kjy', 'hqr', 'qpb', 'tyf', 'vrs', 'tsi', 'cht', 'mxv', 'wbm', 'pfd', 'lhh', 'qzm', 'gqn', 'wdu', 'osc', 'xbq', 'aov', 'ept', 'fap', 'mry', 'yro', 'oyl', 'pjs', 'bre', 'cnl', 'dfr', 'raf', 'tuq', 'cdc', 'nir', 'qvz', 'ytw', 'whb', 'rmq', 'evl', 'sqx', 'uix', 'ikc', 'wnz', 'fmg', 'ueo', 'dje', 'aey', 'lnp', 'xnf', 'zes', 'fgh', 'ain', 'igt', 'ucw', 'bxj', 'nee', 'hwj', 'lbg', 'mta', 'zid', 'jph', 'aca', 'noj', 'ial', 'vtk', 'rki', 'kfn', 'zck', 'plk', 'jzg', 'xdi', 'kla', 'jvp', 'gwv'],
['zr}', 'pf}', 'xw}', 'rc}']]
from hashlib import md5
v =[0]*8
for v[0] in k[0]:
for v[1] in k[1]:
for v[2] in k[2]:
for v[3] in k[3]:
for v[4] in k[4]:
for v[5] in k[5]:
for v[6] in k[6]:
for v[7] in k[7]:
key = ''.join(''.join(r[i] for r in v) for i in range(3))
m = md5(key.encode()).hexdigest()
if m.startswith('3fe04'):
print(key, m)
#flag{hardertorecoverkey}
'''
flag{aapcnsabjcfwdznxpa} 3fe042d564d4e19a99c18038e95a4923
flag{npxcxsnovlwwnzfkrk} 3fe044e1e71f56918db24bc143cbdd02
flag{chrcxsnqmwcwnzfyoj} 3fe045110e1506d003ae582f22ce3d6c
flag{gsrcxpnbtqcwnvfxxx} 3fe044fcc37311bc71e76987e341ee16
flag{ycrcxptopncwnvrkrl} 3fe049dcd66d48a19f824ed9dfd51ae2
flag{vxrceraocncwvenktf} 3fe0477ae63f44db4e5657ae34b71f8e
flag{yrpcepkbrkfwvvjxoi} 3fe040c7d8ff041d0784e93b952f206a
flag{gvxdxsdqytwonzbyak} 3fe043b101bbbec84b04027f4872e6a5
flag{rwzdxpnobbronvfkam} 3fe04ea7bc9169f9565730a5786a767b
flag{hardertorecoverkey} 3fe0442d2939da2767c07fe4b735a917
flag{gczenstoldrfdzrkzc} 3fe04b78470cb999ecce16590b5a2a04
flag{nfpexsnqnmffnzfypg} 3fe04a6cce75c141462498c92e7517bf
flag{afpeesdbjgffvzbxph} 3fe04500376029ba4432e8af5d79b00b
'''
后边两个恩格码机的题,第1个是基于明文不出现在密文中,比如明文第5位是A则密文中第5位一定不是A。
第二个基于某个论文。跳过。
secureAgg
这个题目很长,如果能有时间认识读题就能答。
AggServer.py
from Crypto.Util.number import *
from User import User
class AggServer:
def __init__(self,mbits):
self.N=8
self.g=2
self.p=0x9f785dd75d97d7dea66a89a038e7c880b962e526fa9d0f14639de8d82b953fbf0e01739495df3d5fdb189aa079c70e9cc49c7390c2fd3166d91fe8b511f918a7
self.mbits=mbits
self.users=[ User(mbits,i) for i in range(self.N)]
self.M=getPrime(mbits+self.N.bit_length()+1)
self.params=(self.g,self.p)
def genKeys(self):
for u in self.users:
u.genKey(self.params)
for u in self.users:
u.agree(self.users,self.params[1])
def get_enc_list(self): #泄露
enc_list=[]
for u in self.users:
enc_data=u.get_enc_data(self.M)
enc_list.append(enc_data)
return enc_list
def aggregate(self): #生成key
self.key=sum([ u.data for u in self.users])%self.M
return self.key
def update(self):
for u in self.users:
u.update_data(self.key)
def system_info(self):
info = ""
info += "System params: \n"
info += f"g={self.params[0]}\np={self.params[1]}\nM={self.M}\n"
info += "User pubkeys: \n"
info += f"pubkeys={str([ u.pub for u in self.users])}\n"
return info
chall.py
# !/usr/bin/env python
from Crypto.Util.number import *
from hashlib import sha256
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from base64 import b64encode
from FLAG import flag
from AggServer import AggServer
import socketserver
import os, sys, random
import signal, string
ROUNDS=20
BANNER=br'''
Welcome to my Secure Aggregation System.
If you can pass my 20 rounds of agg testing, I'll give you flag~
Have fun!
'''
class Task(socketserver.BaseRequestHandler):
def _recvall(self):
BUFF_SIZE = 2048
data = b''
while True:
part = self.request.recv(BUFF_SIZE)
data += part
if len(part) < BUFF_SIZE:
break
return data.strip()
def send(self, msg, newline=True):
try:
if newline:
msg += b'\n'
self.request.sendall(msg)
except:
pass
def recv(self, prompt=b'> '):
self.send(prompt, newline=False)
return self._recvall()
def close(self):
self.request.close()
def handle(self):
signal.alarm(180)
self.send(BANNER)
self.send(b"Generating parameters...")
agg=AggServer(114)
agg.genKeys()
self.send(agg.system_info().encode())
try:
for i in range(ROUNDS):
self.send(f'#Round {i+1}'.encode())
enc_list=agg.get_enc_list()
self.send(f"enc_list={enc_list}".encode())
key=agg.aggregate()
message=''.join(random.sample(string.ascii_letters,16))
aes_key=sha256(str(key).encode()).digest()[:16]
aes=AES.new(aes_key,AES.MODE_CBC,iv=bytes(range(16)))
enc=aes.encrypt(pad(message.encode(),16))
self.send(f"enc={b64encode(enc)}".encode())
inp=self.recv(b"input your message: ").decode()
if inp==message:
self.send(b'Correct!')
else:
self.send(b'Error!')
exit(0)
agg.update()
self.send(flag)
except:
self.send(b'wtf?')
self.close()
class ThreadedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
class ForkedServer(socketserver.ForkingMixIn, socketserver.TCPServer):
pass
if __name__ == "__main__":
HOST, PORT = '0.0.0.0', 12345
server = ForkedServer((HOST, PORT), Task)
server.allow_reuse_address = True
server.serve_forever()
usr.py
from Crypto.Util.number import *
class PRG:
def __init__(self,seed):
self.state=seed
self.a=114514
self.b=1919810
self.kbits=seed.bit_length()
self.bits=[]
def gen(self):
self.state=(self.a*self.state+self.b)&((1<<self.kbits)-1)
bin_arr=list(map(int,bin(self.state)[2:].zfill(self.kbits)[::-1]))
self.bits.extend(bin_arr)
def randbits(self,n):
while len(self.bits)<n:
self.gen()
output=self.bits[:n]
self.bits=self.bits[n:]
return int(''.join(map(str,output)),2)
class User:
def __init__(self,mbits,id):
self.mbits=mbits
self.id=id
self.data=getRandomNBitInteger(mbits)
def genKey(self,params):
g,p=params
self.priv=getRandomRange(1,p)
self.pub=pow(g,self.priv,p)
def agree(self,users,p):
self.agreement_keys={}
for u in users:
if u.id!=self.id:
u_pub=u.pub
k=pow(u_pub,self.priv,p)
self.agreement_keys.update({u.id:k})
def get_enc_data(self,M):
enc=(114*self.data+514)%M
for id,k in self.agreement_keys.items():
if id>self.id:
enc+=PRG(k).randbits(self.mbits)%M
elif id<self.id:
enc-=PRG(k).randbits(self.mbits)%M
return enc
def update_data(self, key):
mask=(1<<self.mbits)-1
noise=getRandomNBitInteger(16)
self.data=(self.data ^ key ^ noise) & mask
chall.py给了一个交互的服务,如果正确20次就给flag
先成成Agg,然后每一轮都先给出 enc_list=agg.get_enc_list(),这个函数调用users
def get_enc_list(self): #泄露
enc_list=[]
for u in self.users:
enc_data=u.get_enc_data(self.M)
enc_list.append(enc_data)
return enc_list
users里的加密是个非常小的LCG
def get_enc_data(self,M):
enc=(114*self.data+514)%M
for id,k in self.agreement_keys.items():
if id>self.id:
enc+=PRG(k).randbits(self.mbits)%M
elif id<self.id:
enc-=PRG(k).randbits(self.mbits)%M
return enc
显然,这里组出enc_data 求data只需要 (sum(enc_data)-514*8)*114^-1 % M,最后在有key的情况下求AES就行了。
最后一个关于DES的太长了,没看,说是很简单。