Custom encryption

Published: April 3, 2024

Description

Can you get sense of this code file and write the function that will decode the given encrypted file content. Find the encrypted file here flag_info and code file might be good to analyze and get the flag.

Local script

Download enc_flag and custom_encryption.py locally.

Inspect the script to understand the generator parameters, XOR key ("trudeau"), and how the cipher list was produced.

wget https://artifacts.picoctf.net/c_titan/18/enc_flag && \
wget https://artifacts.picoctf.net/c_titan/18/custom_encryption.py

Solution

This custom cryptography challenge involves reversing Diffie-Hellman and XOR operations. For another custom cipher challenge, check out C3, which uses a cyclical differential cipher.
  1. Step 1Rebuild the shared key
    custom_encryption.py prints the values of a and b during test(). Plug them into generator(g, x, p) to recover the same shared key that encrypt() used.
    Learn more

    Diffie-Hellman key exchange is a foundational cryptographic protocol that allows two parties to establish a shared secret over an insecure channel. The generator(g, x, p) function computes g^x mod p - modular exponentiation. When two parties compute g^a mod p and g^b mod p and exchange results, each can raise the received value to their own private exponent to arrive at the same shared secret g^(ab) mod p.

    In this challenge, the parameters a, b, g, and p are all printed or provided, so the "key exchange" is trivially reversible. In real Diffie-Hellman security, the values of a and b are kept secret - only g^a mod p and g^b mod p are shared publicly. The security rests on the discrete logarithm problem: given g, p, and g^x mod p, finding x is computationally infeasible for large enough parameters.

    Modern Diffie-Hellman is used in TLS handshakes, Signal protocol, and WireGuard VPN. The elliptic curve variant (ECDH) provides equivalent security with much smaller key sizes and is preferred in modern systems.

  2. Step 2Invert encrypt()
    Write a decrypt() that divides each cipher entry by key * 311. This yields the "semi_cipher" string prior to the dynamic XOR stage.
    Learn more

    The encryption multiplies each character's ordinal by key * 311. Decryption simply divides - this works because multiplication by a non-zero constant is invertible over the integers (unlike modular arithmetic, where you need the modular inverse). The result is the "semi-cipher": an intermediate state that still needs XOR reversal to yield the plaintext.

    The value 311 is a hard-coded constant in the encryption function - a common pattern in custom ciphers where the designer adds extra "complexity" through multiplication. In practice, such constants provide no additional security if the multiplier is known (which it is, since the script is provided). This is called security through obscurity and is explicitly not recommended as a cryptographic primitive.

    Recognizing the layered structure of this cipher - DH key exchange, then multiplication, then XOR - is important. Decryption must undo each layer in reverse order: XOR first (innermost), then division, then the DH key is already known. This compositional approach to cipher design and analysis is fundamental to understanding block cipher modes and authenticated encryption schemes.

  3. Step 3Reverse dynamic_xor_encrypt
    Create dynamic_xor_decrypt that runs the text_key XOR three times in reverse order (mirroring the encrypt function). Applying it to semi_cipher with key "trudeau" reveals the flag.
    python3 solver.py  # uses decrypt + dynamic_xor_decrypt
    Learn more

    XOR encryption with a repeating key is a simple stream cipher. Each character of the plaintext is XORed with the corresponding character of the key (cycling if the key is shorter than the message). The critical property of XOR is that it is its own inverse: if cipher = plain XOR key, then plain = cipher XOR key.

    The "dynamic" aspect of this XOR function refers to applying the key multiple times or in a transformed manner. If the function applies XOR three times with the key in some sequence, reversal means applying XOR in the reverse sequence - since each XOR application is its own inverse, reversing the order is sufficient to undo the effect.

    The key "trudeau" is a weak key: it's a short dictionary word. In real XOR stream ciphers (like RC4, ChaCha20), the key is much longer and pseudorandomly expanded. A short repeating XOR key is trivially broken by frequency analysis if the key length can be guessed - a technique codified in the Kasiski examination and index of coincidence methods used to break Vigenere ciphers.

    This challenge is excellent practice for reading unfamiliar Python code, identifying the encryption's structure, and writing the inverse systematically. In real CTF and malware analysis, reverse-engineering custom encryption is a common task - attackers often implement weak custom ciphers hoping to deter analysis.

Alternate Solution

Once you have recovered the semi_cipher and know the repeating XOR key is "trudeau", use the XOR Cipher tool on this site to apply the final XOR step - paste the semi_cipher bytes and enter the key to reveal the flag without writing any additional Python.

Flag

picoCTF{custom_d2cr0pt6d_751a...}

The decrypted semi_cipher plus the reversed XOR routine yields the flag above.

Want more picoCTF 2024 writeups?

Tools used in this challenge

Related reading

Do these first

What to try next