Le progamme lapin.py
est le suivant:
from Crypto.Cipher import AES
from Crypto.Protocol.KDF import scrypt
from Crypto.Util.number import long_to_bytes
while True:
pin = int(input(">>> PIN code (4 digits): "))
if 0 < pin < 9999:
break
flag = open("flag.txt", "rb").read()
k = scrypt(long_to_bytes(pin), b"FCSC", 32, N = 2 ** 10, r = 8, p = 1)
aes = AES.new(k, AES.MODE_GCM)
c, tag = aes.encrypt_and_digest(flag)
enc = aes.nonce + c + tag
print(enc.hex())
Un code PIN à 4 chiffres est demandé, il est utilisé pour dériver une clé de chiffrement à l’aide de la fonction scrypt
, cette clé sera utilisée pour chiffrer le flag grâce à la fonction encrypt_and_digest
.
La fonction encrypt_and_digest
permet d’obtenir une valeur tag
qui permettra de vérifier que le déchiffrement s’est bien passé grâce à la fonction verify
.
Le fichier qui npous est fourni contient dans l’ordre les données suivantes :
nonce
qui fait 128 bits (32 caractères hexadécimal)cipher
tag
qui fait 128 bits (32 caractères hexadécimal)
Les données chiffrées sont donc les données restantes quand on enlève les 32 premiers caractères (le nonce
) et les 32 derniers caractères (le tag
).
Comme la clé est dérivée d’un code PIN à 4 chiffres et que nous pouvons utiliser la fonction verify
pour vérifier la clé de chiffrement nous allons utiliser la force brute pour déchiffrer et retrouver le flag.
Le programme suivant va nous permettre d’attaquer le fichier par force brute :
#!/usr/bin/python3
from Cryptodome.Cipher import AES
from Cryptodome.Protocol.KDF import scrypt
from Cryptodome.Util.number import long_to_bytes
output = open("output.txt").read().strip()
nonce = bytearray.fromhex(output[:32])
ciphertext = bytearray.fromhex(output[32:-32])
tag = bytearray.fromhex(output[-32:])
print(f"nonce : {nonce.hex()}\nciphertext : {ciphertext.hex()}\ntag : {tag.hex()}")
for pin in range(9999):
k = scrypt(long_to_bytes(pin), b"FCSC", 32, N = 2 ** 10, r = 8, p = 1)
aes = AES.new(k, AES.MODE_GCM, nonce=nonce)
flag = aes.decrypt(ciphertext)
try:
aes.verify(tag)
print(f"pin : {pin}\nflag : {flag}")
break
except ValueError:
pass
Le script suivant automatise les opérations :
#!/bin/bash
set -e
if [ ! -f output.txt ]; then
wget https://hackropole.fr/challenges/fcsc2021-crypto-lapin/public/output.txt -O output.txt
fi
time python3 029_Lapin.py
Le résultat est le suivant :
--2025-02-09 18:14:35-- https://hackropole.fr/challenges/fcsc2021-crypto-lapin/public/output.txt
Resolving hackropole.fr (hackropole.fr)... 51.91.236.193, 2001:41d0:301::28
Connecting to hackropole.fr (hackropole.fr)|51.91.236.193|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 207 [text/plain]
Saving to: ‘output.txt’
output.txt 100%[=======>] 207 --.-KB/s in 0s
2025-02-09 18:14:35 (141 MB/s) - ‘output.txt’ saved [207/207]
nonce : f049de59cbdc9189170787b20b24f742
ciphertext : 6ccb9515e8b0250f3fc0f0c14ed7bb1d4b42c09d02fe01e0973a7233d99af55ce696f599050142759adc26796d64e0d6035f2fc39d2edb8a0797a9e45ae4cd55074cf99158d3a6
tag : 4dc70a7e836e3b30382df30de49ba60a
pin : 6273
flag : b'FCSC{xxxxxxxx}\n'
real 0m29,439s
user 0m29,366s
sys 0m0,071s
On note que l’attaque en force brute n’a pris que 29 secondes sur un modeste ordinateur portable.