> For the complete documentation index, see [llms.txt](https://simon-6.gitbook.io/simoncyber/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://simon-6.gitbook.io/simoncyber/ctf-writeups/picoctf-2024/cryptography/custom-encryption-medium.md).

# Custom encryption (Medium)

<figure><img src="/files/drSxZuepqK5MHmM1Zwt7" alt=""><figcaption></figcaption></figure>

wget <https://artifacts.picoctf.net/c\\_titan/92/enc\\_flag> <https://artifacts.picoctf.net/c\\_titan/92/custom\\_encryption.py>

***

If we run the encryption on the encrypted flag, we get this. I'm guessing since the flag is already encrypted, we basically need to reverse the encryption into decryption in the python file.

<figure><img src="/files/WWTR5S7VlqaG5oVbqvPQ" alt=""><figcaption></figcaption></figure>

In the custom\_encryption.py:

```python
from random import randint
import sys


def generator(g, x, p):
    return pow(g, x) % p


def encrypt(plaintext, key):
    cipher = []
    for char in plaintext:
        cipher.append(((ord(char) * key*311)))
    return cipher


def is_prime(p):
    v = 0
    for i in range(2, p + 1):
        if p % i == 0:
            v = v + 1
    if v > 1:
        return False
    else:
        return True


def dynamic_xor_encrypt(plaintext, text_key):
    cipher_text = ""
    key_length = len(text_key)
    for i, char in enumerate(plaintext[::-1]):
        key_char = text_key[i % key_length]
        encrypted_char = chr(ord(char) ^ ord(key_char))
        cipher_text += encrypted_char
    return cipher_text


def test(plain_text, text_key):
    p = 97
    g = 31
    if not is_prime(p) and not is_prime(g):
        print("Enter prime numbers")
        return
    a = randint(p-10, p)
    b = randint(g-10, g)
    print(f"a = {a}")
    print(f"b = {b}")
    u = generator(g, a, p)
    v = generator(g, b, p)
    key = generator(v, a, p)
    b_key = generator(u, b, p)
    shared_key = None
    if key == b_key:
        shared_key = key
    else:
        print("Invalid key")
        return
    semi_cipher = dynamic_xor_encrypt(plain_text, text_key)
    cipher = encrypt(semi_cipher, shared_key)
    print(f'cipher is: {cipher}')


if __name__ == "__main__":
    message = sys.argv[1]
    test(message, "trudeau")
```

```python
a = 89
b = 27
cipher is: [33588, 276168, 261240, 302292, 343344, 328416, 242580, 85836, 82104, 156744, 0, 309756, 78372, 18660, 253776, 0, 82104, 320952, 3732, 231384, 89568, 100764, 22392, 22392, 63444, 22392, 97032, 190332, 119424, 182868, 97032, 26124, 44784, 63444]
```

Let's analysis each function one by one again,

```python
def test(plain_text, text_key):
    p = 97
    g = 31
    ### declares 2 prime numbers
    if not is_prime(p) and not is_prime(g):
        print("Enter prime numbers")
        return
    a = randint(p-10, p)
    b = randint(g-10, g)
    ## generates a random number for a (87-97) and (21-31) 
    print(f"a = {a}")
    print(f"b = {b}")
    u = generator(g, a, p)
    v = generator(g, b, p)
    key = generator(v, a, p)
    b_key = generator(u, b, p)
    shared_key = None
    if key == b_key:
        shared_key = key
    else:
        print("Invalid key")
        return
    semi_cipher = dynamic_xor_encrypt(plain_text, text_key)
    cipher = encrypt(semi_cipher, shared_key)
    print(f'cipher is: {cipher}')
```

The u, v, and keys are then sent to the generator. What the generator function does is raises the first input to the power of the send input, and then modulus. This is called "**modular exponentiation**." What this does is uses the plain text and the text key to encrypt it using dynamic XOR. It then passes the ciphertext to encrypt again using the same key through the encrypt function.

For the encrypt function, it encrypts the character into its ASCII value, then it multiplies it by 311 and the key. This gives us some big number.

What the xor\_encrypt does&#x20;

```python
def dynamic_xor_encrypt(plaintext, text_key):
    cipher_text = ""
    key_length = len(text_key)
## Creates a cipher text and a the same key length 
    for i, char in enumerate(plaintext[::-1]):
## This for loop accesses the index and character of every well, index. This also reverses
## the string. enumerate() is needed as it provides a index and a value 
        key_char = text_key[i % key_length]
### Makes the key repeat, as once i reaches the key length 
        encrypted_char = chr(ord(char) ^ ord(key_char))
### This is the xoring. This is not to the power. That would be ** or pow(), this XORs it.
### It get's the ASCII values of both char, xors them, and converts them to a character
        cipher_text += encrypted_char
    return cipher_text
```

***

### The Script

```python
from random import randint
import sys


def generator(g, x, p):
    return pow(g, x) % p


def is_prime(p):
    v = 0
    for i in range(2, p + 1):
        if p % i == 0:
            v = v + 1
    if v > 1:
        return False
    else:
        return True

def dynamic_xor_decrypt(plaintext, text_key):
    cipher_text = ""
    key_length = len(text_key)

    for i, char in enumerate(plaintext[::-1]):
        key_char = text_key[i % key_length]
        encrypted_char = chr(ord(char) ^ ord(key_char))
        cipher_text += encrypted_char

    plaintext = cipher_text
    cipher_text = ""

    for i, char in enumerate(plaintext[::-1]):
        key_char = text_key[i % key_length]
        encrypted_char = chr(ord(char) ^ ord(key_char))
        cipher_text += encrypted_char

    plaintext = cipher_text
    cipher_text = ""

    for i, char in enumerate(plaintext[::-1]):
        key_char = text_key[i % key_length]
        encrypted_char = chr(ord(char) ^ ord(key_char))
        cipher_text += encrypted_char
    
    return cipher_text

## do this 3 times 

def decrypt(cipher, key):
    plaintext = ""
    for encrypted_value in cipher:
        decrypted_value = encrypted_value // (key * 311)
        plaintext += chr(decrypted_value)
    return plaintext

def test2():
    p = 97
    g = 31
    a = 89
    b = 27

    u = generator(g, a, p)
    v = generator(g, b, p)
    key = generator(v, a, p)
    b_key = generator(u, b, p)

    shared_key = None
    if key == b_key:
        shared_key = key
    else:
        print("Invalid key")
        return

    cipher = [33588, 276168, 261240, 302292, 343344, 328416, 242580, 85836, 82104, 156744, 0, 309756, 78372, 18660, 253776, 0, 82104, 320952, 3732, 231384, 89568, 100764, 22392, 22392, 63444, 22392, 97032, 190332, 119424, 182868, 97032, 26124, 44784, 63444]

    semi_cipher = decrypt(cipher, shared_key)

    flag = dynamic_xor_decrypt(semi_cipher, "trudeau")

    print(flag)


if __name__ == "__main__":
    # message = sys.argv[1]
    # test(message, "trudeau")
    test2()
```

This pretty much just divides it by 311 \* key. But more importantly it reverses the XOR by having the ciphertext go through it 3 times.&#x20;
