On se connecte au service :
❯ nc localhost 4000
Welcome to our state-of-the-art encryption service!
We use PBKDF2 and AES-GCM!
As an example, here is the encrypted flag: 7b656d3993152e8f04f8273ca1509e27a3e39249cf4784e23b81d5f2524fee75f6b28a6a07a128e4880e770bc70b32bd7d5f37bb5eba76d38edb8d1964733b
Now, enter your text: plop
Here is your ciphertext: 4e4a510af24f52198512336b5cb563caa1e2b6e5
^C
Le point intéressant ici est l’utilisation du mode GCM. En effet, il s’agit intrinsèquement d’un chiffrement par flux : l’IV et la clef génèrent un flux pseudo-aléatoire qui sera XORé avec le contenu en clair. Cela a une implication extrêmement importante : l’IV doit être modifié à chaque opération de chiffrement. Dans le cas contraire, le même flux est utilisé pour toutes les opérations de chiffrement. C’est d’ailleurs le cas ici, puisque si on tente de chiffrer plusieurs fois la même chaîne, on a le même résultat.
Ainsi, à partir du serveur, on peut chiffrer une chaîne, puis la XORer avec le résultat pour retrouver le flux pseudo-aléatoire. Ce flux peut alors être de nouveau XORé avec n’importe quel chiffré pour obtenir le message en clair.
Ainsi, chiffrons une chaîne au moins aussi longue que le flag chiffré :
❯ nc localhost 4000
Welcome to our state-of-the-art encryption service!
We use PBKDF2 and AES-GCM!
As an example, here is the encrypted flag: 7b656d3993152e8f04f8273ca1509e27a3e39249cf4784e23b81d5f2524fee75f6b28a6a07a128e4880e770bc70b32bd7d5f37bb5eba76d38edb8d1964733b
Now, enter your text: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Here is your ciphertext: 5f475f1b8910788b55a1766ff9039b7ff7e4c21b9f43d5b468d485f20a16eb77a1b0da6a07f179b7de562e529f175954d915de810a50f35eb3c4e864486bbce1cb8c14b16b30fe5262136280e17dd30eeeb17a231e370c754e4c20a3eda3a50919778749b25647e5d41ac5fad2fb078f8bcb91a39599242bfd28729fb9d65211cb6cfcaa02bac29b7c9502ef5974d81c369ce5705a2a
On récupère le flux pseudo-aléatoire de chiffrement :
>>> encrypted = bytes.fromhex("5f475f1b8910788b55a1766ff9039b7ff7e4c21b9f43d5b468d485f20a16eb77a1b0da6a07f179b7de562e529f175954d915de810a50f35eb3c4e864486bbce1cb8c14b16b30fe5262136280e17dd30eeeb17a231e370c754e4c20a3eda3a50919778749b25647e5d41ac5fad2fb078f8bcb91a39599242bfd28729fb9d65211cb6cfcaa02bac29b7c9502ef5974d81c369ce5705a2a")
>>> flux = bytes([ ord('a') ^ x for x in encrypted ])
>>> flux
b'>&>z\xe8q\x19\xea4\xc0\x17\x0e\x98b\xfa\x1e\x96\x85\xa3z\xfe"\xb4\xd5\t\xb5\xe4\x93kw\x8a\x16\xc0\xd1\xbb\x0bf\x90\x18\xd6\xbf7O3\xfev85\xb8t\xbf\xe0k1\x92?\xd2\xa5\x89\x05)\n\xdd\x80\xaa\xedu\xd0\nQ\x9f3\x03r\x03\xe1\x80\x1c\xb2o\x8f\xd0\x1bB\x7fVm\x14/-A\xc2\x8c\xc2\xc4hx\x16\xe6(\xd37&\x84\xb5{\xa4\x9b\xb3\x9af\xee\xea\xaa\xf0\xc2\xf4\xf8EJ\x9cI\x13\xfe\xd8\xb73p\xaa\r\x9d\xcbc\xdb\xa3\xfa\x1d\xf4c\x8e8\x15\xb9}W\xfd\x84\x11;K'
Il est alors trivial de retrouver le flag :
>>> flag = bytes.fromhex('7b656d3993152e8f04f8273ca1509e27a3e39249cf4784e23b81d5f2524fee75f6b28a6a07a128e4880e770bc70b32bd7d5f37bb5eba76d38edb8d1964733b')
>>> bytes([ a^b for a, b in zip(flag, flux) ])
b'ECSC{d7e080292d95f131e07241a98dc6c1aa10279889}\n\x88\xc5+\x88[5\x8b\xe4\xec\\~\x04\x1cMy\xe6'