""" This is an (incomplete) implement for a new (and experimental) secret/password sharing scheme. The idea is simple. Basically, a secret or password is turned into a set of finite field elements and each share is just a linear combination of these elements. On the other hand, when enough shares are collected, the finite field elements are determined, allowing the original secret or password to be recovered. """ from typing import List from secrets import randbelow import string ALLOWED_CHARS = string.ascii_lowercase + string.digits + "_" P = len(ALLOWED_CHARS) INT_TO_CHAR = {} CHAR_TO_INT = {} for _i, _c in enumerate(ALLOWED_CHARS): INT_TO_CHAR[_i] = _c CHAR_TO_INT[_c] = _i def get_shares(password: str, n: int, t: int) -> List[str]: """ Get password shares. Args: password: the password to be shared. n: the number of shares returned. t: the minimum number of shares needed to recover the password. Returns: the shares. """ assert len(password) <= t assert n > 0 ffes = [CHAR_TO_INT[c] for c in password] ffes += [randbelow(P) for _ in range(t - len(password))] result = [] for _ in range(n): coeffs = [randbelow(P) for _ in range(len(ffes))] s = sum([x * y for x, y in zip(coeffs, ffes)]) % P coeffs.append(s) result.append("".join(INT_TO_CHAR[i] for i in coeffs)) return result def combine_shares(shares: List[str]) -> str: raise Exception("unimplemented") def main(): pw_len = 16 password = "".join(INT_TO_CHAR[randbelow(P)] for _ in range(pw_len)) # how about n < t :D n = 16 t = 32 for _ in range(2022): line = input() if line == password: from secret import FLAG print(FLAG) return else: print(get_shares(password, n, t)) if __name__ == '__main__': main()
get_shares
という関数で、passwordをシェアするための配列を生成してくれるSecret Sharing
パスワードの文字数、シェアの数、秘密の復元に必要なシェアの数
シェアの生成 where
シェアは2022回まで生成できて、は共通。目的はの復元
行列の形で書けばこう。
さらにこう書いても良い。 とする
このとき、がnot full-rankedならのRow Echelon Formを考えると、 となるような行が存在する(要するに掃き出し法をやった時に下の方の行は1が残らなくて0になる)
このときだけの式となる
は2022回の施行の中で共通かつ、は毎回わかっているから、となるような行について という式を16個集めてこれを解けばが求まる ( #連立方程式を解く問題 )
sageではleft_kernel
という関数で行列に対してを満たすようなleft kernelを求めることができる。これを使えば
が構成できるのでこれで解ける
(ある行だけを0にするような係数を探して組み合わせてもいいけど、こっちの方が楽……)
import string ALLOWED_CHARS = string.ascii_lowercase + string.digits + "_" P = len(ALLOWED_CHARS) INT_TO_CHAR = {} CHAR_TO_INT = {} for _i, _c in enumerate(ALLOWED_CHARS): INT_TO_CHAR[_i] = _c CHAR_TO_INT[_c] = _i F = GF(P) from ptrlib import Process sock = Process(["python3", "./shares.py"]) lhs = [] rhs = [] while True: sock.sendline("a") mat = matrix(F, [[CHAR_TO_INT[c] for c in row] for row in eval(sock.recvline().decode())]) A = mat[:16,:16] B = mat[:16,16:32] s = mat[:16,32:] # A*x + B*y = s if B.rank() != 16: # ker*(A*x + B*y) = ker*s # ker*A*x + 0 = ker*s ker = B.left_kernel().matrix() assert (ker * B).is_zero() lhs.append((ker*A)[0]) rhs.append((ker*s)[0,0]) if len(lhs) == 16: x = matrix(F, lhs).solve_right(vector(F, rhs)) password = "".join(INT_TO_CHAR[v] for v in x) sock.sendline(password) sock.interactive() quit()