SectorCrypt is a kernel driver that implements sector-level disk encryption using Xoofff-WBC, originally written for Windows and later ported to FreeBSD. I wrote SectorCrypt as a way to learn about Windows KMDF and FreeBSD GEOM kernel drivers. It is written entirely in C.
For most disk I/O driver models, there is a "down path" and an "up path". When writing, the sector address and plaintext are passed down the chain from one driver to another. The encryption driver transforms the plaintext to ciphertext and passes it to the next driver downstream until it eventually reaches the disk; a status code is then propagated upstream. When reading, the sector address is propagated downstream until it eventually reaches the disk. The disk returns the requested sector, which is then propagated upstream. The encryption driver transforms the ciphertext to plaintext and passes it upstream until it eventually reaches the application.
SectorCrypt works by employing a tweakable wide block cipher (Xoofff-WBC) to encrypt each sector.
Sector-level disk encryption is challenging because it does not allow for any expansion; i.e., there is no room for a nonce or authentication tag. The solution is to use a wide block cipher to reversibly map (e.g. 4K) blocks to other (4K) blocks in a way that is computationally indistinguishable from true randomness to an adversary who does not know the secret key. As input, we have the secret key and the sector plaintext. The idea is to split the sector plaintext into two halves, and use the halves to encrypt each other. Clearly, some sacrifices must be made: without a nonce, it is not possible to achieve perfect semantic security nor prevent replay attacks (e.g. copying in a sector from a previous state); without an authentication tag, it is not possible to achieve surefire authentication. However, flipping even a single bit in an encrypted sector will cause every bit of the decrypted plaintext to flip with 50% probability. In other words, this mode has the advantage of being non-malleable. Thanks to the fact that the first and last rounds need only expand a short one-block tag, the construction performs two read passes over the input and only one write pass.
Consider an adversary who copies an encrypted sector from one location to another. It could be problematic if an encrypted sector decrypts to identical plaintext regardless of its location on the disk. To mitigate this concern, we employ a tweak W. The tweak is a public value that is plugged into the wide block cipher to change its mapping on the fly. For the application of disk encryption, it is natural to use the sector address as the tweak. This way, copying a sector to another location offers no gain: instead, the copied sector is decrypted under a different tweak and thus yields random garbage. In addition, the tweak helps provide a level of semantic security: even if multiple sectors hold identical plaintext, the encrypted output is uncorrelated due to the differing tweak.
Because SectorCrypt is an FPE scheme, there is no data overhead due to a lack of nonce and authentication tag. Encryption/decryption of a 4096-byte sector with dynamic tweak takes approximately 4.87 microseconds on an Intel Xeon Silver 4110 CPU with AVX-512. 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.
To demonstrate the wide block cipher, I created a small disk of size approximately 256 MB. I inserted my disk encryption driver into the chain of drivers for this disk, and then initialized and formatted the disk.
Next, I created a very large bitmap to fill a majority of the disk. I did this to simplify the demonstration.
From outside the VM, I opened the disk in a disk editor. I then seeked to a sector somewhere near the middle. Note how even though an empty bitmap fills the disk, the sectors appear to be full of random bits. This is because the wide block cipher reversibly transforms blocks to other blocks in a uniformly random way, tweaked by the sector address. This effectively prevents any patterns from appearing, regardless of disk contents.
I then modified a single byte in a single sector using the disk editor.
Back in the VM, I opened the bitmap. Since one 512-byte sector underpins 171 24-bit pixels, we would expect to see a string of 171 random-looking pixels somewhere within the bitmap. Sure enough, this is exactly what we see. This demonstrates that tampering with a sector causes every bit of the decrypted plaintext to flip with 50% probability. This non-malleable property provides some protection against an adversary modifying sectors from the outside.