RaRCTF 2021 - Shamir's Stingy Sharing 题解
题目的名字叫 Shamir's Stingy Sharing,源码会生成一个长度为 30 的 poly 数组,每个系数都是 128 bit 随机数。服务端先用 poly[0] 作为随机数种子生成 keystream,把 flag 做一次 bxor 后输出;然后只允许你提交一次整数 x,服务端返回 sum(poly[i] * x^i)。
import random, sys
from crypto.util.number import long_to_bytes
def bxor(ba1, ba2):
return bytes([_a ^ _b for _a, _b in zip(ba1, ba2)])
bits = 128
shares = 30
poly = [random.getrandbits(bits) for _ in range(shares)]
flag = open('/challenge/flag.txt', 'rb').read()
random.seed(poly[0])
print(bxor(flag, long_to_bytes(random.getrandbits(len(flag) * 8))).hex())
try:
x = int(input('Take a share... BUT ONLY ONE. '))
except:
print('Do you know what an integer is?')
sys.exit(1)
if abs(x) < 1:
print('No.')
else:
print(sum(map(lambda i: poly[i] * pow(x, i), range(len(poly)))))
在普通大整数上直接把多项式值打出来
因为一旦是在整数环里算,x 就不只是“取值点”,还可以被拿来当进位基数。那接下来要做的就不是恢复整条多项式,而是想办法把高次项全挤到高位去,让最低位只留下常数项
如果把多项式写成
并且所有系数都小于 ,那么当我们取 时有
因为所有 的项都至少带有一个 因子。
知识补充
解题思路
直接选 x = 2^129 去问一次值。由于每个系数都只有 128 bit,高次项都会被推到更高位,对结果取模 2^129 以后,剩下来的就只会是 poly[0]。
而题目最开始拿来异或 flag 的随机流正是用 poly[0] 做种子生成的,所以常数项一到手,随机流也就能完整复现。最后把密文和同长度 mask 异或一下,flag 就回来了。
def recover_seed(query_once):
x = 1 << 129
return query_once(x) % x
def unmask_flag(ciphertext, seed):
rng = random.Random(seed)
mask = bytes(rng.getrandbits(8) for _ in range(len(ciphertext)))
return bytes(c ^ m for c, m in zip(ciphertext, mask))