Aller au contenu
Tous les challenges
Crypto Résolu Moyen · 3 000 m

Too Much Crypto

Une version « allégée » d'ASCON qui laisse fuir sa clé secrète.

Auteur
Alexandre Duc
Scripts
1 fichier

Énoncé

Cryptography is too costly… We propose a lighter implementation of ASCON. Recover the secret key. To obtain the flag, call getFlag on the key.

Fichiers fournis :

  • ascon_light.py : une implémentation d’Ascon-AEAD128 dont la permutation a été remplacée ;
  • parameters.txt : un texte clair connu, avec le chiffré, le nonce et le tag correspondants (en base64) ; les données associées (AD) sont vides.

getFlag(key) renvoie BA26{ + blake3(str(key))[:16] + }.

Analyse

Le mode AEAD suit à peu près la norme NIST SP 800-232. Seule la permutation a changé :

def ascon_permutation(S):
    H = words(blake3(bytes(S[:3])).digest()[:24], 3)   # seule partie non linéaire
    for i in range(3): H[i] ^= S[i+2]
    S = S[-1:] + S[:-1]                                  # rotation
    for i in range(3): S[i] ^= H[i]
    return S

On note h = blake3(S0‖S1‖S2). La sortie vaut alors :

mot valeur
S’0 S4 ⊕ h0 ⊕ S2
S’1 S0 ⊕ h1 ⊕ S3
S’2 S1 ⊕ h2 ⊕ S4
S’3 S2
S’4 S3

L’initialisation a aussi été modifiée (« Changed for simplicity ») : S = [IV, N0, N1, K0, K1], suivie d’une seule application de la permutation, sans XOR final de la clé.

Vulnérabilité

Dans cette permutation, blake3 ne voit que S0..S2. Or à l’initialisation ces trois mots valent IV, N0, N1, tous publics. On peut donc calculer h soi-même, sans connaître la clé.

Après l’initialisation (AD vide, puis XOR de séparation de domaine sur S4), on obtient :

S0 = K1 ⊕ h0 ⊕ N1
S1 = IV ⊕ h1 ⊕ K0

Le premier bloc chiffré vaut C0 = P0 ⊕ (S0‖S1). Comme le texte clair est connu, on lit S0 et S1 directement, ce qui donne :

K1 = (C0 ⊕ P0)[0:8]  ⊕ h0 ⊕ N1
K0 = (C0 ⊕ P0)[8:16] ⊕ h1 ⊕ IV

Un seul bloc de texte clair connu suffit pour récupérer toute la clé. Il n’y a besoin d’aucune cryptanalyse.

Exploitation

  1. Décoder le nonce, puis découper N0, N1 en mots de 64 bits little-endian.
  2. Calculer h = blake3(IV‖N0‖N1)[:24] et le découper en trois mots h0, h1, h2.
  3. Calculer C0 ⊕ P0 sur les 16 premiers octets, ce qui donne S0, S1.
  4. En déduire K1 et K0 avec les formules ci-dessus, puis former key = K0‖K1.
  5. Vérifier en rechiffrant le texte clair : on doit retrouver exactement le chiffré et le tag fournis.
  6. Calculer getFlag(key).

Script

solve.py :

cd . && .venv/bin/python -I solve.py
# key = 962d43e30e8b7b5df37e74f47e000cee
# BA26{1ecc173213eb25a7}

Le venv .venv contient les dépendances blake3 et pycryptodome.

Code

"""Too Much Crypto (BlackAlps 2026) - key recovery from the first ciphertext block."""
import sys
from base64 import b64decode
from blake3 import blake3
sys.path.insert(0, "files")
import ascon_light as A  # only reused for encrypt (verification) + getFlag

pt = b'Ascon-AEAD128, with less crypto than in NIST SP 800-232.'
ct = b64decode(b'HcRLfNb6GrW9VIyxcMtcXt6coXZOKKu33xZrEOtzb7FBYJUnOfFy6hYE2Gl80ILp8OqWd6BUIoo=')
nonce = b64decode(b'rNo1NnaqG3EQhTlnVcPaTA==')
tag = b64decode(b'a0PWORRlAlq872NDeLLMtg==')

w = lambda b, n: [int.from_bytes(b[8*i:8*i+8], "little") for i in range(n)]
IV = A.IV_AEAD128
N0, N1 = w(nonce, 2)
# blake3 only sees S0..S2 = IV, N0, N1 -> fully public
h = w(blake3(A._words_to_bytes([IV, N0, N1])).digest()[:24], 3)
# After init: S0 = K1^h0^N1, S1 = IV^h1^K0 ; C_0 = P_0 ^ (S0||S1)
S0, S1 = w(bytes(a ^ b for a, b in zip(ct[:16], pt[:16])), 2)
K1 = S0 ^ h[0] ^ N1
K0 = S1 ^ h[1] ^ IV
key = A._words_to_bytes([K0, K1])
c2, t2 = A.ascon_aead128_encrypt(key, nonce, b"", pt)
assert (c2, t2) == (ct, tag), "verification failed"
print("key =", key.hex())
print(A.getFlag(key))