Skip to content

Commit f1a8457

Browse files
0xBassiaPentesterTNpre-commit-ci[bot]
authored
Add XTEA block cipher implementation (#14414)
* 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>
1 parent 39c3947 commit f1a8457

1 file changed

Lines changed: 118 additions & 0 deletions

File tree

ciphers/xtea.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
"""
2+
XTEA (eXtended Tiny Encryption Algorithm) is a block cipher designed to
3+
correct weaknesses in TEA. It was published by David Wheeler and Roger
4+
Needham in 1997. XTEA operates on 64-bit blocks with a 128-bit key and
5+
uses a Feistel network with a recommended 64 rounds.
6+
7+
It's still found in embedded systems and game networking protocols due
8+
to its simplicity and small code footprint.
9+
10+
Reference: https://en.wikipedia.org/wiki/XTEA
11+
"""
12+
13+
import struct
14+
15+
DELTA = 0x9E3779B9
16+
MASK = 0xFFFFFFFF
17+
18+
19+
def xtea_encrypt(block: bytes, key: bytes, num_rounds: int = 64) -> bytes:
20+
"""
21+
Encrypt a single 64-bit block using XTEA.
22+
23+
:param block: 8 bytes of plaintext
24+
:param key: 16 bytes (128-bit key)
25+
:param num_rounds: number of Feistel rounds (default 64)
26+
:return: 8 bytes of ciphertext
27+
28+
>>> key = b'\\x00' * 16
29+
>>> plaintext = b'\\x00' * 8
30+
>>> ciphertext = xtea_encrypt(plaintext, key)
31+
>>> ciphertext.hex()
32+
'fc924d124ad0ed50'
33+
34+
>>> xtea_encrypt(b'hello!!!', b'sixteenbyteskey!')
35+
b'u\\x8d\\x00\\x17c\\xb8\\xf0*'
36+
37+
>>> xtea_encrypt(b'short', key)
38+
Traceback (most recent call last):
39+
...
40+
ValueError: block must be 8 bytes
41+
42+
>>> xtea_encrypt(plaintext, b'short')
43+
Traceback (most recent call last):
44+
...
45+
ValueError: key must be 16 bytes
46+
"""
47+
if len(block) != 8:
48+
raise ValueError("block must be 8 bytes")
49+
if len(key) != 16:
50+
raise ValueError("key must be 16 bytes")
51+
52+
v0, v1 = struct.unpack("!II", block)
53+
k = struct.unpack("!4I", key)
54+
55+
total = 0
56+
for _ in range(num_rounds):
57+
v0 = (v0 + ((((v1 << 4) ^ (v1 >> 5)) + v1) ^ (total + k[total & 3]))) & MASK
58+
total = (total + DELTA) & MASK
59+
v1 = (
60+
v1 + ((((v0 << 4) ^ (v0 >> 5)) + v0) ^ (total + k[(total >> 11) & 3]))
61+
) & MASK
62+
63+
return struct.pack("!II", v0, v1)
64+
65+
66+
def xtea_decrypt(block: bytes, key: bytes, num_rounds: int = 64) -> bytes:
67+
"""
68+
Decrypt a single 64-bit block using XTEA.
69+
70+
:param block: 8 bytes of ciphertext
71+
:param key: 16 bytes (128-bit key)
72+
:param num_rounds: number of Feistel rounds (default 64)
73+
:return: 8 bytes of plaintext
74+
75+
Roundtrip test -- encrypt then decrypt returns original plaintext:
76+
>>> key = b'\\x00' * 16
77+
>>> plaintext = b'\\x00' * 8
78+
>>> xtea_decrypt(xtea_encrypt(plaintext, key), key) == plaintext
79+
True
80+
81+
>>> msg = b'hello!!!'
82+
>>> k = b'sixteenbyteskey!'
83+
>>> xtea_decrypt(xtea_encrypt(msg, k), k) == msg
84+
True
85+
86+
>>> xtea_decrypt(b'short', key)
87+
Traceback (most recent call last):
88+
...
89+
ValueError: block must be 8 bytes
90+
91+
>>> xtea_decrypt(b'\\x00' * 8, b'short')
92+
Traceback (most recent call last):
93+
...
94+
ValueError: key must be 16 bytes
95+
"""
96+
if len(block) != 8:
97+
raise ValueError("block must be 8 bytes")
98+
if len(key) != 16:
99+
raise ValueError("key must be 16 bytes")
100+
101+
v0, v1 = struct.unpack("!II", block)
102+
k = struct.unpack("!4I", key)
103+
104+
total = (DELTA * num_rounds) & MASK
105+
for _ in range(num_rounds):
106+
v1 = (
107+
v1 - ((((v0 << 4) ^ (v0 >> 5)) + v0) ^ (total + k[(total >> 11) & 3]))
108+
) & MASK
109+
total = (total - DELTA) & MASK
110+
v0 = (v0 - ((((v1 << 4) ^ (v1 >> 5)) + v1) ^ (total + k[total & 3]))) & MASK
111+
112+
return struct.pack("!II", v0, v1)
113+
114+
115+
if __name__ == "__main__":
116+
import doctest
117+
118+
doctest.testmod()

0 commit comments

Comments
 (0)