My neutral OTPs got destroyed last year; all that’s left are the non-neutral ones.
题目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
from secrets import randbits from Crypto.Util.number import bytes_to_long import os
flag = os.environ.get('FLAG', 'SEE{not_the_real_flag}').encode()
# get a one-time-pad in which not exactly half the bits are set defget_xorpad(bitlength): xorpad = randbits(bitlength) return xorpad ifbin(xorpad).count('1') != bitlength // 2else get_xorpad(bitlength)
from Crypto.Util.number import * from math import comb,log from tqdm import *
#tools defbinxor(bin1,bin2): assertlen(bin1) == len(bin2) res = "" for i inrange(len(bin1)): if(bin1[i] == bin2[i]): res += "0" else: res += "1" return res
defcount_known_index(bin1,known_index): res = 0 for i in known_index: if(bin1[i] == "1"): res += 1 return res
deffunc(x,k): returnint(2**(x-1) - comb(x,k))
#part1 get data withopen(r"D:\题\历届赛题\SEETF 2023\dist_non-neutrality\nn_out.txt","r") as f: temp = f.readlines() c = [] for i in temp: c.append(bin(int(i.strip()))[2:].zfill(272)) #notice that len of flag_bin is 271 bits and len of flag is 34 bytes
#part2 judge parity of hamming weight of flag odd_num = 0 even_num = 0 for i in c: if(i.count("1") % 2 == 0): even_num += 1 else: odd_num += 1 #print(even_num / odd_num) #means flag's hamming weight is even at the same time
#part3 init known_part = b"SEE{" + b"\x00"*29 + b"}" known_part = bin(bytes_to_long(known_part))[2:].zfill(272) known_index = [] known_index += [i for i inrange(32)] #prefix known_index += [(34-1) * 8 + i for i inrange(8)] #suffix known_index += [i * 8 + 32for i inrange(29)] #MSB of every byte cause printable known_index = sorted(known_index)
#part4 remove ci whose hamming weight is odd C = [] for i in c: if(i.count("1") % 2 == 0): C.append(i)
#part5 while(1): #means possibility of 1 for every bit possiblities = {} for t inrange(272): if(t notin known_index): possiblities[t] = 0
for i in trange(len(C)): resi = binxor(C[i],known_part) sumi = count_known_index(resi,known_index) x = 272 - len(known_index) k = 136 - sumi if(k <= 1): continue possible_0 = func(x-1,k) possible_1 = func(x-1,k-1) for j inrange(len(C[i])): if(j in known_index): continue if(C[i][j] == "1"): possiblities[j] += (log(possible_0) - log(possible_1)) else: possiblities[j] += (log(possible_1) - log(possible_0)) #update temp = list(sorted(possiblities.items(), key = lambda kv:(abs(kv[1]), kv[0])))[::-1] high_level = temp[:10] for i in high_level: known_index.append(i[0]) temp = list(known_part) for i in high_level: if(i[1] > 0): temp[i[0]] = "1" else: temp[i[0]] = "0" known_part = "".join(temp) print(long_to_bytes(int(known_part,2))) print(len(possiblities))
I’ve been learning about one-time pads, but they sometimes generate more 1 bits than 0 bits or vice-versa. I think this leaks some information, so it’s better to ensure that the one-time pad is always neutral.
题目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
from secrets import randbits from Crypto.Util.number import bytes_to_long
# get a one-time-pad in which exactly half the bits are set defget_xorpad(bitlength): xorpad = randbits(bitlength) return xorpad ifbin(xorpad).count('1') == bitlength // 2else get_xorpad(bitlength)
defleak_encrypted_flag(): from secret import flag return bytes_to_long(flag.encode()) ^ get_xorpad(len(flag) * 8)
# I managed to leak the encrypted flag a few times if __name__ == '__main__': for _ inrange(200): print(leak_encrypted_flag())
#tools defbinxor(bin1,bin2): assertlen(bin1) == len(bin2) res = "" for i inrange(len(bin1)): if(bin1[i] == bin2[i]): res += "0" else: res += "1" return res
defcount_known_index(bin1,known_index): res = 0 for i in known_index: if(bin1[i] == "1"): res += 1 return res
#part1 get known bits index known_part = b"SEE{" + b"\x00"*35 + b"}" known_part = bin(bytes_to_long(known_part))[2:].zfill(320) known_index = [] known_index += [i for i inrange(32)] #prefix known_index += [(40-1) * 8 + i for i inrange(8)] #suffix known_index += [i * 8 + 32for i inrange(35)] #MSB of every byte cause printable known_index = sorted(known_index)
#part2 remove known bits and LLL C = [] for i in c: temp = bin(i)[2:].zfill(320) resi = binxor(temp,known_part) sum1 = 160 - count_known_index(resi,known_index) vec = [] sum2 = 0 for j inrange(320): if(j notin known_index): vec.append(1-2*int(temp[j])) sum2 += int(temp[j])^2 vec.append(-(sum1 - sum2)) C.append(vec)
C = Matrix(ZZ,C).T L = block_matrix( [ [C,identity_matrix(246)] ] ) L[:,:200] *= 2^20 res = L.LLL()[0]
#part3 get flag m = res[200:-1]
flag = list(known_part) for i inrange(320): if(i notin known_index): flag[i] = "*"
ind = 0 for i inrange(320): if(flag[i] == "*"): ind = i break
for i in m: if(i == 0): flag[ind] = "0" else: flag[ind] = "1" if(all(i == "0"or i == "1"for i in flag)): break while(flag[ind] != "*"): ind += 1
c = C = [] for i inrange(len(c)): temp = bin(c[i])[2:].zfill(320) vec = [] for j in temp: if(j == "0"): vec.append(1) else: vec.append(-1) C.append(vec)
C = Matrix(ZZ,C).T L = block_matrix( [ [C,identity_matrix(320)] ] ) L[:,:200] *= 2^20
res = L.BKZ()[0]
m = res[200:] flag1 = "" flag2 = "" for i in m: if(i == 1): flag1 += "0" flag2 += "1" else: flag1 += "1" flag2 += "0" print(long_to_bytes(int(flag1,2))) print(long_to_bytes(int(flag2,2)))