# Learning with Exploitation author's writeup ## Challange summary In this challenge, I implemented multibit-variant of [Regev's cryptosystem](https://en.wikipedia.org/wiki/Learning_with_errors#Public-key_cryptosystem). ### Normal Regev's cryptosystem First, the cryptosystem generates [LWE Oracle](https://doc.sagemath.org/html/en/reference/cryptography/sage/crypto/lwe.html). The matrix $A$, vector $s$, error vector $e$, and target vector $t$ that holds: $$A\cdot s+e=t$$ and $A,t$ will be a public key. To encrypt, the cryptosystem generates vector consisting of only 0 or 1 $r$. The ciphertext $(u,v)$ of $m\in\{0,1\}$ will be: $$(u,v)=(r\cdot A,r\cdot t+mq)$$ Note that $q:=\lceil \frac{p}{2}\rceil$ Decryption will be as follows: * If $v-u\cdot s\approx 0$, $m=0$. * If $v-u\cdot s\approx q$, $m=1$. ### Extended Regev's Cryptosystem in this challenge I extended this cryptosystem to encrypt multibit rather than single bit. In this challenge, to encrypt 64-bit plaintext $0\le m<2^{64}$, I defined $q:=\lceil \frac{p}{2^{64}}\rceil$. The encryption and decryption are almost the same as the original. ## Solution Unfortunately, this trivial extensions do not work. In this cipher, the following equation holds. $$u=r\cdot A$$ $A$ and $u$ are known and $r\:(\in \{0,1\}^{100})$ is hidden. By taking one row from $A$, recovering the parameter $r$ is almost the same with attacking [Merkle–Hellman knapsack cryptosystem](https://en.wikipedia.org/wiki/Merkle%E2%80%93Hellman_knapsack_cryptosystem). It is known that, with certain conditions, [Low-Density attack](https://web.stevens.edu/algebraic/Files/SubsetSum/p229-lagarias.pdf) works well for attacking knapsack cryptosystem. In the condition given, The bit length of the element of A is far longer than 100bit (to make the cryptosystem work with 64bit plaintext). Low-density attack can be applied and $r$ can be recoverd. After $r$ is recovered, we can restore the plaintext $m$ from the equation $v=r\cdot t+mq$. ## Solver python from output import public_key, ciphertext from sage.modules.free_module_integer import IntegerLattice p = 0xfffffffffffffffffffffffffffffffeffffffffffffffff F = GF(p) d = 100 n = 10 q = p // (2 ** 64) A, T = public_key A = matrix(F, A) T = vector(F, T) N = 2 ** 64 t = 2 flag = b'' for (U, v) in ciphertext: U = vector(F, U) v = F(v) M = [] for i in range(d): g = [0 for _ in range(d + t)] for j in range(t): g[j] = int(A[i][j]) * N g[i + t] = 2 M.append(g) g = [0 for _ in range(d + t)] for i in range(t): g[i] = -int(U[i]) * N for i in range(d): g[i + t] = -1 M.append(g) for i in range(t): g = [0 for _ in range(d + t)] g[i] = p * N M.append(g) print('LLL start') lattice = IntegerLattice(M, lll_reduce=True) print('LLL end') E = lattice.reduced_basis ok = False r_recovered = None for g in E: if g[0] == 0 and all(x == 1 or x == -1 for x in g[t:]): r_recovered = vector([F((x + 1) // 2) for x in g[t:]]) print(r_recovered) print((r_recovered * A)[0:3]) print(U[0:3]) if U == r_recovered * A: ok = True break if not ok: print('attack failed') exit() m = (v - r_recovered * T) / q flag += int(m).to_bytes(8, 'big') print(flag)