mirror of
https://github.com/TheAlgorithms/Python.git
synced 2026-09-28 21:45:27 +08:00
f1a8457eab
* Add XTEA block cipher implementation Implemented XTEA (eXtended Tiny Encryption Algorithm) with encrypt and decrypt functions. Includes input validation and full doctest coverage. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: PentesterTN <pentestertn@proton.me> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
119 lines
3.3 KiB
Python
119 lines
3.3 KiB
Python
"""
|
|
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()
|