How VeraCrypt's Encryption Algorithm Works: XTS Mode and Cascaded Ciphers Explained

VeraCrypt encrypts data at the sector level using a modular architecture that separates block ciphers (AES, Serpent, Twofish) from encryption modes, implementing XTS mode exclusively for disk encryption with support for both single-cipher and cascaded algorithms.

VeraCrypt's encryption algorithm is implemented in the src/Volume directory of the veracrypt/VeraCrypt repository. The design follows a composable pattern that allows combining multiple block ciphers while standardizing on the XTS mode for all on-disk encryption operations.

Modular Architecture: Ciphers and Modes

VeraCrypt separates cipher primitives from encryption modes through three core abstractions defined in src/Volume/EncryptionAlgorithm.cpp, src/Volume/Cipher.cpp, and src/Volume/EncryptionModeXTS.cpp.

EncryptionAlgorithm Class

The EncryptionAlgorithm class acts as a container that manages one or more Cipher objects and a single EncryptionMode. It calculates the total key size by summing individual cipher key requirements and builds human-readable algorithm names. In EncryptionAlgorithm::GetAvailableAlgorithms(), VeraCrypt registers supported configurations:

EncryptionAlgorithmList l;
l.push_back (shared_ptr<EncryptionAlgorithm>(new AES()));
l.push_back (shared_ptr<EncryptionAlgorithm>(new Serpent()));
l.push_back (shared_ptr<EncryptionAlgorithm>(new Twofish()));
l.push_back (shared_ptr<EncryptionAlgorithm>(new Camellia()));
l.push_back (shared_ptr<EncryptionAlgorithm>(new Kuznyechik()));
l.push_back (shared_ptr<EncryptionAlgorithm>(new AESTwofish()));
l.push_back (shared_ptr<EncryptionAlgorithm>(new AESTwofishSerpent()));

Cipher Implementations

Individual ciphers like CipherAES, CipherSerpent, and CipherTwofish inherit from the base Cipher class in src/Volume/Cipher.cpp. Each implements Encrypt(), Decrypt(), and hardware-accelerated variants. The base class handles runtime detection of AES-NI support via HasAESNI(), routing operations to CPU-intrinsic implementations when available.

EncryptionModeXTS

VeraCrypt exclusively uses XTS mode (XEX-based tweaked-codebook mode with ciphertext stealing) for volume encryption. The EncryptionModeXTS class in src/Volume/EncryptionModeXTS.cpp manages per-sector encryption by handling sector numbers as tweaks and coordinating with underlying ciphers.

Algorithm Composition and Key Distribution

VeraCrypt supports both single-cipher algorithms (e.g., AES) and cascaded algorithms (e.g., AES-Twofish-Serpent). When constructing a cascaded algorithm, the constructor populates the Ciphers vector with multiple cipher instances:

// AESTwofish constructor from EncryptionAlgorithm.cpp
Ciphers.push_back (shared_ptr<Cipher>(new CipherTwofish()));
Ciphers.push_back (shared_ptr<Cipher>(new CipherAES()));
SupportedModes.push_back (shared_ptr<EncryptionMode>(new EncryptionModeXTS()));

The total key size equals the sum of all cipher key sizes. In EncryptionAlgorithm::SetKey(), VeraCrypt segments the master key across ciphers using GetRange():

size_t keyOffset = 0;
foreach_ref (Cipher &c, Ciphers)
{
    c.SetKey (key.GetRange(keyOffset, c.GetKeySize()));
    keyOffset += c.GetKeySize();
}

For AES-Twofish-Serpent with 256-bit keys, this requires 96 bytes of key material (32 bytes × 3 ciphers).

XTS Mode Implementation Details

XTS mode operates on data units (typically 512-byte sectors) using two distinct keys:

  • Data key: Encrypts the actual plaintext via the primary cipher
  • Tweak key: Encrypts the sector number to generate a whitening value

In EncryptionModeXTS::EncryptBufferXTS(), the implementation performs:

  1. Derives the tweak from the sector number (startDataUnitNo)
  2. Encrypts the tweak with the secondary cipher to produce the whitening value
  3. Iterates through each block, XORing plaintext with the whitening value, encrypting with the primary cipher, then XORing back
  4. Applies ciphertext stealing for final partial blocks when sector size isn't a multiple of the block size

The high-level EncryptionAlgorithm::Encrypt() method delegates to the mode:

void EncryptionAlgorithm::Encrypt (uint8 *data, uint64 length) const
{
    if_debug (ValidateState());
    Mode->Encrypt (data, length);          // XTS mode drives the cipher
}

Hardware Acceleration

VeraCrypt detects AES-NI support at runtime in src/Volume/Cipher.cpp:

#ifdef TC_AES_HW_CPU
    state = HasAESNI() ? true : false;
