Auvergn'hack 2026: Crypto challenge
Auvergn’hack 2026 CTF: Crypto - Saki
There was a crypto challenge at Auvergn’hack. I can’t remember exactly the title (I think it was “Saki” or something). I did not preserve the description either, but the only important files that were provided were:
- an encrypted message (
message.txt):
| |
- a verification file (
v.txt), with a single word:Monkey - a Python script (
script.py) to encrypt or decrypt messages
Goal: we need to decrypt message.txt
Code analysis
The decrypt function in script.py shows we are supposed to have a key file, key.txt. But we don’t have that.
| |
The gen_verification function explains the format of message.txt:
his a base64 encoding of the keyed HMAC-SHA256 of filev.txtwith the keynis a base64 encoded noncemis a base64 encoded ciphertext
| |
Consequently, to decrypt the message, we theoretically need to launch script.py and provide:
| Script prompt | Explanation | Value |
|---|---|---|
| message> | ciphertext (m) from message.txt | 2gFQlO5+YZFnqaar7QEGpu3/f/2WdbJEWPnVjNuNs2dXyHUi7/8= |
| key verification> | h field of message.txt | g1oatFTHSYsUH377iZQSuesUM/t+pFXRrwCrNW8v8Lw= |
| nonce> | n field of message.txt | WQq7B4XEueM= |
The “only” issue is that we don’t have the key…
A word about ChaCha20
ChaCha20 is a stream cipher. From a key and a nonce, a random key stream is generated, and XORed against the plaintext (or the ciphertext for decryption).
As it’s XOR, the keystream must never be re-used. That’s what the nonce is for: the nonce is unique, and ensure the key can be re-used, as long as the nonce is never re-used.
Typically, ChaCha20 is used along with an HMAC or Poly1305 mechanism, to guarantee the ciphertext hasn’t been modified by an attacker (ChaCha20 is an encryption algorithm, it only protects confidentiality). To do so, in addition to the ciphertext, people usually produce a tag which is the HMAC of the ciphertext with the key. If the tag is verified, this ensures that the ciphertext hasn’t been modified.
In the CTF challenge’s implementation, that’s not what happens. We are not doing a HMAC of the ciphertext but of a short “verification message”. The verification message (v.txt = “Monkey”) is known. So, if the HMAC(key, v.txt) is correct, we know the key is correct. This acts as a key validation procedure. Strange… but the puzzle solves in the next paragraph.
The trick with the key
Two observations stand out:
In the
decryptfunction, we notice the key is padded to 32 bytes:cipher_d = ChaCha20.new(key=pad(key,32),nonce=b64decode(nonce))… which means that the key is very probably shorter!We know
handv, i.e we know that:b64encode(HMAC.new(key, "Monkey", digestmod=SHA256)) = g1oatFTHSYsUH377iZQSuesUM/t+pFXRrwCrNW8v8Lw=
So, we are going to bruteforce the key with this property. We’ll download a known password list, and test their HMAC value until we find the right one.
Will this work? It’s not totally guaranteed (our password list may be too short + we’re not exactly certain how long the key will be), but there are strong hints this is the way to go:
- There’s isn’t any other way to solve the challenge, so the key is likely to be short for the challenge to be do-able.
- It explains the usual use of
h, which HMAC the key withv, only to help us verify the key is correct.
Key recovery
I wanted to bruteforce script to be fast, so I opted for an implementation in C. The implementation is not tricky, a perfect job for AI. It uses OpenSSL EVP.
| |
I downloaded a password list from SecList and ran the program:
| |
NB. The program returns very fast. Probably, an implementation in C wasn’t necessary and Python would have worked.
Decrypting the message
The next step is trivial: we have the key, the ciphertext, the nonce. We simply need to decrypt.
| |
No need for AI for this piece of code :P
| |
Flag: ZiTF{23135dbf5cdb57b80d18d506dc0b0a21}
Conclusion
An interesting relatively easy crypto challenge around ChaCha20. I was perhaps slightly disappointed the solution relied on bruteforcing (not very “noble”), but the fact was smartly hinted by the unusual use of the verification message.
This writeup demonstrates what I call a “reasonable” use of AI: we solve the challenge with “assistance”, but still entirely understand the concept. In particular, we use AI to generate an implementation (in C) which is not “difficult”, but a bit boring (moreover, actually, C was overkill and Python would have worked).
Full script to find the key
The script was generated by AI (I can’t remember which LLM I used). I would have far quicker to generate it in Python (even by hand), but I wanted to ensure this would run quick, so I preferred an implementation in C.
| |