XXTEA is an encryption
algorithm, first introduced in 1998, which is relatively easy to implement. Unlike many block ciphers, XXTEA has a variable block size. (For example, the original
Tiny Encryption Algorithm works on 64-bit blocks, as does
SAFER.) This page gives Lisp code for XXTEA as an example of
bit bashing in CL, particularly with type and
optimize declarations.
The algorithm
This implementation (by David Mullen) is released under an Apache 2 license. It's translated from the C implementation, with the Lisp version being more verbose, but not much harder to follow. There are few things to note. For one, C's overflow/underflow doesn't come into play here, so we have to explicitly constrain each result to a 32-bit unsigned integer (the element of a block).
Secondly: %xxtea-encrypt and %xxtea-decrypt both operate destructively on the input block, and are compiled with zero safety—which disables argument count checking, as well as type checks. And finally: type declarations are made with the hope that each intermediate result will fit in a fixnum (which is the case in a 64-bit Lisp). This code has been tested with CCL, SBCL, and CLISP.
(defconstant +xxtea-delta+ #x9E3779B9
"32-bit constant for XXTEA cipher.")
(defmacro %xxtea-mx (p &aux (p-remainder `(logand (the fixnum ,p) 3)))
`(macrolet ((%xor (u1 u2) `(the (unsigned-byte 48) (logxor ,u1 ,u2))))
(%xor (+ (%xor (ash z -5) (ash y 2)) (%xor (ash y -3) (ash z 4)))
(+ (%xor sum y) (%xor (aref key (%xor ,p-remainder e)) z)))))
(defmacro %xxtea-rounds (block-size)
`(+ 6 (the (unsigned-byte 16)
(truncate 52 ,block-size))))
(defmacro with-u32 ((word place) &body body)
"Helper macro for 32-bit modular arithmetic."
`(let ((,word (the (unsigned-byte 32) ,place)))
(declare (type (signed-byte 48) ,word))
,@body (setf ,place (ldb (byte 32 0) ,word))))
(defun %xxtea-encrypt (v n key)
(declare (optimize (safety 0) (speed 3)))
(declare (type (simple-array (unsigned-byte 32) (*)) v))
(declare (type (simple-array (unsigned-byte 32) (4)) key))
(declare (type (integer 0 (#.array-total-size-limit)) n))
(loop with z of-type (unsigned-byte 32) = (aref v (1- n))
with sum of-type (unsigned-byte 48) = 0
with e of-type (unsigned-byte 32) = 0
with y of-type (unsigned-byte 32) = 0
with rounds fixnum = (%xxtea-rounds n)
do (incf sum +xxtea-delta+)
(setq e (logand (ash sum -2) 3))
(loop for p fixnum below (1- n)
do (setq y (aref v (the fixnum (1+ p))))
(setq z (with-u32 (word (aref v p))
(incf word (%xxtea-mx p)))))
(setq y (aref v 0))
(setq z (with-u32 (word (aref v (1- n)))
(incf word (%xxtea-mx (1- n)))))
do (decf rounds)
until (zerop rounds)
finally (return v)))
(defun %xxtea-decrypt (v n key)
(declare (optimize (safety 0) (speed 3)))
(declare (type (simple-array (unsigned-byte 32) (*)) v))
(declare (type (simple-array (unsigned-byte 32) (4)) key))
(declare (type (integer 0 (#.array-total-size-limit)) n))
(loop with y of-type (unsigned-byte 32) = (aref v 0)
with rounds of-type (unsigned-byte 16) = (%xxtea-rounds n)
with sum of-type (unsigned-byte 48) = (* rounds +xxtea-delta+)
with z of-type (unsigned-byte 32) = 0
with e of-type (unsigned-byte 32) = 0
do (setq e (logand (ash sum -2) 3))
(loop for p fixnum downfrom (1- n) above 0
do (setq z (aref v (the fixnum (1- p))))
(setq y (with-u32 (word (aref v p))
(decf word (%xxtea-mx p)))))
(setq z (aref v (1- n)))
(setq y (with-u32 (word (aref v 0))
(decf word (%xxtea-mx 0))))
do (decf sum +xxtea-delta+)
when (zerop sum) return v)) Base24 encoding
Base24 is a binary-to-text encoding designed for data with 32-bit alignment, which makes it a good fit for XXTEA output (and the encoding overhead is the same as Base36, i.e. seven digits per 32-bit word). These low-level functions can be used to implement functions that encrypt and decrypt strings, representing the encrypted data as Base24 encoded text.
String encryption/decryption
Quick test
Here a string is encrypted three times, using the default encryption key (*default-key*) that was defined above, for testing purposes:
? (encrypt-string "X Marks the Spot")
"FWFRZ4ZBWRBEZGW2X5RTA79HR58T"
? (encrypt-string *)
"6ESFY5XPR5ATR9KSKC8E8APHB9BYF6PYWHTPTS674RGYGR583"
? (encrypt-string *)
"TB6AFKCCBHKH7CSHRBB2BT35596KE57Y4T5TSX4CFFEG6S6T7RG8467Y39T2E3AHKE7G6WCR7BG87H6BKZEP4PKAGYA"
? (decrypt-string *)
"6ESFY5XPR5ATR9KSKC8E8APHB9BYF6PYWHTPTS674RGYGR583"
? (decrypt-string *)
"FWFRZ4ZBWRBEZGW2X5RTA79HR58T"
? (decrypt-string *)
"X Marks the Spot" Note how each layer of encryption nearly doubles the size of the string, due to the overhead of Base24 in representing 32-bit raw data. Also observe that the encrypt-string function encrypts ASCII strings. These cannot have embedded nulls, as the data block being encrypted (or decrypted) is zero-padded with 32-bit alignment. For extra credit, consider how to represent arbitrary byte vectors (or Unicode strings) in encrypted form.
Cryptography