That's where new cryptsetup-reencrypt tool from cryptsetup 1.5 can help.
What it is about...
With LUKS device, you can easily specify encryption parameters and these are stored to on-disk header.LUKS allows you to easily set up multiple passphrases to unlock device and easily change these unlocking passphrases later.
But sometimes, even if it should be rare operation, you need to change other attribute like volume encryption key itself or used cipher.
With new cryptsetup-reencrypt you can run required re-encryption in-place and almost automatically.
Cryptsetup-reencrypt is available in Fedora (in Fedora 17 updates and Rawhide/F18), for other distro you need to get cryptsetup 1.5.x sources and recompile with option --enable-cryptsetup-reencrypt.
Important note: it is experimental tool and it doesn't use backup copies while reencryption is running so in the case of hw or power failure you will lose data.
It can restart after intentional suspend though (but you have to preserve temporary files with header).
What cryptsetup-reencrypt tool allows
- change arbitrary parameter of LUKS encryption
- volume (master) key
- key size
- cipher, cipher mode, IV
- LUKS header hash algorithm
- add new LUKS header (switch device from unencrypted to encrypted)
- shift ciphertext data (for new header or to fix alignment)
- volume (master) key is always regenerated during re-encryption.
- some operations (extending key size, adding new encryption layer) requires shift of data (thus some unused space is required in the end of device)
- the tool requires inactive LUKS device (offline reencryption)
- devices with detached LUKS headers are not yet supported
Why offline?
The former idea was to run reencryption in-kernel allowing active device use.While this feature still can be implemented in future, utilizing existing libcryptsetup functions provides solution for most of the use cases.
Also it doesn't require any kernel update and no changes in LUKS on-disk format (the whole solution is quite easily usable in old Linux distro releases - basically you just need to compile cryptsetup 1.5.0).
How it is works?
LUKS device looks like this:| LUKS header |(padding)| data (ciphertext) ... |
Cryptsetup-reencrypt tool simply creates a new temporary LUKS header and continuously read (with old header parameters) and writes (with new header parameters) data area, using simple metadata header to log progress.
The headers are stored in files and original header on-disk is during encryption marked unusable (known signature is overwritten).
So during re-encryption you have temporarily these files:
- LUKS-<UUID>.log - re-encryption log
- LUKS-<UUID>.org - old temporary LUKS header
- LUKS-<UUID>.new - new temporary LUKS header
If the re-encryption is suspended (e.g. by ctrl+c) it will automatically restart if these files are present in current directory.
You will need to enter all keyslot passphrases (or remove some keyslots in advance).
After re-encryption new header is restored to original device and temporary files are deleted.
Example: Change of cipher (and volume key)
Let's change cipher and key on devie /dev/sde1...# cryptsetup-reencrypt -c twofish-xts-plain64 /dev/sde1
Enter LUKS passphrase for key slot 0:
Enter LUKS passphrase for key slot 1:
Progress: 100.0%, ETA 00:00, 4094 MiB written, speed 25.6 MiB/s
If re-encryption is slow, there are several options like block size and directio which can help - this depends on hw. e.g. here we use 32MiB block and direct-io.
See man cryptsetup-reencrypt for description.
# cryptsetup-reencrypt -B 32 --use-directio /dev/sde1
Example: Switch unencrypted installation to encrypted
To switch device to LUKS from plain filesystem or LVM PV you need to perform these steps:- reduce device (and fs) to create space for LUKS header and keyslots (alternatively extend underlying device)
- create new luks header and re-encrypt data
- fix system config (crypttab, fstab, etc) to activate modified device stack
- generate new initramfs to include new tools (for root device)
- fix kernel boot parameters (for root device)
Note: This example is intentionally complex.
In fact, nobody should use it (re-installation is much simpler).
But it is nice demonstration how storage stack operations looks like...
I. Preparation
- start with F17 minimal installation, use ext4, use LVM, use updates repo
I will use "f17t" as system name -> vg_f17t LVM VG. - boot and verify that system is fully updated
- install needed tools
# yum install cryptsetup cryptsetup-reencrypt - regenerate initramfs to include tools we will use in ramdisk
Do NOT forget quotes below or you will overwrite lsblk by ramdisk image!
# dracut -f -I "/sbin/cryptsetup /sbin/cryptsetup-reencrypt /sbin/resize2fs /bin/lsblk"
II. boot into dracut shell and manually do all what's needed
- in grub menu, edit parameters and add rdbreak=pre-mount
- boot - you should get dracut shell environment
- you should see something like this storage space
# lsblk -o name,fstype
sda
├─sda1 ext4
└─sda2 LVM2_member
├─vg_f17t-lv_swap (dm-0) swap
└─vg_f17t-lv_root (dm-1) ext4
Boot partition (sda1) remains, sda2 (lvm PV) will be shifted and LUKS header added (and encrypted).
Free 4M is enough for keyslots and header. Unfortunately, it is LVM so we need to check extent size. (You can allocate space only in multiple of extents.)
- now create some space for LUKS header
The whole goal of this exercise is to free last 4M space of /dev/sda2. - Be sure we have lvm volumes activated
# lvm vgchange -a y
- we need to free last extents on device, need to figure out which LV need to be resized (swap or root?)
# lvm vgs -o name,extent_size
VG Ext
vg_f17t 32.00m
So extent is 32MiB, so we have to reduce last LV of 1 extent/32M.
- which LV is allocated there?
# lvm lvs -o lv_name,seg_pe_ranges
LV PE Ranges
lv_root /dev/sda2:63-239
lv_swap /dev/sda2:0-62
The number is start-end extent, so last extent is allocated by lv_root.
Nice, another complication with filesystem. - Well, let's simplify it: resize fs to minimum.
(I expect it will be always enough, if not, your system is doomed anyway ;-) You will get used blocks in e2fsck output.
# e2fsck -f /dev/vg_f17t/lv_root
# resize2fs -M /dev/vg_f17t/lv_root
...
The filesystem on /dev/vg_f17t/lv_root is now <n> blocks long.
- resize LV - reduce one extent, note "-1" argument and lower case "l" for extents.
LVM uses locking which is not available in dracut by default. I will use trick to overwrite locking type on commandline. Alternatively you can edit /etc/lvm/lvm.conf directly.
# lvm lvresize -l-1 vg_f17t/lv_root --config 'global {locking_type=1}'
...
Logical volume lv_root successfully resized.
- You can now verify it with lvs -o+seg_pe_ranges
- resize fs back to full device (no size specified == size of LV)
# resize2fs /dev/vg_f17t/lv_root
- Let's ignore pvresize now (we will do it later - out of device space is unused).
- deactivate whole VG
# lvm vgchange -a n
- reencrypt with data shift and adding new header
# cryptsetup-reencrypt -N --reduce-device-size 4MiB /dev/sda2
Enter new LUKS passphrase:
Progress: 100.0%, ETA 00:00, 7687 MiB written, speed 33.9 MiB/s
III. Continue to boot system manually, now with LUKS
- activate LUKS device.
Note I will use more friendly name "sda2_crypt" instead of Fedora ugly default "luks-UUID" name.
# cryptsetup luksOpen /dev/sda2 sda2_crypt
- activate LVs
# lvm vgchange -a y
- continue boot - just use ctrl+d and dracut should bring system up
Do not reboot yet.
IV. Fix system configuration
- Now you should see LUKS in storage stack
# lsblk -o name,fstype
sda
├─sda1 ext4
└─sda2 crypto_LUKS
└─sda2_crypt (dm-0) LVM2_member
├─vg_f17t-lv_swap (dm-1) swap
└─vg_f17t-lv_root (dm-2) ext4
- fix PV device size in lvm metadata
# pvresize /dev/mapper/sda2_crypt
- get UUID of new LUKS
# blkid /dev/sda2
- edit, or create new /etc/cryptab and add line
sda2_crypt UUID=<LUKS UUID above> none
- edit grub to not ignore LUKS devices
remove rd.luks=0 in /etc/default/grub
Precisely we should add UUID of device which contains root fs, but default is activate all.
- regenerate grub.conf
# grub2-mkconfig -o /boot/grub2/grub.cfg
- regenerate dracut initramfs
# dracut -f
V. Reboot & profit.
- You just get advanced LVM training for free :-)
Example: Regenerate key
Now more realistic use cases
- you have old encrypted laptop and cannot reinstall it for now but you are not sure if old user had some LUKS header metadata backups which allows unlocking device even if passphrase was changed
- you have an image for standard install of your company notebooks and want to simplify installation by dd of this image and personalize it on first boot (regenerate key, UUIDs, passwords).
It is pretty stupid to have the same volume key on all laptops, isn't it? :-) - for some reason (security policy, etc) you just want to regenerate volume key
Simply run cryptsetup-reencrypt on the LUKS device (for system device use live-CD) as in the first example.
There is also proof-of-concept dracut module for reencryption in initramfs (but it is very tricky and it is doing a lot of presumptions about the system like stable kernel name - laptop with one disk - etc. But maybe it can be useful for someone.)
Please be sure your kernel RNG is properly seeded before automatic reencryption. (Also see --use-random/--use-urandom options.)
Warning & Co.
Note that LUKS re-encrypt tool is experimental and it is not resistant to media errors and power failure. Also if you stop re-encryption and remove temporary files, you lost the whole device.Never ever use it without reliable backup.
This comment has been removed by the author.
ReplyDeleteI was using russian ecryption instruction http://sysadmin.te.ua/tag/luks
ReplyDeleteGreat Information :)
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteThe general guidelines of this post work well - however, cryptsetup-reencrypt will no longer work at the dracut# prompt and will produce errors at this point. This is seen with both RHEL 7.5 and 7.6.
ReplyDelete