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.Padding import pad
from Crypto.Util.number import bytes_to_long
import os


assert("FLAG" in os.environ)
flag = os.environ["FLAG"]

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):
        key = bytes.fromhex(key)
        text = xor(bytes.fromhex(text), otp)
        cipher = DES.new(key, DES.MODE_ECB)
        ct = xor(cipher.encrypt(pad(text, 8)), otp)
        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))

if __name__ == "__main__":

My current approach:

from pwn import *
from Crypto.Cipher import DES
from Crypto.Util.Padding import pad, unpad
import warnings
r = remote('desoracle.challs.cyberchallenge.it', 9035)
# First we want to get the OTP
myText = b'This is my cool text'.hex()
r.recvuntil('(in hex)?')
myKey = "FEFEFEFEFEFEFEFE" #this is one of the four weak keys
r.recvuntil('(in hex)?')
output = r.recvline().strip().decode()
outputInBytes = bytes.fromhex(output)
otp = xor(bytes.fromhex(myText), outputInBytes)
myKey = "FEFEFEFEFEFEFEFE" #this is one of the four weak keys
r.recvuntil('(in hex)?')
output = r.recvline().strip().decode()
paddedFlagAfterXor = bytes.fromhex(output)
flag = xor(paddedFlagAfterXor, otp)
  • 1
    $\begingroup$ The hint could be related to weak keys in DES. Or not. $\endgroup$
    – fgrieu
    Aug 6 at 6:33
  • $\begingroup$ 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.. $\endgroup$
    – Shark44
    Aug 6 at 9:13
  • $\begingroup$ 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? $\endgroup$
    – Shark44
    Aug 6 at 10:07
  • $\begingroup$ 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". $\endgroup$
    – J_H
    Aug 6 at 16:11

1 Answer 1


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
1. Encrypt text
2. Encrypt flag
0. Exit
> 1
What do you want to encrypt (in hex)? 4bbe3dbd2af198110b1dba3b8be427a53119b02e2c9872687846c3058a225f77
With what key (in hex)? FEFEFEFEFEFEFEFE
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))


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))


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


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

import codecs

print(codecs.decode(enc, 'hex'))
  • 1
    $\begingroup$ 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 :) $\endgroup$
    – Shark44
    Aug 6 at 18:29
  • $\begingroup$ 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. $\endgroup$
    – J_H
    Aug 6 at 19:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.