SpaceCTF: AES-CTR key reuse

Challenge

Difficulty: 3/5

Max points: 150

Description: Bessie is hiding something on the moonbase

Files

output.txt

6be9a7a25ad6a99cf9312f48666229e3b57130f3c91732ddfbf414efd92e96c36ea69c4a1f35158ed53fca5b1db2
4ce2b6e450cdf389d76312484c4072a68c752d8a963d3dffc7f506

aes.py

#!/usr/bin/env python3
from Crypto.Cipher import AES
from Crypto.Util import Counter
import os
    
    
key = os.urandom(16)


def encrypt(text):
    cipher = AES.new(key, AES.MODE_CTR, counter=Counter.new(128))
    ciphertext = cipher.encrypt(text)
    return ciphertext.hex()

msg = b"The quick brown fox jumps over the lazy dog..."
print(encrypt(msg))

with open('flag0.txt', 'rb') as flagFile:
    flag = flagFile.read().strip()
print(encrypt(flag))

Solution

AES CTR mode

AES CTR mode is vulnerable to key reuse. The decryption operation is just: ciphertext_1 xor key_stream = plaintext_1. This also means that ciphertext_1 xor plaintext_1 = key_stream and key_stream xor plaintext_1 = ciphertext_1. When we have the key stream, we can use it to decrypt the second ciphertext as well with ciphertext_2 xor key_stream = plaintext_2.

Learn more about symmetric encryption:

Read more about AES CTR:

Getting the flag

After encrypting the first message, this is the output:

6be9a7a25ad6a99cf9312f48666229e3b57130f3c91732ddfbf414efd92e96c36ea69c4a1f35158ed53fca5b1db2

After encrypting the second message (the flag), this is the output:

4ce2b6e450cdf389d76312484c4072a68c752d8a963d3dffc7f506

As we can see the first ciphertext is significantly longer than the second. This also means that the first message is longer than the second message. It also means that the key stream we can recover from the first message xor the first ciphertext is long enough to also decrypt the entire second message (key has to be equal length or longer). Since we know the plaintext for the first ciphertext, we can recover the reused key stream like so using a neat website called CyberChef (https://gchq.github.io/CyberChef/):

# ciphertext 1
0x6be9a7a25ad6a99cf9312f48666229e3b57130f3c91732ddfbf414efd92e96c36ea69c4a1f35158ed53fca5b1db2

xor

# message 1
"The quick brown fox jumps over the lazy dog..."

equals

# key stream
0x3f81c2822ba3c0ff92114d3a091547c3d31e48d3a3625fad88d47b99bc5cb6b706c3bc267e4f6caeb150ad75339c

And using the key stream once more, we can recover the plaintext for the second ciphertext:

# key stream
0x3f81c2822ba3c0ff92114d3a091547c3d31e48d3a3625fad88d47b99bc5cb6b706c3bc267e4f6caeb150ad75339c

xor

# ciphertext 2
0x4ce2b6e450cdf389d76312484c4072a68c752d8a963d3dffc7f506

equals

# plaintext 2
"sctf{n3vEr_rEU5e_keY5_bRO!}Õ^êRçË05ñ ]$âñ\" ùF±"

Flag: sctf{n3vEr_rEU5e_keY5_bRO!}