Setting up LUKS on a new SSD
I’m not a linux OS expert even though I’ve been using linux for over twenty years (that said, I’d say that most anyone who installs and uses linux has to have a good amount of technical knowledge). It wasn’t until somewhat recently that I realized I really should have Full Disk Encryption (FDE) on the NVMe SSD drives that I’ve been installing on the last few computers builds I’ve done (and, for sure, it’s wise to use some encryption if there’s any private or important information on a drive–a simple delete of files doesn’t actually overwrite the data on the disk of deleted files usually; this advice applies regardless of NVMe or SATA form-factor). So, as I’ve been building newer systems and adding drives, I’ve been setting them up with with FDE using LUKS (Linux Unified Key Setup), and have been quite happy with the simplicity and the security of it all.
This article goes over the steps needed to set up LUKS on a new, non-boot SSD drive–in this case it’s a new 1TB Crucial MX500 2.5″ SATA drive.
I’m going to label Step 0 as “install the physical SSD drive in the computer” and not go into any details at all there. For my setup, I just plugged it into a SATA connector and confirmed via UEFI setup screens that it’s detectable at the hardware level.
Step 1 is to locate the block device in linux itself. While a non-root user can do a few of these commands, you really need to be root, and I tend to just do a simple sudo bash
and then do all the commands while in the root shell. So open up a shell:
[scott] $ sudo bash
[root] #
and list block devices:
[root] # lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 931.5G 0 disk
sr0 11:0 1 1024M 0 rom
zram0 252:0 0 8G 0 disk [SWAP]
nvme0n1 259:0 0 1.8T 0 disk
├─nvme0n1p1 259:1 0 600M 0 part /boot/efi
├─nvme0n1p2 259:2 0 1G 0 part /boot
└─nvme0n1p3 259:3 0 1.8T 0 part
└─luks-822c7f1b-7d42-48ac-b2f6-3758ceecc5b3 253:0 0 1.8T 0 crypt /home
/
nvme1n1 259:4 0 931.5G 0 disk
└─nvme1n1p1 259:5 0 931.5G 0 part
└─crucialx 253:1 0 931.5G 0 crypt /crucial
You can see the already-setup FDE on crucialx
and luks-822c7f1b-7d42-48ac-b2f6-3758ceecc5b3
–both have type crypt
. The new MX500 is a SATA drive and it shows up as sda
–not even partitioned, so the next step is to partition it.
Note also that there’s another way to list the new device: fdisk -l
, one advantage of which is that it also displays the full /dev path and the drive model info:
[root] # fdisk -l
Disk /dev/sda: 931.51 GiB, 1000204886016 bytes, 1953525168 sectors
Disk model: CT1000MX500SSD1
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
...
(output has been truncated to show just the new drive).
Partitioning can be done with fdisk
(or, if the device is more than 2TB, the GNU utility parted
needs to be used) and the steps for this are to create a new primary partition (assuming all of the drive is to be part of the FDE process), write out the partition table, and then make sure the OS uses this new information:
[root@titan scott]# fdisk /dev/sda
Welcome to fdisk (util-linux 2.38).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.
Device does not contain a recognized partition table.
Created a new DOS disklabel with disk identifier 0x0cbc7bb1.
Command (m for help): n
Partition type
p primary (0 primary, 0 extended, 4 free)
e extended (container for logical partitions)
Select (default p): p
Partition number (1-4, default 1):
First sector (2048-1953525167, default 2048):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-1953525167, default 1953525167):
Created a new partition 1 of type 'Linux' and of size 931.5 GiB.
Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.
[root] # partprobe
Use lsblk
to see the new partition:
[root@titan scott]# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 931.5G 0 disk
└─sda1 8:1 0 931.5G 0 part
The new partition is /dev/sda1
. Now it’s time for LUKS setup.
LUKS setup really requires that you understand a bit of the overall model of it before jumping in. The high level idea of how it operates here is that LUKS sits between the filesystem (such as btrfs
or ext4
) and the raw block device (like /dev/sda1
) doing the encryption of data written and the decryption of data read. The LUKS command that manages this in-between part is cryptsetup
and the basic operations needed for this management are as follows:
cryptsetup luksFormat
: formats a raw block device so it can be used with the LUKS systemcryptsetup luksOpen
: opens up a formatted LUKS block device and assigns a logical ID to idcryptsetup luksClose
: closes a device opened up with luksOpencryptsetup luksAddKey
: adds a new encryption key to the devicecryptsetup luksDump
: lists the encryption key info on the device
As LUKS uses AES256 encryption, the question of how the keys are stored comes up. It turns out that there’s a single AES256 master key and it’s encrypted by secondary keys. LUKS provides 8 “slots” (slot storage is created during the luksFormat
operation and are filled via the luksAddKey
command) to store the keys used to decrypt the master key. The keys stored in these 8 slots can be created via a passphrase that’s stretched into the required number of bits (LUKS version 2 uses Argon2 for that), or via a good secure random number (bit) generator. Both of these methods will be used so that the FDE is automounted during bootup.
The usefulness of the 8 slots is that you can specify a passphrase (or two) while having another secure random key of 256 bits that can be used for scripts or during bootup, all of which will allow decryption of the master key that is used for actual encryption and decryption. (And, having multiple slots allows multiple people to unlock, say, a boot drive that has FDE without sharing passwords.)
Before jumping into the actual operations to use LUKS, it’s important to understand the logical ID that is required for some operations: this logical ID is the “handle” to a block device/partition that’s been opened by LUKS and it shows up in the file system (under /dev/mapper/
). It must be unique across mapped/opened block devices and I’ve found it useful to have a convention of appending x
to a useful name to indicate that it’s encrypted, like mx500x
.
Step 2 is to put it all together:
[root]# cryptsetup luksFormat /dev/sda1
WARNING!
========
This will overwrite data on /dev/sda1 irrevocably.
Are you sure? (Type 'yes' in capital letters): YES
Enter passphrase for /dev/sda1:
Verify passphrase:
[root]# cryptsetup luksOpen /dev/sda1 mx500x
Enter passphrase for /dev/sda1:
[root]# ls -l /dev/mapper/
total 0
crw-------. 1 root root 10, 236 Mar 5 06:50 control
lrwxrwxrwx. 1 root root 7 Mar 5 06:50 crucialx -> ../dm-1
lrwxrwxrwx. 1 root root 7 Mar 5 06:50 luks-822c7f1b-7d42-48ac-b2f6-3758ceecc5b3 -> ../dm-0
lrwxrwxrwx. 1 root root 7 Mar 5 07:53 mx500x -> ../dm-2
[root]#
For the passphrase, choose a secure (long) one and do not forget it as you will lose access to all data on the FDE drive (well, not 100% true if you set things up for automount by using the secure random key generated during that process). And to show the effect of closing a logical ID:
[root]# cryptsetup luksClose mx500x
[root]# ls -l /dev/mapper/
total 0
crw-------. 1 root root 10, 236 Mar 5 06:50 control
lrwxrwxrwx. 1 root root 7 Mar 5 06:50 crucialx -> ../dm-1
lrwxrwxrwx. 1 root root 7 Mar 5 06:50 luks-822c7f1b-7d42-48ac-b2f6-3758ceecc5b3 -> ../dm-0
[root]#
At this point all the LUKS setup has been done and it’s time for Step 4 where the drive is formatted. The device does need to be opened (via cryptsetup luksOpen
) and then it’s easily formatted; I have recently been using btrfs
instead of ext4
, so the following is what I did:
[root]# cryptsetup luksOpen /dev/sda1 mx500x
Enter passphrase for /dev/sda1:
[root]# mkfs.btrfs /dev/mapper/mx500x
btrfs-progs v6.1.3
See http://btrfs.wiki.kernel.org for more information.
NOTE: several default settings have changed in version 5.15, please make sure
this does not affect your deployments:
- DUP for metadata (-m dup)
- enabled no-holes (-O no-holes)
- enabled free-space-tree (-R free-space-tree)
Label: (null)
UUID: 8bc72d72-09f9-478a-b21e-dc0a76bf7eda
Node size: 16384
Sector size: 4096
Filesystem size: 931.50GiB
Block group profiles:
Data: single 8.00MiB
Metadata: DUP 1.00GiB
System: DUP 8.00MiB
SSD detected: yes
Zoned device: no
Incompat features: extref, skinny-metadata, no-holes
Runtime features: free-space-tree
Checksum: crc32c
Number of devices: 1
Devices:
ID SIZE PATH
1 931.50GiB /dev/mapper/mx500x
and then to mount it:
[root]# mkdir /mx500
[root]# mount /dev/mapper/mx500x /mx500/
[root]# df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 4.0M 0 4.0M 0% /dev
tmpfs 31G 736M 31G 3% /dev/shm
tmpfs 13G 2.4M 13G 1% /run
/dev/dm-0 1.9T 267G 1.6T 15% /
tmpfs 31G 45M 31G 1% /tmp
/dev/dm-0 1.9T 267G 1.6T 15% /home
/dev/nvme0n1p2 974M 318M 589M 36% /boot
/dev/nvme0n1p1 599M 18M 582M 3% /boot/efi
/dev/mapper/crucialx 932G 367G 565G 40% /crucial
tmpfs 6.2G 2.4M 6.2G 1% /run/user/1000
/dev/mapper/mx500x 932G 3.8M 930G 1% /mx500
At this point /mx500
is fully usable and all data written to it will be encrypted.
For convenience I (strongly) suggest setting it up so that the FDE drive is automounted during OS bootup. This can be a bit more complicated than desired if you have more than one NVMe drive, as the assignment of /dev
drive is dependent on the scan done during bootup and isn’t 100% predictable (as I found out later). If you do have more than one NVMe drive, read the section lower down also.
Setting up automount during bootup requires the following:
- create a secure random key, saved to a protected file
- edit
/etc/crypttab
to add a line that will do an automatic open on the device during bootup - edit
/etc/fstab
to add a line that will do an automatic mount of the opened device
I’ve put the the secure random key file under /root
directory as that’s protected and isn’t likely to be mistakenly deleted or modified. It can be created like this:
[root]# dd if=/dev/random bs=32 count=1 of=/root/lukskey.mx500x
1+0 records in
1+0 records out
32 bytes copied, 5.4303e-05 s, 589 kB/s
If you haven’t used dd
(“data duplicator”) before, what that command is doing is copying 32 bytes (bs=32 count=1
: 1 block of size 32) from /dev/random
to /root/lukskey.mx500x
. It’s a very useful command (but can be dangerous if you don’t know what you’re doing as it can and will write to raw block devices (and totally destroy the data on them).
Now the new 32-byte key must be added to one of the 8 slots:
[root] cryptsetup luksAddKey /dev/sda1 /root/lukskey.mx500x
Enter any existing passphrase:
[root]
Once you’ve created the key file, add a line like the following to /etc/crypttab
(which automates the luksOpen
done manually above):
mx500x /dev/sda1 /root/lukskey.mx500x
and finally a line like the following can be added to /etc/fstab
(which automates the mount
done manually above):
/dev/mapper/mx500x /mx500 btrfs defaults 0 0
And that’s all the setup needed if you don’t have more than one NVMe drive. At this point, double-check paths that were entered into /etc/*tab
files as errors here will cause bootup problems. Reboot the computer to test it all out to make sure everything gets automounted.
If you have more than one NVMe drive, then it’s necessary to use the UUID of the partition as the actual drive number (e.g., the 0
in /dev/nvme0n1
) is assigned during a boot-time scan of devices. And there are actually two UUIDs that need to be used: one for use in /etc/crypttab
and one for use in /etc/fstab
. In order to get the UUIDs, use the following command (the important UUIDs are in bold):
$ sudo lsblk -o +name,mountpoint,uuid
[root@titan scott]# lsblk -o +name,uuid
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS NAME UUID
sda 8:0 0 931.5G 0 disk sda
└─sda1 8:1 0 931.5G 0 part sda1 561e1265-0150-437c-b979-4bf4a5a1ff49
└─mx1000x 253:1 0 931.5G 0 crypt /mx1000 mx1000x 8bc72d72-09f9-478a-b21e-dc0a76bf7eda
sdb 8:16 0 3.6T 0 disk sdb
└─sdb1 8:17 0 3.6T 0 part sdb1 d8347463-e812-4293-ac9b-c9dae64d2992
└─mx4000x 253:2 0 3.6T 0 crypt /mx4000 mx4000x 331eef2c-4ef9-47d7-a856-8ef28e928a90
zram0 252:0 0 8G 0 disk [SWAP] zram0
nvme0n1 259:0 0 1.8T 0 disk nvme0n1
├─nvme0n1p1 259:1 0 600M 0 part /boot/efi nvme0n1p1 B4CD-57EE
├─nvme0n1p2 259:2 0 1G 0 part /boot nvme0n1p2 c0d6df9c-9664-4174-99a3-4c207a52f318
└─nvme0n1p3 259:3 0 1.8T 0 part nvme0n1p3 822c7f1b-7d42-48ac-b2f6-3758ceecc5b3
└─luks-822c7f1b-7d42-48ac-b2f6-3758ceecc5b3 253:0 0 1.8T 0 crypt /home luks-822c7f1b-7d42-48ac-b2f6-3758ceecc5b3
66cead1d-0664-4f46-99a8-0b3ddb8891f5
/
nvme1n1 259:4 0 931.5G 0 disk nvme1n1
└─nvme1n1p1 259:5 0 931.5G 0 part nvme1n1p1 40818881-4df8-45a2-8c34-390e1faaca5d
└─luks-40818881-4df8-45a2-8c34-390e1faaca5d 253:3 0 931.5G 0 crypt /crucial luks-40818881-4df8-45a2-8c34-390e1faaca5d
2686e2f5-4a18-4be3-9340-79296349a7af
The first UUID (that’s associated with the crypt
type partition of the device) is used in /etc/crypttab
like so:
luks-40818881-4df8-45a2-8c34-390e1faaca5d UUID=40818881-4df8-45a2-8c34-390e1faaca5d /root/lukskey.crucialx
and the second UUID (that’s associated with the mounted partition) is used in /etc/fstab
like so:
UUID=2686e2f5-4a18-4be3-9340-79296349a7af /crucial btrfs defaults 0 0
(I haven’t verified the following but I think that it’s necessary to do a luksOpen
and mount
of the partition to get the second UUID.)