Executive Summary
Challenge: ToyPQC
CTF: Daily Alpacahack 2026 #27
Category: Cryptography
Difficulty: Hard
Flag: Alpaca{sa6em4th_i5_u5efu1^_^}
ToyPQC is a cryptography challenge based on the Learning With Errors (LWE) problem. We are given an overdetermined system of linear equations over a finite field, perturbed by a “noise” vector. The critical vulnerability lies in the small size of the error vector (binary elements, length 10), which allows for a brute-force attack to recover the correct error, solve the linear system, and decrypt the flag.
1. Challenge Analysis
The provided SageMath script implements an LWE-based system:
- Setup: Defines a finite field with .
- Secret (): The flag is split into 7 chunks, forming the secret vector .
- Matrix (): A random matrix is generated.
- Error (): A random error vector is generated.
- Encryption: The public vector is computed as .
We are provided with and .
2. Vulnerability Assessment
The equation represents a noisy system of linear equations. while LWE is typically hard, the specific parameters here make it vulnerable.
The error vector has length , and each element is chosen from . Total search space: .
This extremely small search space allows us to brute-force the error vector .
3. Solution Strategy
- Iterate: Generate all possible binary vectors for .
- Clean: Compute candidate target .
- Solve: Attempt to solve .
- If is incorrect, the system is inconsistent (no solution).
- If is correct, we find the unique .
- Decode: Convert the resulting secret vector back to bytes.
4. Solve Script (SageMath)
import itertoolsfrom Crypto.Util.number import long_to_bytes
# Challenge Parametersp = 8380417n = 7m = 10F = GF(p)
# Assume A (matrix) and b (vector) are loaded from challenge output# A = matrix(F, ...)# b = vector(F, ...)
print("[-] Starting Brute-Force on Error Vector...")
possible_errors = itertools.product([0, 1], repeat=m)
for err_tuple in possible_errors: e_guess = vector(F, err_tuple) target = b - e_guess
try: # Attempt to solve A * s = target s_candidate = A.solve_right(target)
flag_content = b"" for val in s_candidate: flag_content += int(val).to_bytes(3, "big")
full_flag = f"Alpaca{{{flag_content.decode()}}}" print(f"[+] Success! Error vector: {err_tuple}") print(f"[+] FLAG: {full_flag}") break
except ValueError: continue except UnicodeDecodeError: continue5. Conclusion
By exploiting the low entropy of the error vector, we reduced the problem to a trivial brute-force check. Validating each of the 1024 possibilities against the linear system allowed us to isolate the correct error and recover the plaintext.