- Category: Crypto
- Score: 371/500
- Solves: 6
I don’t know how to design a secure stream cipher, but a large key space should be sufficient to block most attacks right?
This challenges implements a stream cipher base on a non-linear combination function to combine 4 LFSR, each with 128 bits of state. The target is to recover its internal state given
The bit
function is defined as:
def bit(self):
x = self.lfsr1() ^ self.lfsr1() ^ self.lfsr1()
y = self.lfsr2()
z = self.lfsr3() ^ self.lfsr3() ^ self.lfsr3() ^ self.lfsr3()
w = self.lfsr4() ^ self.lfsr4()
return (
sha256(str((3 * x + 1 * y + 4 * z + 2 * w + 3142)).encode()).digest()[0] & 1
)
We know that
Construct its truth table and we can analyze it with sage, and its algebraic normal form is:
$$ f(x,y,z,w)=xyz+xyw+xzw+y*w+y+z $$
We can try to see if
But every LFSR have 128 bits of state, naive correlation attack would require a
- Fast correlation attacks on certain stream ciphers
- Fast Correlation Attacks: Methods and Countermeasures (This is the easiest one to understand imo)
- A Fast Correlation Attack Implementation
It is recommended to read them yourself, but I would do a brief explanation on how it works here.
The notation here follows "Fast Correlation Attacks: Methods and Countermeasures".
For a (binary) LFSR with a keystream
that is, its feedback equations and its shifts.
Also, we can also square it to get some equations that always hold:
I am not exactly sure why it is called square, but I believe it comes from its feedback polynomial
$f(x)=1+x^2+x^3$ and its square$f(x^2)=1+x^4+x^6$ .If my understanding is correct, it actually means any
$g(x)$ that is a multiple of$f(x)$ also work as a satisfying equation. And$f(x^2)=f(x)$ holds in$\mathbb{F}_2$ is a special case.
Anyway, this means from a LFSR feedback polynomial, we can obtain a bunch of relations that always hold by shifting and squaring it.
Then if we consider a LFSR with a small bias:
Intuitively, if one of the output bit
Of course, it is also possible that not all of the top
- Compute a probability
$p^*$ for every$z_i$ based on the number of equations that holds - Flip all
$z_i$ with$p^* < p_{\text{thr}}$ for some threshold$p_{\text{thr}}$ - Stop if the linear system is solvable, otherwise go back to step 1
So "Algorithm B" is a clever (and much more efficient) way to handle minimize the number of
But to be rigorous, FCA actually have to analyze the probability of the sum of those bias bits
If you are interested in the probablity analysis, you can refer to the resources above. But the most important takeaway is that
But apparently, none of the masks given by the challenge have low hamming weight, so FCA can't be applied directly. Instead, we need to find a low-weight
There are a few methods to find Low-Weight Polynomial Multiples:
- Exhaustive search
- Birthday-Paradox: Split into
$\lfloor t/2 \rfloor$ and$t-\lfloor t/2 \rfloor$ and do a meet-in-the-middle search - Finding Low Weight Polynomial Multiples Using Lattices (Yes, LLL is here too)
- Reduce to find a minimum weight codeword in a linear code (It is quite obvious if you understand the previous method)
- A New Approach for finding Low-Weight Polynomial Multiples (Haven't read)
- Polytool (Only works for weight 4 polynomial multiple)
You can implement any of them to try to find the corresponding MASK1
and MASK4
doesn't have a good enough LWPM, but MASK2
and MASK3
both have a weight 3
In lwpm.sage I implemented three of them for reference.
While there is an existing implementation, I blocked it from working correctly by restricting the number of output bits to
It works with some probability if it is given more than
Once FCA is implementated correctly, you should be able to recover the state bits of lfsr2
and lfsr3
.
Then if you fix
There are also other useful values to fix
$(y,z)$ , not just$(1,1)$ .
Which is the output of a 256 bits LFSR from lfsr1
and lfsr4
, so we call it lfsr14
. To obtain its feedback polynomial you can either:
- Apply Berlekamp-Massey algorithm
- Compute
(companion_matrix(f1)^3).charpoly() * f4
(3
is because3
is not a power of two, which changes the feedback polynomial oflfsr1
)
Once you have the feedback polynomial f14
, you can solve a linear system to recover the internal state of lfsr14
. But to generate the exact keystream, it is still necessary to somehow untangle the state of lfsr14
to lfsr1
and lfsr4
.
This is actually fairly simple as it is just solving another linear system constucted by the companion matrix of lfsr1
and lfsr4
.
Once all states are recovered, you can generate the entire keystream and decrypt the flag.
See solve.py for the full solution.
The combining function
However, it having
The following solution comes from @4yn
But the analysis above is actually wrong, it actually only need output.txt
only gives
Instead, you can ignore the linear term, so
So it has