Hackena CTF - LWE Lattice Challenge Writeup¶
Challenge Overview¶
I was given a Python script (chall.py) that implements a lattice-based cryptographic scheme and an output file containing the public parameters and encrypted flag.
Analysis¶
Understanding the Scheme¶
The challenge constructs the following:
- Secret generation: A small vector
z ∈ {-1, 0, 1}^nis generated -
Lattice basis
whereBs: A special structured matrix of the form:k = 12,n = 40, andq = 12289 -
Secret
s: Computed ass = z · Bs (mod q) - LWE instance:
b = s·A + e (mod q)wheree ∈ {-1, 0, 1}^mis small error - Encryption: The flag is XOR'd with
SHA256(s)
Key Parameters¶
q = 12289(prime modulus)n = 40(secret dimension)k = 12(identity block size)m = 60(LWE sample dimension)
Solution Strategy¶
The goal is to recover s to derive the decryption key. Since s = z·Bs where z is small, this is a bounded distance decoding (BDD) problem that can be solved using lattice reduction.
Kannan's Embedding Technique¶
I constructed a lattice that encodes both constraints:
1. s = z·Bs (s lies in the Bs lattice)
2. b = s·A + e (LWE relation with small error)
Substituting, I got: z·(Bs·A) + e ≡ b (mod q)
I built the embedding lattice:
Where:
- c is a scaling factor for the z components
- W is the embedding weight to identify the target vector
A short vector in this lattice corresponds to (e, c·z, W) where both e and z are small.
Implementation¶
import json
import hashlib
from fpylll import IntegerMatrix, BKZ
# Load data
with open("output.txt", "r") as f:
data = json.load(f)
q, A, b, Bs = data["q"], data["A"], data["b"], data["Bs"]
enc_flag = bytes.fromhex(data["enc_flag_hex"])
n, m = len(Bs), len(A[0])
# Compute Bs * A (mod q)
BsA = [[sum(Bs[i][k] * A[k][j] for k in range(n)) % q
for j in range(m)] for i in range(n)]
# Build embedding lattice
W, Z_scale = 100, 10
dim = m + n + 1
L = IntegerMatrix(dim, dim)
# Fill lattice blocks
for i in range(m):
L[i, i] = q
for j in range(n):
for i in range(m):
L[m + j, i] = BsA[j][i]
L[m + j, m + j] = Z_scale
for i in range(m):
L[m + n, i] = b[i]
L[m + n, m + n] = W
# Run BKZ reduction
BKZ.reduction(L, BKZ.Param(30))
# Search for solution
for i in range(dim):
row = [L[i, j] for j in range(dim)]
if abs(row[-1]) != W:
continue
sign = 1 if row[-1] == W else -1
z = [sign * row[m + j] // Z_scale for j in range(n)]
if max(abs(x) for x in z) > 1:
continue
# Try both z and -z
for z_try in [z, [-x for x in z]]:
s = [sum(z_try[j] * Bs[j][i] for j in range(n)) % q for i in range(n)]
sA = [sum(s[i] * A[i][j] for i in range(n)) % q for j in range(m)]
e = [(b[j] - sA[j]) % q for j in range(m)]
e_norm = [x if x <= q//2 else x - q for x in e]
if max(abs(x) for x in e_norm) <= 1:
# Decrypt flag
key = hashlib.sha256(b"".join(
int(s[i]).to_bytes(2, "little") for i in range(n)
)).digest()
flag = bytes(enc_flag[i] ^ key[i % len(key)]
for i in range(len(enc_flag)))
print(f"Flag: {flag.decode()}")