# CTF - DES Challenge

I am trying to solve a CTF challenge based on DES. I attached the code of the challenge to the question. So far I have noticed that the otp used for the encryption is the same for the entire session, and my idea would be the following:

1. Get the OTP by using choice 1
2. Use the obtained OTP to decrypt the ciphertext given by choice 2 (since I pass the key)

I tried to implement this process and haven't managed to get the flag. Given that the hint of the challenge is the following:

Not all keys are strong alike.

Do you think that I am on the right track or do you have any proposal on how I could approach the problem? Thank you in advice.

Code of the challenge:

#!/usr/bin/env python3

import signal
from Crypto.Cipher import DES
from Crypto.Util.number import bytes_to_long
import os

TIMEOUT = 300

assert("FLAG" in os.environ)
flag = os.environ["FLAG"]
assert(flag.startswith("CCIT{"))
assert(flag.endswith("}"))

otp = os.urandom(8)

def xor(a, b):
return bytes([a[i % len(a)] ^ b[i % len(b)] for i in range(max(len(a), len(b)))])

def encrypt_des(text, key):
try:
key = bytes.fromhex(key)
text = xor(bytes.fromhex(text), otp)
cipher = DES.new(key, DES.MODE_ECB)
return ct.hex()
except Exception as e:
return f"Something went wrong: {e}"

def handle():
while True:
print("1. Encrypt text")
print("2. Encrypt flag")
print("0. Exit")
choice = int(input("> "))
if choice == 1:
text = input("What do you want to encrypt (in hex)? ").strip()
key = input("With what key (in hex)? ").strip()
print(encrypt_des(text, key))
elif choice == 2:
key = input("What key do you want to use (in hex)? ").strip()
print(encrypt_des(flag.encode().hex(), key))
else:
break

if __name__ == "__main__":
signal.alarm(TIMEOUT)
handle()



My current approach:

from pwn import *
from Crypto.Cipher import DES
import warnings
warnings.filterwarnings("ignore")
r = remote('desoracle.challs.cyberchallenge.it', 9035)
r.recvuntil('>')
# First we want to get the OTP
r.sendline('1')
myText = b'This is my cool text'.hex()
r.recvuntil('(in hex)?')
r.sendline(myText)
myKey = "FEFEFEFEFEFEFEFE" #this is one of the four weak keys
print(myKey)
r.recvuntil('(in hex)?')
r.sendline(myKey)
output = r.recvline().strip().decode()
outputInBytes = bytes.fromhex(output)
otp = xor(bytes.fromhex(myText), outputInBytes)
print(otp)
r.recvuntil('>')
r.sendline('2')
myKey = "FEFEFEFEFEFEFEFE" #this is one of the four weak keys
r.recvuntil('(in hex)?')
r.sendline(myKey)
output = r.recvline().strip().decode()
print(flag.decode())
r.interactive()
$$$$

• The hint could be related to weak keys in DES. Or not.
– fgrieu
Aug 6 at 6:33
• I thought about that as well, I have read some articles about them and as I understood with 4 specific keys I could obtain the same ciphertext as the plaintext. I have tried passing one of the keys suggested as weak to the program and proceed to obtain the encrypted flag. I obtained some bytes, but I do not think they are equal to the flag, they seem to be just random bytes.. Aug 6 at 9:13
• I edited the question and added my current approach which makes use of the weak keys to obtain the plaintext by the ciphertext, but the problem is that when I xor it with the otp I get in phase 1 I get random bytes, and I was expecting to get the flag, is there any error in the idea or is the problem in the implementation itself according to you? Aug 6 at 10:07
• You repeatedly assert(foo). Please don't do that. Better to simply assert foo. Then a maintenance engineer won't be tricked into "refactoring" as assert(foo, "diagnostic msg"), which means something very very different from assert foo, "diagnostic msg".
– J_H
Aug 6 at 16:11

You seem to have gotten pretty far. Here are my musings.

spoiler: some keys can be weak

\$ nc desoracle.challs.cyberchallenge.it 9035
1. Encrypt text
2. Encrypt flag
0. Exit
> 2
What key do you want to use (in hex)? FEFEFEFEFEFEFEFE
4bbe3dbd2af198110b1dba3b8be427a53119b02e2c9872687846c3058a225f77
1. Encrypt text
2. Encrypt flag
0. Exit
> 1
What do you want to encrypt (in hex)? 4bbe3dbd2af198110b1dba3b8be427a53119b02e2c9872687846c3058a225f77
With what key (in hex)? FEFEFEFEFEFEFEFE
434349547b35306d335f6b3379355f63346e5f62335f7733346b7df1bd443636b5c08a964c2193be
1. Encrypt text
2. Encrypt flag
0. Exit
> 0

>>> enc = '434349547b35306d335f6b3379355f63346e5f62335f7733346b7d447622cbd9c695f42387476e51'
>>>
>>> vals = [int(enc[i:i+2], 16)  for i in range(0, len(enc), 2)]
>>>
>>> ''.join(map(chr, vals))


'CCIT{50m3_k3y5_c4n_b3_w34k}Dv"ËÙÆ\x95ô#\x87GnQ'

Repeating it with another weak key, 0101010101010101, leads to this:

>>> enc = '434349547b35306d335f6b3379355f63346e5f62335f7733346b7d447622cbd9c695f42387476e51'
>>> enc = '434349547b35306d335f6b3379355f63346e5f62335f7733346b7d3877318cc695d6d51f0502ef03'
>>> vals = [int(enc[i:i+2], 16)  for i in range(0, len(enc), 2)]
>>> ''.join(map(chr, vals))


'CCIT{50m3_k3y5_c4n_b3_w34k}8w1\x8cÆ\x95ÖÕ\x1f\x05\x02ï\x03'

Looks like a 27-byte Flag, as confirmed by the asserts.

EDIT

Whoops, no need for the list comprehension, as this suffices:

import codecs

print(codecs.decode(enc, 'hex'))
`
• Ok so basically your approach is a bit different. You first get the encrypted flag, and then try to encrypt the encrypted flag (which is still the same since we are using a weak key). It works because the xor done in the first phase is re-made in the second phase re-transforming the text to the original flag. Is this correct? In any case chapeau and thank you :) Aug 6 at 18:29
• Yes, that's right. We exploit that the 2nd encrypt acts as decrypt for a weak key. Suppose the use of weak keys is prohibited. I wonder if this approach could be extended to use a related pair of semi-weak keys. If so, feel free to post an Answer.
– J_H
Aug 6 at 19:14