How LUKS works with Full Disk Encryption in Linux

Mattia Zignale
InfoSec Write-ups
Published in
8 min readSep 16, 2021

--

Full Disk Encryption

Full disk encryption is encryption at disk level (as suggested by the name) and it is transparent to the user as it operates below the filesystem layer. Basically it is a block device encryption, which means that when a block from disk is read (or written) the encryption module at kernel level works for us, like a translator.

This kind of encryption does not differentiate between sensitive and not sensitive information, it just crypts all.

While the file-based encryption has to been implemented from developers (using libraries) for each contest and could not work in other computers if not correctly implemented, full disk encryption crypts all and it is file-system agnostic (because you’re crypting blocks on device), this helps if you want to have an encrypted LVM setup or a partition table.

One of the disadvantages of the full disk encryption is that when the system is on the disk is unlocked, while the file-based encryption must be decrypted every time you need to use it.
Essentially the full disk encryption protects you only when the system is off, so in case someone steals your laptop or pc your files are safe (well, it depends on some other factors).

Overview of dm-crypt

The default standard device-mapper encryption in Linux is provided by dm-crypt in the Linux kernel, so if you are looking to have full control over partition and key management this is what you should use.
The management of dm-crypt is done through cryptsetup: LUKS is an additional frontend for dm-crypt with the aim to semplify all the cryptographic procedures.
The following image describes where are the layers mentioned above (filesystems, directories, block device, etc.):

How are organized the encryption layers

How LUKS works

LUKS (Linux Unified Key Setup), in particular LUKS2, provides a generic key store on the dedicated area on a disk, with the ability to use multiple passphrases to unlock a stored key. LUKS2 has a more flexible way of storing metadata (redundant information to provide recovery in the case of corruption in metadata area).

LUKS header provides metadata for encryption setup. The followings are some of the features:

  • Checksum mechanism to detect corruption and manipulation in header
  • Metadata area is stored in two copies for a possible recovery
  • Metadata are stored in JSON format. It allows future extensions without modifying binary structures
  • Header contains objects called token, which contains information to where to get the unlocking passphrase

Header

LUKS Header explaination

LUKS header are basically divided into three parts:

  • Binary header (4096 byte, only 512 bytes are used)
  • Metadata in JSON
  • Keyslot area

As you can see in the image above, the binary and JSON areas are stored twice and in normal condition they contain the same values.

The size of the binary header ensures that it is always written in only one sector (atomic write) to prevent errors and/or corruptions.

Binary Header

The binary header contains all the information needed to inform the system that is going to approach a LUKS device. The information saved here are basic information such as labels, a signature that indicates this is a LUKS device, header size and the metadata checksum (very important!).

The primary header must be stored in sector 0 of the device, the second one starts after the first JSON area at fixed offset position as indicated below:

Offset  | JSON
[bytes] | [kB]
---------------
16384 | 12
32768 | 28
65536 | 60
131072 | 124
262144 | 252
524288 | 508
1048576 | 1020
2097152 | 2044
4194304 | 4092

JSON Area

The JSON Area starts after the binary header and the end must be aligned to 4096-byte sector offset, so JSON area size is

JSON_Area_size = header_size - 4096

So the offset where the second binary header starts (reported in the table above) now makes sense: JSON size + bin_header_size (4096 byte) must match with the Offset. The unused space is filled with zeros.

Keyslot Area

Keyslot area is space on disk that can be allocated for binary data from keyslots, in fact there are stored encrypted keys referenced from keyslot metadata.

The allocated area is defined in a keyslot by an area object that contains offset (from the device beginning) and size fields, both fields must be validated otherwise will be rejected.

Alignment padding

The alignment padding has the purpose to align the encrypted data at the beginning of a block (block are encrypted one by one, typically a block is 512 byte) with the right offset to make LUKS properly work with the encrypted sectors.

Metadata

LUKS metadata allows defining object with a specific functionality. Objects not recognized are ignored, but still maintained into JSON metadata.

The implementation must validate the JSON structure before updating the on-disk header.

LUKS has some mandatory objects as follow:

  • config — which contains persistent header configuration attributes
  • keyslots — that are objects that describe encrypted keys storage areas
  • digests — used to verify that decrypted keys are correct
  • segments — describe areas on disk that contain user encrypted data
  • tokens — can optionally include additional metadata, bindings to other systems

Binary data inside JSON are stored in Base64 encoding and 64-bit integers are stored as string in decimal notation.

The following is a drill-down of the mandatory objects in LUKS metadata:

Config object

The config object contains these fields, that are global for the LUKS device:

  • json_size — JSON area size (in bytes), this fields must be equal to the binary header
  • keyslots_size — binary keyslot area size (in bytes), this must be aligned to 4096 bytes
  • flags — array of string objects with persistent flags for the device
  • requirements — array of string objects with additional required features for the LUKS device

