Skip to content

Latest commit

 

History

371 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

XXTEA implemented as a Python extension module, licensed under 2-clause BSD.

The XXTEA algorithm takes a 128-bit key and operates on an array of 32-bit integers (at least 2 integers), but it doesn't define the conversions between bytes and array. Due to this reason, many XXTEA implementations out there are not compatible with each other.

In this implementation, the conversions between bytes and array are taken care of by longs2bytes and bytes2longs. A non-standard 4-byte block PKCS#7 padding is used by default to make sure that the input bytes are padded to a multiple of 4-byte (the size of a 32-bit integer) and at least 8-byte long (the size of two 32-bit integers, which is required by the XXTEA algorithm). As a result of these measures, you can encrypt not only texts, but also any binary bytes of any length.

Note

The default ("pkcs7_4_min8") is not standard 4-byte PKCS#7. For inputs shorter than 4 bytes it pads an extra 4 bytes (pad values 5–8) to satisfy XXTEA's 2-word minimum. Pass padding="pkcs7_8" for standard 8-byte PKCS#7 (compatible with Python xxteang), or padding=False for raw XXTEA (requires data length ≥ 8 and multiple of 4).

Installation

$ pip install xxtea -U

Usage

This module provides four functions: encrypt(), decrypt(), encrypt_hex(), and decrypt_hex(), plus an XXTEA type for reusable cipher objects.

>>> import os
>>> import xxtea
>>> import binascii
>>>
>>> key = os.urandom(16)  # Key must be a 16-byte string.
>>> s = b"xxtea is good"
>>>
>>> enc = xxtea.encrypt(s, key)
>>> dec = xxtea.decrypt(enc, key)
>>> s == dec
True
>>>
>>> hexenc = xxtea.encrypt_hex(s, key)
>>> s == xxtea.decrypt_hex(hexenc, key)
True
>>>
>>> binascii.hexlify(enc) == hexenc
True

XXTEA Type

The XXTEA type holds a 16-byte key, rounds, and padding setting, so you can encrypt and decrypt multiple times without passing them each call.

>>> from xxtea import XXTEA, Padding
>>>
>>> cipher = XXTEA(key, padding=False, rounds=128)
>>> cipher
<xxtea.XXTEA object at 0x...>
>>>
>>> enc = cipher.encrypt(b'12345678')
>>> cipher.decrypt(enc)
b'12345678'
>>>
>>> hexenc = cipher.encrypt_hex(b'12345678')
>>> cipher.decrypt_hex(hexenc)
b'12345678'

rounds defaults to 0 (auto), padding defaults to True. rounds=0 means 6 + 52 / n, where n is the number of 32-bit words in the data. They are stored on the object and used by every encrypt(), decrypt(), encrypt_hex(), and decrypt_hex() call:

>>> c = XXTEA(key)                                 # rounds=0, padding=Padding.PKCS7_4_MIN8
>>> c = XXTEA(key, rounds=64)                      # override rounds
>>> c = XXTEA(key, padding=False)                  # disable padding
>>> c = XXTEA(key, padding=Padding.PKCS7_8)        # 8-byte PKCS#7
>>> c = XXTEA(key, padding=False, rounds=42)

encrypt_hex() and decrypt_hex() operate on ciphertext in a hexadecimal representation. They are exactly equivalent to:

>>> hexenc = binascii.hexlify(xxtea.encrypt(s, key))
>>> s == xxtea.decrypt(binascii.unhexlify(hexenc), key)
True

Padding

padding is an xxtea.Padding enum (a str enum), so more schemes can be added later. Strings, True, and False still work:

  • True / Padding.PKCS7_4_MIN8 / "pkcs7_4_min8" (default): 4-byte PKCS#7-like, padded to at least 8 bytes. Compatible with previous versions of this package. Not standard 4-byte PKCS#7.
  • Padding.PKCS7_8 / "pkcs7_8": Standard 8-byte PKCS#7, compatible with Python xxteang.
  • Padding.LENGTH_WORD_PREFIX / "length_word_prefix": prepend one little-endian uint32 with the original length, then zero-pad the data to a 4-byte boundary. Empty input is padded to 8 bytes so XXTEA's 2-word minimum is met (plaintext length must fit in a 32-bit unsigned integer).
  • Padding.LENGTH_WORD_SUFFIX / "length_word_suffix": zero-pad to a 4-byte boundary, then append one little-endian uint32 with the original length. Cocos Creator JSC files using this layout can be decrypted. Empty input is padded to 8 bytes so XXTEA's 2-word minimum is met (plaintext length must fit in a 32-bit unsigned integer).
  • False / None / Padding.NONE / "none": No padding (raw XXTEA).

