# How does LUKS encrypt the master key?

I'm studying how LUKS/dm-crypt works, and I've learnt that:

• the user supplies a (possibly weak) passphrase
• the passphrase is turned into a Key Encryption Key (KEK) by a Key Derivative Function (KFD), making it harder to crack via a brute-force attack
• the KEK is used to encrypt and decrypt the Master Key
• the encrypted Master Key is stored in plaintext in the LUKS header, and the decrypted Master Key is used to encrypt and decrypt the disk sectors using a cipher (e.g. AES)

However, I couldn't find how the Master Key is actually encrypted using the KEK. Is it encrypted using the same cipher that is used to encrypt the disk sectors?

• Welcome to Cryptography.SE. Where did you learn? Are you looking version 1 or 2 AES-XTS is used for keyslot encryption in version 2. Apr 9 at 18:58
• @kelalaka not a single source in particular. If there are differences between LUKS 1 and 2, I'd like to know. Apr 9 at 19:06
• FYI, a nice question should mention the sources, the link I've provided to you is the source of all information! Apr 9 at 19:07

G'day. LUKS stores multiple copies of the master key in what it terms key slots. Each key-slot contains an encrypted copy of the master key, and it can vary in its password-based KDF parameters (and even algorithms). The parameters and algorithms can sometimes be wildly different, giving rise to the circumstance where forcing one key-slot may be infeasible, while another is fundamentally weak. (Both will yield the master key.)

For LUKS1, the best option for the password-based KDF is PBKDF2. For LUKS2, an additional option exists to use Argon2, which gives you the potential to make it even harder to force a corresponding key-slot... but in all cases this will depend on the hardness parameters chosen, as well as the relative strength of the password. (In LUKS2 both options can exist in parallel.)

First, create a file to play with: fallocate -l 32M testing.encrypted.iso and xxd -c32 -l256 testing.encrypted.iso

Now turn it into a LUKS container, noting our two key-slots, both wildly different in terms of relative strength of the password-based KDF:

cryptsetup --type=luks2 --pbkdf=pbkdf2 --pbkdf-force-iterations=1000 --hash=sha384 --luks2-metadata-size=16k --luks2-keyslots-size=\$((512*1024)) luksFormat testing.encrypted.iso
cryptsetup --type=luks2 --pbkdf=argon2id --pbkdf-memory=1048576 --pbkdf-parallel=4 --pbkdf-force-iterations=8 --key-slot=5 luksAddKey testing.encrypted.iso


(In fact, our Argon2 key-slot might be derived from pwd awesomedud, while our PBKDF2 one might be 'rp6eq25dgt2orn/mnf. d4r6tj'n2uhpsqsbv.b!)

xxd -c32 -l256 testing.encrypted.iso
cryptsetup luksDump testing.encrypted.iso
cryptsetup luksDump --key-slot=5 --key-file=- --dump-master-key testing.encrypted.iso


Now clean up: shred -z testing.encrypted.iso and rm testing.encrypted.iso.

MASTER KEY RECOVERY

Once the slot key is derived from the password, the alogorithm must then retrieve the encrypted master key from the key-slot area in the header. From https://gitlab.com/cryptsetup/cryptsetup/-/wikis/LUKS-standard/on-disk-format.pdf, figure 5, p11, Fruhwirth '18: (Apologies for the screenshot, feel free to transcribe this text and replace...)

ANTI-FORENSIC SPLITTING

You may have noticed a function in the previous pseudo-code, AFmerge? My crude understanding is as follows: in the LUKS header, the individual bytes that make up each key-slot are interspersed (intermingled, interleaved) throughout the key-slot area of the header at a particular interval. Cumulatively, the key-slot area will contain n key slots, which are all interleaved. To recover the master key from any given key-slot, you need access to all of the blocks that contribute, otherwise the key won't be recovered.

The reason for this is to reduce the likelihood that any part of the key will be recovered after the user chooses to destroy the header. It is also the reason why you should consider more than one key-slot (ie. a high-entropy recovery key), but regardless, it's critical to keep a reliable backup(s) of the header (edit if you value the data within the container):

cryptsetup luksHeaderBackup --header-backup-file=testing.enciso.header testing.encrypted.iso


More detail on this here - https://clemens.endorphin.org/nmihde/nmihde-A4-ds.pdf, s5.2 p75+, Fruhwirth '05 - but fundamentally:

If the probability to destroy a certain block of data is 0 ≤ p ≤ 1, then the probability that the block survives is 1−p. Given a set of data consisting of n data items, the probability to erase the whole data set becomes worse since p^n becomes smaller as n becomes larger.

... the remaining task is to construct a data vector from an original data item and to make the destruction of a single vector part crucial for the survival of the whole information contained in the data vector.