Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

dm-crypt: Block-Layer Encryption

Overview

dm-crypt is a device-mapper target that provides transparent block-level encryption of storage devices. It encrypts all data written to a block device and decrypts all data read from it, using the kernel crypto API. dm-crypt is the foundation for LUKS (Linux Unified Key Setup), the standard disk encryption format on Linux.

dm-crypt operates at the block layer, below the filesystem. This means any filesystem can be used on top of an encrypted device, and the encryption is invisible to userspace applications.

Introduced: Linux 2.6.4 (commit 382699) Source: drivers/md/dm-crypt.c Module: dm-crypt


Architecture

flowchart TD
    subgraph Userspace["Userspace"]
        APP["Application"]
        FS["Filesystem (ext4, xfs, ...)"]
    end

    subgraph Kernel["Block Layer"]
        DM["Device Mapper"]
        CRYPT["dm-crypt target"]
        CRYPTO["Kernel Crypto API"]
        BIO["bio submission"]
        INLINE["Inline Crypto Engine"]
    end

    subgraph Hardware["Storage"]
        SSD["SSD / NVMe / HDD"]
    end

    APP --> FS
    FS --> DM
    DM --> CRYPT
    CRYPT -->|encrypt write| CRYPTO
    CRYPTO -->|encrypted data| BIO
    CRYPT -->|hardware offload| INLINE
    BIO --> SSD
    INLINE --> SSD
    SSD --> BIO
    BIO --> CRYPTO
    CRYPTO -->|decrypt read| CRYPT
    CRYPT --> DM
    DM --> FS

How dm-crypt Works

Write Path (Encryption)

sequenceDiagram
    participant App as Application
    participant FS as Filesystem
    participant DM as Device Mapper
    participant DC as dm-crypt
    participant CA as Crypto API
    participant Disk as Disk

    App->>FS: write(data)
    FS->>DM: bio (write request)
    DM->>DC: crypt_map(bio)
    DC->>DC: Allocate crypt_io, clone bio
    DC->>CA: crypto_skcipher_encrypt(data, key, iv)
    CA-->>DC: encrypted data
    DC->>Disk: Submit encrypted bio
    Disk-->>DC: Write complete
    DC-->>FS: IO complete

Read Path (Decryption)

sequenceDiagram
    participant App as Application
    participant FS as Filesystem
    participant DM as Device Mapper
    participant DC as dm-crypt
    participant CA as Crypto API
    participant Disk as Disk

    App->>FS: read()
    FS->>DM: bio (read request)
    DM->>DC: crypt_map(bio)
    DC->>Disk: Submit read bio
    Disk-->>DC: Encrypted data
    DC->>CA: crypto_skcipher_decrypt(data, key, iv)
    CA-->>DC: Decrypted data
    DC-->>FS: IO complete with decrypted data
    FS-->>App: Data returned

Key Data Structures

struct crypt_config

/* drivers/md/dm-crypt.c */
struct crypt_config {
    struct dm_dev *dev;              /* Underlying device */
    sector_t start;                  /* Start offset */

    /* Crypto configuration */
    struct crypto_skcipher *cipher;  /* Cipher handle */
    struct crypt_iv_operations *iv_gen_ops; /* IV generator */
    char *cipher_string;             /* Cipher algorithm string */
    char *key_string;                /* Key (hex or base64) */
    unsigned int key_size;           /* Key size in bytes */
    u8 *key;                         /* Encryption key */

    /* IV (Initialization Vector) */
    char *iv_mode;                   /* IV mode (plain, plain64, essiv, etc.) */
    u64 iv_offset;                   /* IV offset */

    /* Memory pool for crypt_io */
    mempool_t io_pool;
    mempool_t page_pool;
    struct bio_set bs;

    /* Workqueue for deferred decryption */
    struct workqueue_struct *io_queue;
    struct workqueue_struct *crypt_queue;

    /* Device geometry */
    unsigned int sector_size;
    unsigned int sector_shift;

    /* Integrity */
    struct dm_integrity_crypt *integrity;

    /* Performance */
    bool submit_from_crypt_cpus;
    unsigned int num_write_cpu_ids;
    unsigned int num_read_cpu_ids;

    /* ... */
};

struct crypt_io

/* drivers/md/dm-crypt.c */
struct crypt_io {
    struct crypt_config *cc;         /* Config */
    struct bio *base_bio;            /* Original bio */
    struct work_struct work;         /* Deferred work */
    sector_t sector;                 /* Sector number */
    atomic_t io_pending;             /* Pending sub-IOs */
    int error;                       /* Error code */
    u8 *integrity_metadata;          /* Integrity metadata */
    struct bvec_iter iter;           /* Bio iterator */
    /* ... */
};

Cipher Configuration

Cipher String Format

dm-crypt uses the kernel crypto API cipher string format:

<cipher>[-<chainmode>[-<ivmode>[:<ivopts>]]]

Examples:

aes-xts-plain64          # AES in XTS mode with plain64 IV
aes-cbc-essiv:sha256     # AES in CBC mode with ESSIV IV
aes-gcm-random           # AES in GCM mode (authenticated)
chacha20-poly1305        # ChaCha20-Poly1305 (AEAD)

Common Ciphers

CipherModeSpeedSecurityUse Case
aes-xts-plain64XTSFast (AES-NI)HighStandard disk encryption
aes-cbc-essiv:sha256CBCModerateModerateLegacy compatibility
aes-gcm-randomGCMFastHigh (AEAD)Authenticated encryption
chacha20-poly1305Fast (no AES-NI)HighARM/mobile devices
aria-xts-plain64XTSModerateHighKorean standard (KCDSA)
sm4-xts-plain64XTSModerateHighChinese standard (GM/T)

IV (Initialization Vector) Modes

IV ModeDescriptionUse Case
plainIV = sector number (32-bit)Small devices (<2TB)
plain64IV = sector number (64-bit)Large devices (default)
essivIV = hash(encrypted sector)CBC mode (prevents watermark)
randomRandom IV per sectorAEAD modes
lmkLinear mixingLegacy

IV Mode Security Implications

flowchart TD
    subgraph CBC_Watermark["CBC Watermark Attack"]
        A["Same plaintext block"] --> B["Same ciphertext block"]
        B --> C["Pattern leaked"]
    end

    subgraph ESSIV_Defense["ESSIV Defense"]
        D["IV = hash(sector_number, key)"] --> E["Same sector → same IV"]
        E --> F["But IV is key-dependent"]
        F --> G["No pattern leak"]
    end

    subgraph XTS_Mode["XTS Mode"]
        H["Tweak = sector number"] --> I["Each sector independent"]
        I --> J["No watermark by design"]
    end

    CBC_Watermark --> ESSIV_Defense
    CBC_Watermark --> XTS_Mode

LUKS Integration

LUKS (Linux Unified Key Setup) is the standard format for dm-crypt:

LUKS Header

# LUKS2 header info
cryptsetup luksDump /dev/sda1
# LUKS header information
# Version:        2
# Cipher:         aes-xts-plain64
# Hash:           sha256
# Offset:         32768 [bytes]
# Key offset:     256 [sectors]
# ...

Creating an Encrypted Volume

# Create LUKS2 encrypted partition
cryptsetup luksFormat /dev/sda1 --type luks2 \
    --cipher aes-xts-plain64 --key-size 512 --hash sha256

# Open the encrypted volume
cryptsetup luksOpen /dev/sda1 mydata

# Create filesystem
mkfs.ext4 /dev/mapper/mydata

# Mount
mount /dev/mapper/mydata /mnt/data

# Close when done
umount /mnt/data
cryptsetup luksClose mydata

LUKS2 Features

# Add a key
cryptsetup luksAddKey /dev/sda1

# Remove a key
cryptsetup luksRemoveKey /dev/sda1

# Add a recovery key
cryptsetup luksAddKey /dev/sda1 --new-keyfile /path/to/recovery.key

# Backup header
cryptsetup luksHeaderBackup /dev/sda1 --header-backup-file header.bak

# Restore header
cryptsetup luksHeaderRestore /dev/sda1 --header-backup-file header.bak