#endif

When available, CipherAES routes encryption operations to hardware-accelerated implementations, significantly improving throughput for large volumes.

End-to-End Encryption Flow

The complete encryption path for a VeraCrypt volume follows these steps:

  1. Algorithm Selection: User selects an algorithm (e.g., "AES-Twofish"), instantiating the corresponding EncryptionAlgorithm object
  2. Key Derivation: User password and/or keyfiles are processed through PBKDF2 (implemented in src/Volume/Pkcs5Kdf.cpp) to generate the master key
  3. Key Distribution: EncryptionAlgorithm::SetKey() splits the master key across the cipher chain
  4. Mode Attachment: SetMode() attaches an EncryptionModeXTS instance
  5. Sector Processing: For each read/write operation, EncryptSectors() or DecryptSectors() invokes XTS mode, which calls the underlying CipherAES or other ciphers to transform data
  6. Header Storage: Volume metadata including the selected algorithm and encrypted keys are stored via src/Volume/VolumeHeader.cpp

Practical Code Examples

Single-Cipher Encryption (AES-XTS)

#include "EncryptionAlgorithm.h"
#include "Cipher.h"

int main ()
{
    // Initialize AES algorithm
    VeraCrypt::shared_ptr<VeraCrypt::EncryptionAlgorithm> algo (new VeraCrypt::AES());
    
    // Prepare 256-bit key (two 128-bit keys for XTS)
    uint8 key[32];
    // ... populate key from cryptographically secure source ...
    
    // Install key and encrypt sector
    algo->SetKey (VeraCrypt::ConstBufferPtr (key, sizeof(key)));
    
    uint8 sector[512] = { /* plaintext data */ };
    algo->EncryptSectors (sector, 0, 1, 512);   // sector 0, count 1, size 512
    
    // Decrypt verification
    algo->DecryptSectors (sector, 0, 1, 512);
}

Cascaded Algorithm (AES-Twofish-Serpent)

// Initialize triple-cascade algorithm
VeraCrypt::shared_ptr<VeraCrypt::EncryptionAlgorithm> algo 
    (new VeraCrypt::AESTwofishSerpent());

// Total key size: 256 + 256 + 256 = 768 bits (96 bytes)
uint8 key[96];   
// ... fill with derived key material from PBKDF2 ...

algo->SetKey (VeraCrypt::ConstBufferPtr (key, sizeof(key)));

// Encrypt 4KB (8 sectors)
uint8 data[4096];
algo->Encrypt (data, sizeof(data));

Summary

  • Modular Design: VeraCrypt separates ciphers from modes through EncryptionAlgorithm, Cipher, and EncryptionModeXTS classes in src/Volume/
  • XTS Exclusive: Only XTS mode is supported for disk encryption, using dual keys (data and tweak) with per-sector whitening
  • Cascading Support: Algorithms can chain multiple ciphers (e.g., AES-Twofish-Serpent), with keys split proportionally across the cipher chain via SetKey()
  • Hardware Acceleration: Automatic AES-NI detection in Cipher.cpp routes to optimized CPU intrinsics when available
  • Sector-Level Operations: All encryption happens at the sector level (typically 512 bytes) through EncryptSectors() and DecryptSectors()

Frequently Asked Questions

What encryption algorithm does VeraCrypt use for disk encryption?

VeraCrypt uses the XTS mode exclusively for on-disk encryption, combined with block ciphers such as AES, Serpent, Twofish, Camellia, or Kuznyechik. XTS treats each disk sector as an independent data unit encrypted with a tweak derived from the sector number, preventing patterns from emerging across identical plaintext sectors.

How does VeraCrypt handle multiple ciphers in a cascade?

When using cascaded algorithms like AES-Twofish-Serpent, VeraCrypt initializes multiple Cipher objects in the Ciphers vector within the EncryptionAlgorithm constructor. The master key is divided sequentially across ciphers in SetKey(), with each cipher receiving its required key size (e.g., 32 bytes for AES-256) from the appropriate byte range of the master key.

Why does VeraCrypt use XTS mode instead of CBC or other modes?

VeraCrypt implements XTS mode because it provides tweakable encryption that doesn't require an initialization vector (IV) stored on disk. By deriving the tweak from the sector number, XTS ensures that identical plaintext blocks in different sectors produce different ciphertexts, while remaining deterministic for decryption without additional metadata storage.

Does VeraCrypt support hardware acceleration for encryption?

Yes. VeraCrypt detects AES-NI (Advanced Encryption Standard New Instructions) support at runtime via HasAESNI() in src/Volume/Cipher.cpp. When available, the CipherAES class routes encryption operations to hardware-accelerated implementations, significantly improving throughput while maintaining compatibility with software fallback for systems without AES-NI support.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →