# easy pseudo random - zer0pts ctf 2021 ## challenge The following code and the outputs was given. It seems that the flag is encrypted with random numbers. We can get the upper k bits of two consecutive values, named w0 and w1. We can get the flag when the prediction of pseudo random numbers can be achieved using these. ``` python= # task.sage from Crypto.Util.number import* from flag import flag nbits = 256 p = random_prime(1 << nbits) Fp = Zmod(p) P.<v> = PolynomialRing(Fp) b = randrange(p) d = 2 F = v^2 + b v0 = randrange(p) v1 = F(v0) k = ceil(nbits * (d / (d + 1))) w0 = (v0 >> (nbits - k)) w1 = (v1 >> (nbits - k)) # encrypt m = bytes_to_long(flag) v = v1 for i in range(5): v = F(v) m ^^= int(v) print(f"p = {p}") print(f"b = {b}") print(f"m = {m}") print(f"w0 = {w0}") print(f"w1 = {w1}") ``` ## solution Let $F(v) = v^2 + b$ over $\mathbb{F}_p$ and $n$ be the nbits. Then $v_0$ and $v_1$ are given by the following equations. $$\begin{eqnarray} v_0 &=& 2^{n-k} w_0 + x \\ v_1 &=& 2^{n-k} w_1 + y \end{eqnarray}$$ Here $x$ and $y$ are unknown quantities. If they are found, we can expect the pseudo random sequences. Expanding $v_1$, we get further $$\begin{eqnarray} v_1 &=& F(v_0) \\ &=& (2^{n-k}w_0 + x)^2 + b \end{eqnarray}$$ Thus, we get the following equation $$\begin{eqnarray} f(x, y) &=& 2^{n-k}w_1 + y - ((2^{n-k}w_0 + x)^2 + b) \\ &=& y + c_2x^2 + c_1x + c_0 \\ &=& 0\end{eqnarray}$$ where $c_i \ (i=0,...,2)$ are suitable constants (They are explicitly depend on know parameters). For easy understanding, the equation is written $y + c_2x^2 + c_1x + c_0 \equiv 0 \mod p$ as a congruence in integers. Now, $k = \lceil \frac{nd}{d + 1} \rceil \ where\ d = \deg f$, and therefore we can believe that required roots are found by Coppersmith's method. ```python= # solve.sage from Crypto.Util.number import * from small_roots import small_roots nbits = 256 with open("output.txt") as f: while True: buf = f.readline() if not buf: break exec(buf) Fp = Zmod(p) P.<v> = PolynomialRing(Fp) F = v^2 + b Pf.<x, y> = PolynomialRing(Fp) d = 2 k = ceil(nbits * (d / (d + 1))) delta = float(1 / (d + 1)) bounds = (floor(p^delta), floor(p^delta)) f = (y + w1 * pow(2, nbits - k)) - ((x + w0 * pow(2, nbits - k))^2 + b) # multivariate coppersmith res = small_roots(f, bounds) print(res[0]) x0 = res[0][0] x1 = res[0][1] v0 = x0 + w0 * pow(2, nbits - k) v1 = x1 + w1 * pow(2, nbits - k) print(v0, v1) v = v1 for i in range(5): v = F(v) m ^^= int(v) print(long_to_bytes(m)) ``` Here is the flag ```zer0pts{is_blum_blum_shub_safe?}```