StreamCrypt

StreamCrypt performs authenticated encryption of streams using Xoofff. It is highly parallel and supports AVX-512. It allows any length of key to be specified down to the granularity of a bit. It can represent the key in a wide range of bases, including a word-based scheme to aid in memorization. It is written in pure unmanaged C++ using Win32 for window creation and event handling and Direct2D for custom UI rendering. A cross-platform console version also exists.

Details

The program splits the input stream into 8 KiB blocks. Each block is encrypted and authenticated as part of a stateful session, resulting in a sequence of (ciphertext, tag)-pairs being written to the output stream.

StreamCrypt

More specifically, the program first generates a random diversifier D using OS-specific secure random number generator facilities. Then, it compresses the diversifier into the state for diversification. Next, enough keystream K1 is expanded to encrypt plaintext P1, both of which are XORed together to produce ciphertext C1. Ciphertext C1 is then compressed into the state and tag T1 is expanded, which authenticates not only C1 but everything compressed thus far (the diversifier D). This process is repeated as many times as necessary until the entire stream has been encrypted. Because each tag authenticates all compressed blocks thus far in the session, the adversary is prevented from being able to undetectably tamper with any blocks in any way imaginable. Because the last ciphertext block is known to be partial, truncation is also detected. There is no practical limit on the size of streams that can be handled thanks to Xoofff's data complexity bound. Semantic security is achieved due to the random diversifier: encrypting the same stream repeatedly will result in entirely uncorrelated ciphertexts.

The random diversifier is 256 bits in length. Using a diversifier of this length ensures that it would take on average about 2128 encryptions under a single key for some pair of diversifiers to collide, which is astronomical. Tags are 128 bits in length, which means it would take on average about 2127 attempts by the adversary to successfully forge a ciphertext block. Because forgery attempts are inherently online (i.e., such an attack requires access to a keyed decryption oracle), this attack is expensive. Instead, the adversary would likely try to brute force search for the key, which would take about 2127 attempts on average, assuming a 128-bit key is used. Even if all of the computing power in the world was capable of testing 297 keys per year, it would take more than 1 billion years to find a 128-bit key on average.

Data overhead

The diversifier contributes 32 bytes to the output size. The authentication tags contribute 16 bytes per 8 KiB block of plaintext, plus the last block, which is always less than 8 KiB in length. Asymptotically, there are 2-9 units of overhead per unit of input:

StreamCrypt

Time overhead

Asymptotically, an encryption latency of ~34 picoseconds per bit (or throughput of ~3.45 GiB per second) is achieved on an Intel Xeon E-2356G CPU with AVX-512:

StreamCrypt

Note that I don't measure in terms of cycles since the results still differ across architectures and require disabling a number of CPU features. Instead, I prefer real-world measurements with meaningful numbers.

Authentication overhead

Consider the scenario where the very last bit of ciphertext is corrupted, given an arbitrary plaintext length. Because StreamCrypt is session-based, authentic blocks can be safely consumed as soon as they are authenticated. This means there are asymptotically very few "processed-then-thrown-out" bits in contrast to a sessionless mode:

StreamCrypt

At the other extreme, consider when the very first bit of ciphertext is corrupted, given an arbitrary plaintext length. StreamCrypt detects the corruption within the first 8 KiB and stops processing, while a sessionless mode must process the entire ciphertext:

StreamCrypt

Demonstration

StreamCrypt StreamCrypt StreamCrypt StreamCrypt StreamCrypt StreamCrypt

A cross-platform console version also exists and has been tested under FreeBSD and Windows. It supports the same features as the GUI version, including AVX-512.

% ./streamcrypt
Usage: ./streamcrypt --encrypt in out [--keybase words|16|32|64|85] [--keyfile file] [--genkey 128-192]
       ./streamcrypt --decrypt in out [--keybase words|16|32|64|85] [--keyfile file]
       ./streamcrypt --verify  in     [--keybase words|16|32|64|85] [--keyfile file]

% ./streamcrypt --encrypt FreeBSD-13.0-RELEASE-amd64-bootonly.iso out.bin
Keypack:
Encryption successful

In this example, a ZFS dataset is being piped into the program, producing an encrypted output file.

% sudo zfs send test_zvol | ./streamcrypt --encrypt /dev/stdin fs.bin --keyfile mykey
Password:
Encryption successful

% ls -lh fs.bin
-rw-r--r--  1 seth  seth   263M Nov 13 12:52 fs.bin