RedEnginePress logo
RedEnginePress
AlgorithmsLanguagesPlaygroundAbout

Xtea

p
"""
XTEA (eXtended Tiny Encryption Algorithm) is a block cipher designed to
correct weaknesses in TEA. It was published by David Wheeler and Roger
Needham in 1997. XTEA operates on 64-bit blocks with a 128-bit key and
uses a Feistel network with a recommended 64 rounds.

It's still found in embedded systems and game networking protocols due
to its simplicity and small code footprint.

Reference: https://en.wikipedia.org/wiki/XTEA
"""

import struct

DELTA = 0x9E3779B9
MASK = 0xFFFFFFFF


def xtea_encrypt(block: bytes, key: bytes, num_rounds: int = 64) -> bytes:
    """
    Encrypt a single 64-bit block using XTEA.

    :param block: 8 bytes of plaintext
    :param key: 16 bytes (128-bit key)
    :param num_rounds: number of Feistel rounds (default 64)
    :return: 8 bytes of ciphertext

    >>> key = b'\\x00' * 16
    >>> plaintext = b'\\x00' * 8
    >>> ciphertext = xtea_encrypt(plaintext, key)
    >>> ciphertext.hex()
    'fc924d124ad0ed50'

    >>> xtea_encrypt(b'hello!!!', b'sixteenbyteskey!')
    b'u\\x8d\\x00\\x17c\\xb8\\xf0*'

    >>> xtea_encrypt(b'short', key)
    Traceback (most recent call last):
        ...
    ValueError: block must be 8 bytes

    >>> xtea_encrypt(plaintext, b'short')
    Traceback (most recent call last):
        ...
    ValueError: key must be 16 bytes
    """
    if len(block) != 8:
        raise ValueError("block must be 8 bytes")
    if len(key) != 16:
        raise ValueError("key must be 16 bytes")

    v0, v1 = struct.unpack("!II", block)
    k = struct.unpack("!4I", key)

    total = 0
    for _ in range(num_rounds):
        v0 = (v0 + ((((v1 << 4) ^ (v1 >> 5)) + v1) ^ (total + k[total & 3]))) & MASK
        total = (total + DELTA) & MASK
        v1 = (
            v1 + ((((v0 << 4) ^ (v0 >> 5)) + v0) ^ (total + k[(total >> 11) & 3]))
        ) & MASK

    return struct.pack("!II", v0, v1)


def xtea_decrypt(block: bytes, key: bytes, num_rounds: int = 64) -> bytes:
    """
    Decrypt a single 64-bit block using XTEA.

    :param block: 8 bytes of ciphertext
    :param key: 16 bytes (128-bit key)
    :param num_rounds: number of Feistel rounds (default 64)
    :return: 8 bytes of plaintext

    Roundtrip test -- encrypt then decrypt returns original plaintext:
    >>> key = b'\\x00' * 16
    >>> plaintext = b'\\x00' * 8
    >>> xtea_decrypt(xtea_encrypt(plaintext, key), key) == plaintext
    True

    >>> msg = b'hello!!!'
    >>> k = b'sixteenbyteskey!'
    >>> xtea_decrypt(xtea_encrypt(msg, k), k) == msg
    True

    >>> xtea_decrypt(b'short', key)
    Traceback (most recent call last):
        ...
    ValueError: block must be 8 bytes

    >>> xtea_decrypt(b'\\x00' * 8, b'short')
    Traceback (most recent call last):
        ...
    ValueError: key must be 16 bytes
    """
    if len(block) != 8:
        raise ValueError("block must be 8 bytes")
    if len(key) != 16:
        raise ValueError("key must be 16 bytes")

    v0, v1 = struct.unpack("!II", block)
    k = struct.unpack("!4I", key)

    total = (DELTA * num_rounds) & MASK
    for _ in range(num_rounds):
        v1 = (
            v1 - ((((v0 << 4) ^ (v0 >> 5)) + v0) ^ (total + k[(total >> 11) & 3]))
        ) & MASK
        total = (total - DELTA) & MASK
        v0 = (v0 - ((((v1 << 4) ^ (v1 >> 5)) + v1) ^ (total + k[total & 3]))) & MASK

    return struct.pack("!II", v0, v1)


if __name__ == "__main__":
    import doctest

    doctest.testmod()