# Enable TRIM (discard) support
cryptsetup open --allow-discards /dev/sda1 mydata

# Convert LUKS1 to LUKS2
cryptsetup convert /dev/sda1 --type luks2

LUKS2 Token System

# Add a token for systemd-creds
cryptsetup token add --token-id 0 /dev/sda1

# List tokens
cryptsetup token export --token-id 0 /dev/sda1

# Use FIDO2 token for unlock
systemd-cryptenroll /dev/sda1 --fido2-device=auto

# Use TPM2 for automatic unlock
systemd-cryptenroll /dev/sda1 --tpm2-device=auto

# Use PKCS#11 smart card
systemd-cryptenroll /dev/sda1 --pkcs11-token-uri=auto

Threat Model

What dm-crypt Protects Against

ThreatMitigation
Physical disk theftData encrypted at rest
Cold boot attacksKey in kernel memory only (partially)
Evil maid attacksCombined with TPM + Secure Boot
Data recovery from discarded drivesEncrypted blocks appear random
Unauthorized data accessKey required to decrypt

Attack Surface

flowchart TD
    subgraph Attacks["dm-crypt Attack Surface"]
        COLD["Cold boot attack<br>(RAM freeze + read)"]
        EVIL["Evil maid attack<br>(modify boot chain)"]
        DMA["DMA attack<br>(Thunderbolt, FireWire)"]
        TIMING["Timing side-channel<br>(IV reuse patterns)"]
        SUSPEND["Suspend-to-RAM<br>(LUKS wipe bug CVE-2024)"]
        KEY_LEAK["Key material leak<br>(core dump, swap)"]
    end

    subgraph Mitigations["Mitigations"]
        TPM_SEAL["TPM-sealed keys"]
        SECURE_BOOT["Secure Boot chain"]
        IOMMU["IOMMU (VT-d)"]
        XTS["XTS mode (no watermark)"]
        WIPE["Memory wipe on suspend"]
        NO_SWAP["Disable swap on encrypted root"]
    end

    COLD --> TPM_SEAL
    EVIL --> SECURE_BOOT
    DMA --> IOMMU
    TIMING --> XTS
    SUSPEND --> WIPE
    KEY_LEAK --> NO_SWAP

Known Vulnerabilities

CVE/IssueYearImpactMitigation
LUKS suspend wipe failure2024Key not wiped from RAM on suspendKernel fix in 6.9+
CBC watermark attack2005Pattern leakage in CBC modeUse XTS mode
Evil maid attack2009Boot chain modificationSecure Boot + TPM
Cold boot attack2008RAM data persistenceMemory encryption (AMD SME)

Performance Considerations

Hardware Acceleration

# Check if AES-NI is available
grep aes /proc/cpuinfo

# Check kernel crypto algorithm availability
cryptsetup benchmark
# Tests encryption/decryption speeds for various algorithms

# Detailed crypto info
cat /proc/crypto | grep -A5 "name.*aes"

# Check for hardware crypto engines
cat /proc/crypto | grep "driver.*aes.*ni"

Multiqueue Support

Modern dm-crypt uses per-CPU workqueues for parallel encryption:

# Check dm-crypt queue configuration
dmsetup table --showkeys /dev/mapper/mydata
# 0 1953525168 crypt aes-xts-plain64 ... 0 8:1 32768

# The "8 8" at the end shows 8 encryption threads
# Adjust with:
cryptsetup refresh mydata --perf-submit_from_crypt_cpus

# Check current crypto threads
cat /proc/interrupts | grep crypto

TRIM/Discard

# Enable TRIM (allows SSD wear leveling but leaks info about encrypted sectors)
cryptsetup open --allow-discards /dev/nvme0n1p1 mydata

# In /etc/crypttab:
mydata /dev/nvme0n1p1 none discard

# Check TRIM support
lsblk --discard /dev/mapper/mydata

⚠️ Security warning: TRIM reveals which sectors are unused, potentially leaking information about filesystem usage patterns.

Performance Benchmarks

