Crypt::Mode::XTS(3)

πŸ“¦ version 0.091, in arch rolling

πŸ’… Crypt::Mode::XTS β€” the perl manpage, but make it group chat

okay so bestie, the module you're looking at is Crypt::Mode::XTS, which lives in the User Contributed Perl Documentation section (section 3, iykyk). It's the XEX-based tweaked-codebook mode with ciphertext stealing β€” say that five times fast, or don't, honestly nobody does. πŸŽ€


✨ NAME ✨

Crypt::Mode::XTS β€” Block cipher mode XTS [XEX-based tweaked-codebook mode with ciphertext stealing] πŸ’–


πŸ¦‹ SYNOPSIS πŸ¦‹

use Crypt::Mode::XTS;

my $xts = Crypt::Mode::XTS->new('AES', $key);   # $key = key1 || key2

# one complete data unit (sector) per call; same object does both directions
my $ct = $xts->encrypt($plaintext,  $tweak);
my $pt = $xts->decrypt($ciphertext, $tweak);

# tweak: 16-byte string, or a data-unit number
my $ct1 = $xts->encrypt($sector, "\x02" . "\x00" x 15);
my $ct2 = $xts->encrypt($sector, 2);            # same thing (64-bit LE, zero-padded)

# the canonical loop - key schedules computed exactly once, in new()
my $unit = 0;
while (my $n = sysread $in, my $sector, 4096) {
  syswrite $out, $xts->encrypt($sector, $unit++);
}

like, look at that last block. that's the whole point of this module. one object, key schedules done once, loop forever. iconic. πŸ‘‘


🌸 DESCRIPTION 🌸

So this module implements the XTS cipher mode exactly as specified by IEEE 1619-2007 and NIST SP 800-38E, ciphertext stealing included β€” meaning data units that aren't a multiple of the block size still get handled, no cap. 🫢 Quick heads up though: it works only with 128-bit block ciphers from CryptX β€” think 'AES', 'Twofish', 'Serpent', 'Camellia', 'ARIA', 'SM4', ... and friends.

BEWARE bestie, this one's important: XTS gives you confidentiality only β€” no integrity, no authentication, nada. A single bit-flip in the ciphertext garbles exactly the corresponding 16-byte plaintext block, and it's predictably positioned β€” that's malleability by design, not a bug. Don't come for me, it's literally the spec. πŸ’€

XTS is for encrypting storage in place, where the tweak is implicit in the location and there is genuinely no room for a MAC. For anything that travels β€” files, messages, backups β€” please please please use an AEAD mode ("Crypt::AuthEnc::*") instead. πŸ™ˆ Also be aware that an attacker with two snapshots of the same device can see which blocks changed. that's just how it is, ngl.

And unlike the other "Crypt::Mode::*" modules, XTS is not an online mode (ciphertext stealing needs the complete data unit), so there's no "start_encrypt"/"add"/"finish" API, and this module does not subclass Crypt::Mode. One "encrypt"/"decrypt" call processes exactly one complete data unit. Ciphertext length always equals plaintext length. periodt. ✨


🧚 METHODS 🧚

πŸ’œ new

my $xts = Crypt::Mode::XTS->new($cipher, $key);
#or
my $xts = Crypt::Mode::XTS->new($cipher, $key, $cipher_rounds);

# $cipher .......... [string] cipher with 128-bit blocks, e.g. 'AES', 'Twofish',
#                    'Serpent', 'Camellia', 'ARIA', 'SM4'
#                    or any <NAME> for which there is a Crypt::Cipher::<NAME>
#                    module with a 16-byte block size
# $key ............. [binary string] key1 || key2, each half a valid key for the
#                    cipher; for AES that means 32, 48 or 64 bytes total
# $cipher_rounds ... [integer] optional, number of rounds for the given cipher
#                    (0 or omitted = the cipher's standard number of rounds)

Both key schedules get computed right here, exactly once β€” "encrypt"/"decrypt" only use the schedules afterwards. The raw key is not stored in the object, and the schedules are zeroized when the object gets destroyed. clean. 🧼

It croaks if the cipher doesn't have 128-bit blocks, if either key half isn't a valid key size for the cipher, and β€” per FIPS 140 Implementation Guidance A.9 β€” if "key1" equals "key2" (that config degrades XTS toward ECB on single-block data units, which is giving ✨ nothing ✨).

The returned object is direction-free: the same object encrypts and decrypts. one object, both vibes. πŸ’…


πŸ”₯ encrypt

my $ct = $xts->encrypt($plaintext, $tweak);

Encrypts one complete data unit (like, one disk sector, for example). Returns the ciphertext as a binary string the same length as the plaintext. πŸŽ€

$plaintext must be at least 16 bytes (one full cipher block); any length above that is fine, including lengths that aren't a multiple of 16 (ciphertext stealing handles the final partial block, she's got it). It croaks above 2^20 blocks (16MiB) β€” that's the NIST SP 800-38E bound per data unit, not me being dramatic.

$tweak is either exactly 16 binary bytes, or a non-negative integer < 2^64 that gets encoded as 64-bit little-endian and zero-padded to 16 bytes (the "data unit sequence number" convention β€” identical to dm-crypt plain64 and to what OpenSSL and kernel test vectors use). πŸ‘‘

Note bestie: a 16-byte string is always taken as a raw binary tweak, even if it consists entirely of digits. no sneakiness. πŸ™ˆ

⭐ decrypt

my $pt = $xts->decrypt($ciphertext, $tweak);

Decrypts one complete data unit. Returns the plaintext as a binary string the same length as the ciphertext. Same rules for $ciphertext and $tweak as in "encrypt". 🌈


πŸ’– SEE ALSO πŸ’–


and that's the whole page bestie! πŸ¦‹ this one's from perl v5.44.0, dated 2026-10-17, and it's filed under Crypt::Mode::XTS(3) in the User Contributed Perl Documentation. go encrypt a disk, or don't, just β€” use an AEAD if it travels, ily, bye! πŸ’…βœ¨πŸŽ€

πŸ“¦ where this page comes from