How LUKS works with Full Disk Encryption in Linux
By: Mattia Zignale
Tags: Computer Security, Cryptography, Linux, Encryption, Technology

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

— which contains persistent header configuration attributes keyslots — that are objects that describe encrypted keys storage areas

— that are objects that describe encrypted keys storage areas digests — used to verify that decrypted keys are correct

— used to verify that decrypted keys are correct segments — describe areas on disk that contain user encrypted data

— 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

— 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

— binary keyslot area size (in bytes), this must be aligned to 4096 bytes flags — array of string objects with persistent flags for the device

— 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

— keyslot type key_size — key size (in bytes) stored in keyslot

— key size (in bytes) stored in keyslot area — allocated area in binary keyslot area

— allocated area in binary keyslot area kdf — PBKDF

— PBKDF af — anti-forensic. Not in use in modern systems (LUKS2)

— 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

— digest type keyslots — array of keyslot objects names assigned to the digest

— array of keyslot objects names assigned to the digest segments — array of sement objects names assigned to the digest

— array of sement objects names assigned to the digest salt — binary salt for the digest

— 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)

— segment type (only crypt is currently used) offset — offset from the device start to the beginning of the segment

— 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)

— segment size (in bytes) or dynamic (for the dynamic resize of the device) iv_tweak — starting offset for the Initializaion Vector

— starting offset for the Initializaion Vector encryption — segment encryption algorithm in dm-crypt notation

— segment encryption algorithm in dm-crypt notation sector_size — sector size for the segment (512, 1024, 2048 or 4096 bytes)

— sector size for the segment (512, 1024, 2048 or 4096 bytes) integrity — LUKS2 user data integrity protection

— 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 hot to get a passphrase to unlock a particular keyslot, and can contain additional JSON metadata.

These are the mandatory fields: