Try   HackMD

Some Note for UIUCTF2022 Crypto Challenges

wringing_rings

import sympy as sp import random import signal from secret import FLAG secret = random.SystemRandom().randint(1, 500_000) _MAX = 10 ** (len(str(secret)) - 1) # generating a polynomial def _f(secret, minimum=3): coeffs = [secret] + [ random.SystemRandom().randint(1, _MAX) for _ in range(minimum - 1) ] # print("Secret Polynomial:") # f_str = str(secret) # for i, coeff in enumerate(coeffs[1:]): # f_str += " + " + str(coeff) + "*x^" + str(i + 1) # print(f_str) def f(x): res = 0 for i, coeff in enumerate(coeffs): res += coeff * x ** (i) return res return f def gen_shares(secret, minimum=3): f = _f(secret, minimum) shares = [(i + 1, f(i + 1)) for i in range(minimum)] return shares def challenge(secret, minimum=3): shares = gen_shares(secret, minimum) points = random.sample(shares, minimum - 1) points.sort() return points def main(): minimum = 10 points = challenge(secret, minimum) print("[SSSS] Known shares of the secret polynomial: ") for point in points: print(f" {point}") print() signal.alarm(60) guess = int(input("[SSSS] Enter my secret: ")) if guess == secret: print(f"[SSSS] Correct! {FLAG}") else: print("[SSSS] Incorrect...") if __name__ == "__main__": main()

This is related to Shamir's Secret Sharing(SSS) scheme. But secret polynomial is integer polynomial, not over finite field.

Interpolation of Polynomial

The scheme is based on the fact that univarite polynomial

f(x) whose degree
d
is uniquely determined as distinct
(d+1)
points
(x0,f(x0)),,(xd,f(xd))
. This fact can be explained as Chinese Remainder Theorem(CRT) over univariate polynomial.

If

f(xi)=yi, then
f(x)=yimod(xxi)

by polynomial remainder theorem. CRT states that we can recover polynomial
F(x)
such that
F(x)=yimod(xxi) (i=0,1,,d)
uniquely defined over
modi(xxi) .
deg(F)=d
(same as
f(x)
) and
deg(i(xxi))=d+1
, we have
F(x)=f(x)
by modulus uniqueness.

Lagrange Interpolation represents explicit formula of recovered polynomial.

F(x)=iyiji(xxj)ji(xixj)

Note that the polynomial may not be over the integer ring

Z[x], but the rational field
Q[x]
. This causes an vulnerablility for integer version of SSS.

My First Solution of the Challenge

In the challenge, we have to find secret whose determined as

f(0)=secret for degree 9 polynomial
f(x)
by given 9 points. At first glance, we cannot recover unique polynomial
f(x)
by 9 points only. However, the condition that recovered polynomial is over
Z[x]
is so restrictive.

My solution is straightforward. I recovered polynomial

f(x) by adding symbolical point
(0,t)
(
t
is variable). Then, I solved the equation for all coefficient of
f(x)
is in
Z
, that is, the numerator modulo the denominator is
0
. The solution is
secret
.

This leads me the folowing code.

from pwn import * import cypari2 from Crypto.Util.number import * from sympy.ntheory.modular import solve_congruence proc = remote('ring.chal.uiuc.tf', 1337) proc.recvline() proc.recvline() minimum = 10 points = [] for _ in range(minimum-1): points.append(eval(proc.recvline().strip(b'\n').split(b" ")[1])) print(points) pari = cypari2.Pari() result = None for ele in points: if result == None: result = pari.Mod(ele[1], (pari('x') - ele[0])) else: result = pari.chinese(result, pari.Mod(ele[1], (pari('x')-ele[0]))) print(result) result = pari.chinese(result, pari.Mod(pari('t'), pari('x'))) print(result) tequ = [] resultpol = result.lift() for i in range(1, minimum): coef = pari.polcoeff(resultpol, i, pari('x')) numer = pari.numerator(coef, 1) denom = int(pari.denominator(coef, 1)) tequ.append((-inverse(int(pari.polcoeff(numer, 1, pari('t'))), denom)*int(pari.polcoeff(numer, 0, pari('t'))), denom)) sol = (solve_congruence(*tequ)) print(sol) sol = sol[0] print(proc.recvuntil(b': ')) proc.sendline(str(sol).encode()) print(proc.recvline())

(nearly) uniqueness

On CTF, I obtained flag and it works repeatedly, I think that recovered polynomial is unique. @grhkm pointed why it is, I could not explain exactly. But we can understand nearly uniqueness by using CRT construction.

Given 9 points

(x0,y0),,(x8,y8), we can recover
F(x)
such that
F(xi)=yi (i=0,,8)
.
F(x)
is uniquely defined
modi(xxi)
. So we have
f(x)=F(x)+c(x)i(xxi) .

deg(f)=9
,
deg(i(xxi))=9
and
deg(F)=8
, so
deg(c)=0
, that is,
c(x)
is a constant. And
i(xxi)
is monic, so
c(x)Z
. Then,
F(x)Z[x]
. By substituting
x=0
, we have
secret=F(0)+ci(xi) .

This means that
secret
is uniquely defined
modixi
.

So I did not have to recover symbolic polynomial. I only have to compute

F(0)modi(xi).

On this challenge,

ixi is
10!/a
for some
a{1,,10}
.
We know
1<=secret<=500000
, but
10!/7=518400>500000
, so my solution is unique if
a<=7
. Even if
a=10
,
10!/10=362880
, then my (smallest) solution is correct with the probability
362880/500000=0.73
. The overall probability is

>>> sum([min(((10*9*8*7*6*5*4*3*2*1)/i)/500000, 1.0) for i in range(1, 10+1)])/10 0.9439359999999999

Of cource, we can compute

c and check that recovered polynomial is valid (all coefficients except secret is positive and
<=_MAX
) for reliablity of solution.