Solution de U03 pour La PIN

intro crypto symétrique

9 février 2025

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 :

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.