This set was surprisingly easy, actually. The book Understanding Cryptography by Paar & Pelzl is an excellent intro to the basic maths needed for crypto — namely, the group theory and number theory necessary for RSA and Diffie-Hellman.
Let’s dive in!
Challenge 33 Implement Diffie-Hellman
Diffie-Hellman is a remarkably simple algorithm for two parties to jointly compute a shared secret key that may be used, for example, as a key for symmetric encryption.
Alice and Bob agree on an integer group of prime , with a generator . raised to every power in , taken , can produce every element of . Hence, it is called a “generator” of the group.
So, Alice and Bob each choose a random group element and use that as a power of .
Alice computes , .
Bob computes , .
What’s important to Diffie-Hellman is that, given and , one cannot easily compute . This is called the Discrete Log Problem. The value looks like a totally random element of !
Then, Alice sends to Bob and Bob sends to Alice. Alice computes and Bob computes , which are equal, since .
Implementing this is easy in python with the built-in pow
function. However, for educational purposes, I wrote the modexp
function for fast modular exponentiation.
The modexp
function is taken from Paar and Pelzl. The exponent is converted to binary, and for each bit, we square the result. Also, if the bit is 1
, we multiply the result by the base.
The final algorithm is
1 | # Create secret keys, random values mod p |
Challenge 34 Implement a MITM key-fixing attack on Diffie-Hellman with parameter injection
Again, pwntools to the rescue!
This challenge requires some client/server sockets, which I use the pwntools tubes library for.
At a high level, the challenge demonstrates that, if an attacker were able to sit in the middle of a DH key exchange session and modify the messages being passed, he can control the key and decrpyt all the traffic.
The client sends (p, g, A)
to the server, and the server responds with B
. If you recall, p
and g
are the public agreed-upon parameters for the group prime and generator. A
is the Client’s piece of the secret key, and B
is the Server’s piece.
If an attacker replaces A
and B
with p
, then both Client and Server will compute the key to be . 0 is then hashed and used as the symmetric key for AES-encrypting messages, so the attacker can decrypt all the communications.
Challenge 35 Implement DH with negotiated groups, and break with malicious “g” parameters
Let’s see what happens when we choose different values for the generator . When , all powers of are 1 as well, so the secret key is always . When , as we saw in the previous challenge, powers are all divisible by , so the key is always . When is raised to a power, all the terms with will be , leaving either or .
Challenge 36 Implement Secure Remote Password (SRP)
Secure Remote Password is really cool. It is a form of authentication in which the client does not need to reveal her password — a form of zero-knowledge.
The setup involves some large primes, and the security relies on discrete log, as before. A large prime modulus N is chosen, along with a generator g and a magic parameter that is generally set to 3.
The server stores a verifier for a client that wants to authenticate, , where . A salt is a random value used to safeguard the password hash from being easily identifiable in a hash lookup rainbow table.
After exchanging some parameters, both client and server produce a session key . The server needs the verifier to get , while the client needs the password. The server checks that the client’s is equal to the server’s computed , and if so, successfully authenticates the client.
The full protocol can be seen on Wikipedia.
Challenge 37 Break SRP with a zero key
Some buggy implementations of SRP allow authentication without knowing the password, as this challenge illustrates.
The server produces the session key by
where and ( and ) are random one time ephemeral keys of the user and server, is the verifier, is a “scrambling parameter”.
If is or a multiple of , then , and we can authenticate simply by sending without knowing the password!
Challenge 38 Offline dictionary attack on simplified SRP
This problem isn’t really that interesting, so I skipped it. But here’s the lowdown: if you get MITM on SRP and thus can control the values for some parameters, , and , then you can precompute the session keys for common dictionary words. Doing the bruteforce cracking is not really that interesting, though.
Challenge 39 Implement RSA
The security of RSA is not based on the discrete log problem, but rather on the difficulty of factoring large numbers.
We choose two large primes and , and compute . Then, we compute the totient, or Euler’s phi function, . The public key is usually a small number like 3 or 65537 that is coprime to , and we derive the private key such that . This is exactly the definition of a modular inverse, so is the modular inverse of .
I implement the Extended Euclidean Algorithm to find the modular inverse of a number, using the algorithm in Pelzl.
The EEA will compute the coefficients of the equation
The gcd is 1 because and are coprime. , so we are left with , directly giving us .
Here’s my EEA:
1 | def egcd(r0, r1): |
and the driver function that returns the modular inverse:
1 | def modinv(mod, a): |
With the modinv
function, the rest of RSA is straightforward.
Challenge 40 E=3 RSA Broadcast Attack
20 Years of Attacks on RSA describes some great attacks on RSA that come in CTFs as well as Matasano challenges.
One of the simplest is Hastad’s broadcast attack, which allows us to recover a message that has been encrypted with RSA pubkeys that are very small, such as .
The Chinese Remainder Theorem is the key to this problem.
As the paper describes, we only need the same message encrypted with the same public key (but different moduli) to recover the plaintext.
The CRT tells us that there exists some value that satisfies , a solution to all the equations. We just need to calculate the and take its cube root, recovering .
The challenge just gives you the CRT equation to solve for .
1 | m_s_1 = N2*N3 |
Python’s cube root (**(1/3.)
) is not entirely precise, so I use the gmpy
multiple-precision library.
1 | cube_root = gmpy.mpz(result).root(3)[0].digits() |