xxtea.PKCS7_4_MIN8, xxtea.PKCS7_8, xxtea.LENGTH_WORD_PREFIX, and xxtea.LENGTH_WORD_SUFFIX are aliases for the enum members. They are also available as XXTEA.PKCS7_4_MIN8, XXTEA.PKCS7_8, XXTEA.LENGTH_WORD_PREFIX, and XXTEA.LENGTH_WORD_SUFFIX.

Unknown scheme names raise ValueError. Other values still follow Python truthiness for compatibility (0 / "" / empty containers disable padding; 1 and other truthy non-strings use the default scheme), but emit a DeprecationWarning and will be removed in the next major version. Use True, False, None, or Padding.

The default "pkcs7_4_min8" scheme uses pad byte value 4 - (len(data) & 3) (range 1–4), plus an extra 4 bytes when the input is shorter than 4 bytes to meet XXTEA's 2-word minimum (producing pad values 5–8). Standard 4-byte PKCS#7 never uses pad values 5–8. Because padding always adds at least one byte, encrypting an 8-byte input produces a 12-byte ciphertext.

8-byte PKCS#7 uses pad byte value 8 - (len(data) & 7) (range 1–8). Encrypting an 8-byte input produces a 16-byte ciphertext.

>>> xxtea.decrypt_hex(xxtea.encrypt_hex(b'', key), key)
b''
>>> xxtea.decrypt_hex(xxtea.encrypt_hex(b' ', key), key)
b' '
>>> len(xxtea.encrypt(b'12345678', key))                          # PKCS7_4_MIN8
12
>>> len(xxtea.encrypt(b'12345678', key, padding=xxtea.PKCS7_8))   # PKCS7_8
16

You can disable padding by setting padding parameter to False. In this case data will not be padded, so data length must be a multiple of 4 bytes and must not be less than 8 bytes. Otherwise ValueError will be raised:

>>> xxtea.encrypt_hex(b'', key, padding=False)
ValueError: Data length must be a multiple of 4 bytes and must not be less than 8 bytes
>>> xxtea.encrypt_hex(b'xxtea is good', key, padding=False)
ValueError: Data length must be a multiple of 4 bytes and must not be less than 8 bytes
>>> xxtea.decrypt_hex(xxtea.encrypt_hex(b'12345678', key, padding=False), key, padding=False)
b'12345678'

Rounds

By default xxtea manipulates the input data for 6 + 52 / n rounds, where n denotes how many 32-bit integers the input data can fit in. We can change this by setting rounds parameter.

Do note that the more rounds it is, the more time will be consumed. rounds must fit in a 32-bit unsigned integer; values exceeding 2**32 - 1 raise OverflowError.

>>> import xxtea
>>> import string
>>> data = string.digits.encode()
>>> key = string.ascii_letters[:16].encode()
>>> xxtea.encrypt_hex(data, key)
b'5b80b08a5d1923e4cd992dd5'
>>> 6 + 52 // ((len(data) + (4 - 1)) // 4)  # 4 means 4 bytes, size of a 32-bit integer
23
>>> xxtea.encrypt_hex(data, key, rounds=23)
b'5b80b08a5d1923e4cd992dd5'
>>> xxtea.encrypt_hex(data, key, rounds=1024)
b'1577bbf28c43ced93bd50720'

Catching Exceptions

When calling these functions, a ValueError, TypeError, or OverflowError may be raised. Note that invalid hex input raises binascii.Error, which is a subclass of ValueError:

>>> import xxtea
>>> from xxtea import XXTEA
>>>
>>> def try_catch(func, *args, **kwargs):
...     try:
...         func(*args, **kwargs)
...     except Exception as e:
...         print(e.__class__.__name__, ':', e)
...
...
...
>>> try_catch(xxtea.decrypt, b'', key=b'')
ValueError : Need a 16-byte key.
>>> try_catch(xxtea.decrypt, b'', key=b' '*16)
ValueError : Data length must be a multiple of 4 bytes and must not be less than 8 bytes
>>> try_catch(xxtea.decrypt, b' '*8, key=b' '*16)
ValueError : Invalid data, illegal padding. Could be using a wrong key.
>>> try_catch(xxtea.decrypt_hex, b' '*8, key=b' '*16)
Error : Non-hexadecimal digit found
>>> try_catch(xxtea.decrypt_hex, b'abc', key=b' '*16)
Error : Odd-length string
>>> try_catch(xxtea.decrypt_hex, b'abcd', key=b' '*16)
ValueError : Data length must be a multiple of 4 bytes and must not be less than 8 bytes
>>> try_catch(xxtea.encrypt, b'x', b'k'*16, rounds=2**32)
OverflowError : rounds value too large
>>> try_catch(XXTEA, key=b'short')
ValueError : Need a 16-byte key.
>>> try_catch(XXTEA, key=b'k'*16, rounds=2**32)
OverflowError : rounds value too large

About

Python extension module xxtea

Topics

Resources

Stars

30 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages