Personal Encryptor with Nonbreakable Inforation-theoretic Security

Description

The Personal Encryptor with Nonbreakable Information-theoretic Security seems rock solid.

nc challs.rumble.host 17171

The PoC's code is even available.

Solution

The encryption algorithm generates random bytes using os.urandom() and adds them to the position of the original character in the 63-character alphabet. Every 63 characters “wraps around” back to the original character.

def keygen(length):
    key = ""
    rnd_bytes = os.urandom(length)
    for i in range(length):
        pos = rnd_bytes[i] % len(ALPHABET)
        key += ALPHABET[pos]
    return key
    
...

def encrypt(key, msg):
    assert len(key) == len(msg), "For Information-theoretic security the key needs to be as long as the msg."

    ciphertext = ""

    for i in range(len(msg)):
        msg_c = msg[i]
        key_c = key[i]

        if msg_c not in ALPHABET:
            ValueError(f"Can't encrypt char: {msg_c}")

        msg_pos_c = ALPHABET.index(msg_c)
        key_pos_c = ALPHABET.index(key_c)

        new_pos = (msg_pos_c + key_pos_c) % len(ALPHABET)
        ciphertext += ALPHABET[new_pos]

    return ciphertext

Notice that the bytes will range from 0 to 255, and given that each byte has an equal probability of being chosen, the original character, and the 255mod63=3255\mod{63}=3 characters that follow, will have a slightly higher probability of ending up in the ciphertext.

We can obtain a maximum of 1000 ciphertexts, but we can simply reconnect any number of times to get more ciphertexts. If we do this enough times, we will naturally observe that at each position of the flag, the highest frequency character that appears in the ciphertext would be one of the 4 characters (original, and the 3 characters that follow) that have a higher probability of appearing in the ciphertext.

At this point, we would know what the flag roughly looks like. Since we are provided with the SHA256 hash of the flag, we can simply brute force the offsets to obtain the original flag.

There is only a maximum of 4134^{13} combinations to try (the first 4 characters CSR{ are known, and the highest frequency character is at an offset of either +0, +1, +2 or +3 from the original message).

Last updated

Was this helpful?