# Benchmark dm-crypt
cryptsetup benchmark
# Algorithm       | Key |  Encryption |  Decryption
# aes-xts        | 256b |  3.2 GiB/s |   3.1 GiB/s
# aes-xts        | 512b |  2.4 GiB/s |   2.3 GiB/s

# Benchmark underlying device
dd if=/dev/zero of=/dev/mapper/mydata bs=1M count=1024 oflag=direct

# Compare with unencrypted
dd if=/dev/zero of=/dev/sda1 bs=1M count=1024 oflag=direct

# Detailed I/O benchmark
fio --name=crypt-test --rw=randread --bs=4k --size=1G \
    --filename=/dev/mapper/mydata --direct=1 --ioengine=libaio --iodepth=32

Integrity Protection

dm-integrity + dm-crypt

For authenticated encryption (prevents tampering):

# Create integrity device first
integritysetup format /dev/sda1 --tag-size 32 --integrity hmac-sha256

# Open integrity device
integritysetup open /dev/sda1 mydata-integrity

# Create encrypted device on top
cryptsetup luksFormat /dev/mapper/mydata-integrity \
    --cipher aes-gcm-random --integrity hmac-sha256

# Stack: filesystem → dm-crypt → dm-integrity → block device

dm-crypt with AEAD

# Use authenticated encryption directly
cryptsetup luksFormat /dev/sda1 --cipher aes-gcm-random --integrity aead

# Or with HMAC
cryptsetup luksFormat /dev/sda1 --cipher aes-xts-plain64 \
    --integrity hmac-sha256-256

Integrity Stack Diagram

flowchart TD
    subgraph Stack["Encryption + Integrity Stack"]
        FS["Filesystem (ext4)"]
        CRYPT["dm-crypt (AES-XTS)"]
        INTEGRITY["dm-integrity (HMAC-SHA256)"]
        BLK["Block device (NVMe)"]
    end

    FS --> CRYPT
    CRYPT --> INTEGRITY
    INTEGRITY --> BLK

Kernel Internals

dm-crypt Target Registration

/* drivers/md/dm-crypt.c */
static struct target_type crypt_target = {
    .name = "crypt",
    .version = {1, 23, 0},
    .module = THIS_MODULE,
    .ctr = crypt_ctr,
    .dtr = crypt_dtr,
    .map = crypt_map,
    .iterate_devices = crypt_iterate_devices,
    .io_hints = crypt_io_hints,
    .status = crypt_status,
    .postsuspend = crypt_postsuspend,
    .resume = crypt_resume,
};

Workqueue Architecture

flowchart TD
    subgraph WQ["dm-crypt Workqueues"]
        IOQ["io_queue<br>(I/O submission)"]
        CQ["crypt_queue<br>(per-CPU encryption)"]
    end

    BIO["Incoming bio"] --> IOQ
    IOQ --> CQ
    CQ -->|CPU 0| ENC0["Encrypt on CPU 0"]
    CQ -->|CPU 1| ENC1["Encrypt on CPU 1"]
    CQ -->|CPU 2| ENC2["Encrypt on CPU 2"]
    CQ -->|CPU N| ENCN["Encrypt on CPU N"]
    ENC0 --> DISK["Submit to disk"]
    ENC1 --> DISK
    ENC2 --> DISK
    ENCN --> DISK

Key Lifecycle

/* Key is loaded during ctr (constructor) */
static int crypt_ctr(struct dm_target *ti, unsigned int argc, char **argv)
{
    /* Parse cipher string */
    /* Allocate and load key */
    /* Initialize crypto API handles */
    /* Set up IV generator */
    /* Create workqueues */
}

/* Key is freed during dtr (destructor) */
static void crypt_dtr(struct dm_target *ti)
{
    /* Zero key material */
    memzero_explicit(cc->key, cc->key_size);
    /* Free crypto handles */
    /* Destroy workqueues */
}

Kernel Configuration