Keyslot object

Keyslot objects contain information about stored keys, areas, where binary keyslot are stored, encryption type, anti-forensic function used, password-based key derivation function and related parameters.

Each keyslot object contains:

  • type — keyslot type
  • key_size — key size (in bytes) stored in keyslot
  • area — allocated area in binary keyslot area
  • kdf — PBKDF
  • af — anti-forensic. Not in use in modern systems (LUKS2)
  • priority — is the keyslot priority: 0 = ignore, 1 = normal, 2 = high.

Digest object

To verify that a decrypted key (from a keyslot) is correct, LUKS uses digests object. These objects are assigned to keyslots and segments, if not assigned to a segment the digest is used for a unbound key.

Digest object contains these fields:

  • type — digest type
  • keyslots — array of keyslot objects names assigned to the digest
  • segments — array of segment objects names assigned to the digest
  • salt — binary salt for the digest
  • digest — binary digest data

Segment object

Segment object contains the definition for encrypted areas on the disk. For a normal LUKS device there is only one data segment present.

These are the fields:

  • type — segment type (only crypt is currently used)
  • offset — offset from the device start to the beginning of the segment
  • size — segment size (in bytes) or dynamic (for the dynamic resize of the device)
  • iv_tweak — starting offset for the Initializaion Vector
  • encryption — segment encryption algorithm in dm-crypt notation
  • sector_size — sector size for the segment (512, 1024, 2048 or 4096 bytes)
  • integrity — LUKS2 user data integrity protection
  • flags — array of string objects with additional information for the segment

Token object

Token object is an object that describe how to get a passphrase to unlock a particular keyslot, and can contain additional JSON metadata.
These are the mandatory fields:

  • type — defines the token type
  • keyslots — array of keyslot objects names assigned to the token

LUKS Backup and Restore

LUKS Header Backup

LUKS header backup is useful in case you corrupt or delete your header. If you work directly on the disk (and not on the device mapper) for any reason, this could save you in case something bad happens.

This will create a file with your LUKS header:

sudo cryptsetup luksHeaderBackup /dev/MY_DRIVE --header-backup-file /path/to/backup-file

You can backup your LUKS header with dd command too:

sudo dd if=/dev/MY_DRIVE of=/path/to/backup-file bs=X count=Y

With dd you should know the size of your header in order to make sure you copy all the data in it. Use luksDump to get size and offset information.

LUKS Header Restore

This is one of the worst scenario you may face: you have lost/corrupted your LUKS header. If you have a backup copy, you can restore them booting from a Live OS, this procedure will restore your headers back.

sudo cryptsetup luksHeaderRestore /dev/MY_DRIVE --header-backup-file /path/to/backup-file

LUKS will ask for confirm the command:

WARNING!
========
Device /dev/MY_DRIVE already contains LUKS2 header. Replacing header will destroy existing keyslots.

Are you sure? (Type uppercase yes):

How to wipe a LUKS encrypted drive

If you have a LUKS encrypted drive, you have two ways to wipe it: delete the LUKS header located at the beginning of the encrypted disk, or you can wipe the entire disk as a standard disk.

Deleting LUKS Header

The header, as we stated above, contains all the information to unlock the disk, without these information (keys in particular) is practically impossible to recover the data.
So may be sufficient to erase the header:

sudo shred --size=LUKS_HEADER_SIZE /dev/MY_DRIVE

If you have multiple partition make sure you shred only the LUKS partition and not the whole disk or the partition table!

You can also wipe your keys with cryptsetup as follow:

sudo cryptsetup erase /dev/MY_DRIVE

Additionally you can remove all the LUKS header with wipefs:

sudo wipefs -a /dev/MY_DRIVE

Wipe the disk

Wiping the disk will be the simplest solution, but not the fastest. In order to proceed erasing your disk, you need to boot from a Live OS.

With shred we can erase our disk and prevent any forensic data recovery:

sudo shred /dev/MY_DRIVE

Also dd can be used for this purpose and the input source should be urandom/random or zero:

sudo dd if=/dev/urandom of=/dev/MY_DRIVEORsudo dd if=/dev/zero of=/dev/MY_DRIVE

Conclusion

In this article we have explored LUKS, a valid frontend for full disk encryption. It is important to ensure that your LUKS configuration is secure (strong ciphers); if you do not need the explicit features of the first version of LUKS, use LUKS2.

We have also seen that erasing a LUKS partition could be easier and faster than a normal disk, this is an interesting point since at the moment (2021) there is no way to recover encrypted data.

If you want to understand how to secure your Linux system, not only enabling the Full Disk Encryption, see also this article:

--

--