Skip to content
🎉 Welcome! Enjoy your reading, and I hope you will learn something new.

Counter (CTR)

Counter Mode uses the AES output as a XOR key to encrypt the plaintext. The output is generated by encrypting with AES ECB a nonce concatenated with a counter that is incremented for each block.

CTR Mode - Encryption CTR Mode - Decryption

Attacks

Bit-flipping attack

ctr_bit_flipping.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# https://github.com/jvdsn/crypto-attacks/blob/master/attacks/ctr/bit_flipping.py
def attack(c, pos, p, p_):
    """
    Replaces the original plaintext with a new plaintext at a position in the ciphertext.
    :param c: the ciphertext
    :param pos: the position to modify at
    :param p: the original plaintext
    :param p_: the new plaintext
    :return: the modified ciphertext
    """
    c_ = bytearray(c)
    for i in range(len(p)):
        c_[pos + i] = c[pos + i] ^ p[i] ^ p_[i]

    return c_

Nonce reuse

ctr_nonce_reuse

CRIME style attack

ctr_crime_attack.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# https://github.com/jvdsn/crypto-attacks/blob/master/attacks/ctr/crime.py
def attack(encrypt_oracle, known_prefix, padding_byte):
    """
    Recovers a secret using the CRIME attack (CTR version).
    :param encrypt_oracle: the encryption oracle
    :param known_prefix: a known prefix of the secret to recover
    :param padding_byte: a byte which is never used in the plaintext
    :return: the secret
    """
    known_prefix = bytearray(known_prefix)
    padding_bytes = bytes([padding_byte])
    while True:
        for i in range(256):
            # Don't try the padding byte.
            if i == padding_byte:
                continue

            l1 = len(encrypt_oracle(padding_bytes + known_prefix + bytes([i]) + padding_bytes + padding_bytes))
            l2 = len(encrypt_oracle(padding_bytes + known_prefix + padding_bytes + bytes([i]) + padding_bytes))
            if l1 < l2:
                known_prefix.append(i)
                break
        else:
            return known_prefix

Separator Oracle

ctr_separator_oracle.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# https://github.com/jvdsn/crypto-attacks/blob/master/attacks/ctr/separator_oracle.py
def _find_separator_positions(separator_oracle, c):
    separator_positions = []
    c = bytearray(c)
    for i in range(len(c)):
        c[i] ^= 1
        valid = separator_oracle(c)
        c[i] ^= 1
        if not valid:
            c[i] ^= 2
            valid = separator_oracle(c)
            c[i] ^= 2
            if not valid:
                separator_positions.append(i)

    return separator_positions


def attack(separator_oracle, separator_byte, c):
    """
    Recovers the plaintext using the separator oracle attack.
    :param separator_oracle: the separator oracle, returns True if the separators are correct, False otherwise
    :param separator_byte: the separator which is used in the separator oracle
    :param c: the ciphertext
    :return: the plaintext
    """
    separator_positions = _find_separator_positions(separator_oracle, c)
    c = bytearray(c)
    # Ensure that at least 1 separator is missing.
    c[separator_positions[0]] ^= 1
    p = bytearray(len(c))
    for i in range(len(c)):
        if i in separator_positions:
            p[i] = separator_byte
        else:
            c_i = c[i]
            # Try every byte until an additional separator is created.
            for b in range(256):
                c[i] = b
                if separator_oracle(c):
                    p[i] = c_i ^ c[i] ^ separator_byte
                    break

            c[i] = c_i

    return p
Last updated on