# Required for dm-crypt
CONFIG_DM_CRYPT=y               # dm-crypt target
CONFIG_BLK_DEV_DM=y             # Device Mapper core
CONFIG_CRYPTO_AES=y             # AES cipher
CONFIG_CRYPTO_XTS=y             # XTS mode
CONFIG_CRYPTO_CBC=y             # CBC mode (legacy)
CONFIG_CRYPTO_ESSIV=y           # ESSIV IV generator
CONFIG_CRYPTO_GCM=y             # GCM mode (AEAD)
CONFIG_CRYPTO_CCM=y             # CCM mode (AEAD)
CONFIG_CRYPTO_CHACHA20POLY1305=y # ChaCha20-Poly1305
CONFIG_CRYPTO_SHA256=y          # SHA-256 (for ESSIV)
CONFIG_CRYPTO_SHA512=y          # SHA-512 (for key derivation)
CONFIG_CRYPTO_USER_API_SKCIPHER=y # Userspace API

# For dm-integrity
CONFIG_DM_INTEGRITY=y           # dm-integrity target

# For hardware acceleration
CONFIG_CRYPTO_AES_NI_INTEL=y    # AES-NI (Intel)
CONFIG_CRYPTO_AES_ARM64_CE=y    # ARM Crypto Extensions

Troubleshooting

Cannot Open Encrypted Volume

# Check LUKS header
cryptsetup luksDump /dev/sda1

# Verify key slot
cryptsetup luksOpen --test-passphrase /dev/sda1
echo $?  # 0 = success

# Try recovery key
cryptsetup luksOpen --key-file /path/to/recovery.key /dev/sda1 mydata

# Check for header corruption
cryptsetup isLuks /dev/sda1
echo $?  # 0 = valid LUKS

Performance Issues

# Check crypto algorithm availability
cat /proc/crypto | grep "name.*aes"

# Verify hardware acceleration
cryptsetup benchmark

# Check for encryption thread saturation
cat /proc/interrupts | grep dm-crypt

# Monitor I/O latency
iostat -x 1

# Check for queue depth issues
cat /sys/block/nvme0n1/queue/nr_requests

Corrupted Data

# Check filesystem
fsck /dev/mapper/mydata

# Verify LUKS header integrity
cryptsetup luksDump /dev/sda1

# Restore header from backup
cryptsetup luksHeaderRestore /dev/sda1 --header-backup-file header.bak

# Check for bad sectors
badblocks -v /dev/sda1

Suspend/Resume Security

dm-crypt has special handling for system suspend:

sequenceDiagram
    participant User as User
    participant Kernel as Kernel
    participant DC as dm-crypt
    participant RAM as RAM
    participant Disk as Disk

    User->>Kernel: systemctl suspend
    Kernel->>DC: crypt_postsuspend()
    DC->>DC: Flush pending I/O
    DC->>RAM: Wipe key material from CPU caches
    Kernel->>RAM: Enter S3 sleep
    Note over RAM: Key remains in RAM
    Kernel->>RAM: Resume from S3
    Kernel->>DC: crypt_resume()
    DC->>DC: Reload key material

Security concern: During S3 suspend, the encryption key remains in RAM. An attacker with physical access can perform a cold boot attack to extract the key. Mitigations:

  • Use S4 (hibernate) instead of S3 (suspend)
  • Enable memory encryption (AMD SME/SEV, Intel TME)
  • Use CONFIG_RESET_ATTACK_MITIGATION for automatic wipe

fscrypt vs dm-crypt

Aspectdm-cryptfscrypt
ScopeFull block devicePer-file/per-directory
FilesystemAnyext4, f2fs, btrfs
Key granularityPer-devicePer-key (policy)
PerformanceFull I/O pathInline with filesystem
MetadataNot encryptedCan encrypt filenames
Use caseFull disk encryptionMobile, per-user encryption
IntegrationDevice MapperFilesystem built-in

Source Files

FileContents
drivers/md/dm-crypt.cdm-crypt device-mapper target
drivers/md/dm-integrity.cdm-integrity for authenticated encryption
include/linux/device-mapper.hDevice mapper API
crypto/Kernel crypto API implementation
drivers/crypto/Hardware crypto drivers

Further Reading


See Also

  • Device Mapper — device mapper framework
  • Block I/O — block layer
  • Filesystem Encryption — per-file encryption
  • Crypto API — kernel crypto API
  • NVMe — NVMe performance with encryption