Skip to content

Instantly share code, notes, and snippets.

@HacKanCuBa
Last active January 21, 2023 21:15
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save HacKanCuBa/b8565369de9620ba1eb24b75ee836857 to your computer and use it in GitHub Desktop.
Save HacKanCuBa/b8565369de9620ba1eb24b75ee836857 to your computer and use it in GitHub Desktop.
Encrypted SWAP hibernation in Debian 9+

Encrypted SWAP hibernation in Debian 9+

It took me about 6 hours to find out all of this, but after reading a ton of man pages, initram scripts, and bug reports, I got a working result that takes about 2' to set up...

The point is to have a SWAP partition encrypted with LUKS, and it should be decypted during boot.

When using SysV, initram hooks and scripts in Debian worked like a charm but then, Systemd came and it's not yet fully implemented so this kind of crap happens. Systemd's cryptsetup doesn't support parameters in /etc/crypttab so using a script there is ignored:

/* Options Debian's crypttab knows we don't:

    precheck=
    check=
    checkargs=
    noearly=
    loud=
    keyscript=
*/

Source.

Trying to decrypt SWAP partition via /etc/crypttab ends up usable but it can't resume from hibernation because Systemd tries to open it way too late, so the kernel gives up looking for a SWAP. To make it work, you need to pass parameters to the kernel, which Systemd will elegantly take.

Here's the whole deal (read everything first before starting doing things):

Let's say that your encrypted root partition is at /dev/disk/by-partlabel/root and your SWAP, /dev/disk/by-partlabel/swap (here I used GPT partition labels, you can also use UUID). I'll be naming cryptoroot and cryptoswap to the opened devices (accesible via /dev/mapper/...).

1- Edit or create /etc/initramfs-tools/conf.d/resume:
RESUME=/dev/mapper/cryptoswap

2- Remove references to SWAP in /etc/crypttab, i.e. a line like cryptoswap /dev/disk... /dev/urandom swap must be commented out or deleted.

3- Edit /etc/default/grub to set the kernel parameters for resume device and encryption: GRUB_CMDLINE_LINUX="resume=/dev/mapper/cryptoswap cryptopts=source=/dev/disk/by-partlabel/root,target=cryptoroot cryptopts=source=/dev/disk/by-partlabel/swap,target=cryptoswap"

4- Update initramfs and grub: sudo update-initramfs -u && sudo update-grub

Up to this point, you will need a password to open both devices, which is horribly anoying. However, a neat script, provided by cryptsetup, can be used: decypt_derived, which derives a key from an opened device (say, the root device). So, here's what you need to do (skip previous 3rd and 4th steps):

3- Add derived key to SWAP: sudo cryptsetup luksAddKey /dev/disk/by-partlabel/swap <(/lib/cryptsetup/scripts/decrypt_derived cryptoroot)
Verify that it worked: cryptsetup -v --test-passphrase -d <(/lib/cryptsetup/scripts/decrypt_derived cryptoroot) luksOpen /dev/disk/by-partlabel/swap

4- Edit /etc/default/grub: GRUB_CMDLINE_LINUX="resume=/dev/mapper/cryptoswap cryptopts=source=/dev/disk/by-partlabel/root,target=cryptoroot cryptopts=source=/dev/disk/by-partlabel/swap,target=cryptoswap,keyscript=/lib/cryptsetup/scripts/decrypt_derived,key=cryptoroot"
Double-check device and partition names!

5- Add an initram hook to copy decypt_derived binary to the initramfs, create the file /etc/initramfs-tools/hooks/cp_decrypt_derived:

#!/bin/sh
PREREQ=""
prereqs()
{
   echo "$PREREQ"
}

case $1 in
prereqs)
   prereqs
   exit 0
   ;;
esac

. /usr/share/initramfs-tools/hook-functions
# Begin real processing below this line

copy_exec /lib/cryptsetup/scripts/decrypt_derived /lib/cryptsetup/scripts/ >&2

6- Update initramfs and grub: sudo update-initramfs -u && sudo update-grub

That's it! You can hibernate with systemctl hibernate (no need for sudo). If you use a keyfile for the root partition, the same can be done by pointing the kernel to an appropriate keyscript that reads and pushes the key, and a hook that copies said script into initramfs.

Sources

@matschi-klickme
Copy link

matschi-klickme commented Dec 29, 2018

Thanks for the instructions!
At the second (4-): Is it possible that it should go

4- Edit /etc/default/grub: GRUB_CMDLINE_LINUX="resume=/dev/mapper/cryptoswap cryptopts=source=/dev/disk/by-partlabel/root,target=cryptoroot cryptopts=source=/dev/disk/by-partlabel/swap,target=cryptoswap,keyscript=/lib/cryptsetup/scripts/decrypt_derived,key=cryptoroot"

@rjl6789
Copy link

rjl6789 commented Jul 15, 2019

Hi, thanks for this information.
This is still relevant for Buster (v10, stable) but I found a couple of modifications were needed for the cryptopts command, in order for a smooth and error/warning free boot. Namely: hash= andcipher= - the relevant values being obtained from a cryptseup luksDump /dev/sdX e.g. the line might become:

GRUB_CMDLINE_LINUX="resume=/dev/mapper/cryptoswap cryptopts=source=/dev/disk/by-partlabel/root,target=cryptoroot,hash=sha256,cipher=aes-xts-plain64 cryptopts=source=/dev/disk/by-partlabel/swap,target=cryptoswap,keyscript=/lib/cryptsetup/scripts/decrypt_derived,key=cryptoroot,hash=sha256,cipher=aes-xts-plain64"

@HacKanCuBa
Copy link
Author

Hi, thanks for this information.
This is still relevant for Buster (v10, stable) but I found a couple of modifications were needed for the cryptopts command, in order for a smooth and error/warning free boot. Namely: hash= andcipher= - the relevant values being obtained from a cryptseup luksDump /dev/sdX e.g. the line might become:

GRUB_CMDLINE_LINUX="resume=/dev/mapper/cryptoswap cryptopts=source=/dev/disk/by-partlabel/root,target=cryptoroot,hash=sha256,cipher=aes-xts-plain64 cryptopts=source=/dev/disk/by-partlabel/swap,target=cryptoswap,keyscript=/lib/cryptsetup/scripts/decrypt_derived,key=cryptoroot,hash=sha256,cipher=aes-xts-plain64"

True that, thanks :D

@HacKanCuBa
Copy link
Author

Thanks for the instructions!
At the second (4-): Is it possible that it should go

4- Edit /etc/default/grub: GRUB_CMDLINE_LINUX="resume=/dev/mapper/cryptoswap cryptopts=source=/dev/disk/by-partlabel/root,target=cryptoroot cryptopts=source=/dev/disk/by-partlabel/swap,target=cryptoswap,keyscript=/lib/cryptsetup/scripts/decrypt_derived,key=cryptoroot"

Fixed, thanks!

@HydraGene
Copy link

You seem to have set up root and swap on a different encrypted partition, is this correct? Does this also work with LVM inside an encrypted partition? Wouldn't that solve the issue with entering passwords for both devices, since there is only one encrypted device?

@rjl6789
Copy link

rjl6789 commented Nov 7, 2019

Hi,

Yes - swap and root on different partitions. For me (can't speak for @HacKanCuBa ). I wanted a separated swap partition because I was sharing the swap between multiple distros on the same drive and also my root was zfsonlinux, which at the time didn't play nice with native swap encryption. I believe lvm on luks will also work (swap, root etc being on the lvm) with this method, but it may not be necessary as I think lvm on luks is the "standard" encyption method and just setting your RESUME variable and including the drive in crypttab would tke care of things (assuming you have lvm2 installed and the module loaded at boot which I think deb takes care of for you). Anyway - haven't tried that for a while - pretty sure there are lots of tutorial on this. I'm no longer using this boot method, but incase you're interested... how about this for one massive mouthful of a boot line (fyi - I'd also encrypted the boot partition on this machine!):

GRUB_CMDLINE_LINUX="root=ZFS=rpool/ROOT/debian cryptopts=source=/dev/disk/by-partlabel/scratch,target=ZFSbuster,hash=sha256,size=512,cipher=aes-xts-plain64,keyscript=/bin/cat,key=/etc/luks-keys/zfsBuster cryptopts=source=/dev/disk/by-partlabel/bootC,target=cboot,hash=sha256,size=512,cipher=aes-xts-plain64,keyscript=/bin/cat,key=/etc/luks-keys/zfsBoot cryptopts=source=/dev/disk/by-partlabel/swap,target=cryptSWAP,keyscript=/bin/cat,key=/etc/luks-keys/swap,hash=sha256,size=512,cipher=aes-xts-plain64 resume=/dev/mapper/cryptSWAP acpi_mask_gpe=0x06"

@HacKanCuBa
Copy link
Author

You seem to have set up root and swap on a different encrypted partition, is this correct? Does this also work with LVM inside an encrypted partition? Wouldn't that solve the issue with entering passwords for both devices, since there is only one encrypted device?

Yes, I prefer separated partitions (for no reason really, maybe simplicity) but it does also work on a single LUKS with LVM on top :)

@HacKanCuBa
Copy link
Author

Hi,

Yes - swap and root on different partitions. For me (can't speak for @HacKanCuBa ). I wanted a separated swap partition because I was sharing the swap between multiple distros on the same drive and also my root was zfsonlinux, which at the time didn't play nice with native swap encryption. I believe lvm on luks will also work (swap, root etc being on the lvm) with this method, but it may not be necessary as I think lvm on luks is the "standard" encyption method and just setting your RESUME variable and including the drive in crypttab would tke care of things (assuming you have lvm2 installed and the module loaded at boot which I think deb takes care of for you). Anyway - haven't tried that for a while - pretty sure there are lots of tutorial on this. I'm no longer using this boot method, but incase you're interested... how about this for one massive mouthful of a boot line (fyi - I'd also encrypted the boot partition on this machine!):

GRUB_CMDLINE_LINUX="root=ZFS=rpool/ROOT/debian cryptopts=source=/dev/disk/by-partlabel/scratch,target=ZFSbuster,hash=sha256,size=512,cipher=aes-xts-plain64,keyscript=/bin/cat,key=/etc/luks-keys/zfsBuster cryptopts=source=/dev/disk/by-partlabel/bootC,target=cboot,hash=sha256,size=512,cipher=aes-xts-plain64,keyscript=/bin/cat,key=/etc/luks-keys/zfsBoot cryptopts=source=/dev/disk/by-partlabel/swap,target=cryptSWAP,keyscript=/bin/cat,key=/etc/luks-keys/swap,hash=sha256,size=512,cipher=aes-xts-plain64 resume=/dev/mapper/cryptSWAP acpi_mask_gpe=0x06"

Nice!! BTW, how do you encrypt boot? I haven't tried yet.

@rjl6789
Copy link

rjl6789 commented Nov 8, 2019

Hi, encrypting boot is actually fairly simple, setting it so you don't have to enter passwords multiple times is a little trickier - involves injecting your passwords into the initram (the initram now resides in an encrypted boot partition.) Anyway, the arch wiki describes the process (albeit for arch). I also took inspiration from: here and here But basically you encypt boot with luks1 (not luks2 as grub doesn't support luks2 - as of date of writing) and set the GRUB_ENABLE_CRYPTODISK=y in the /etc/default/grub file. Getting the keys into the initram requires creating some hooks in /etc/initramfs-tools/hooks - I've put a gist together of my config files. I can't be sure that I haven't missed one! encrypted-swap-hibernation-boot-gist

@HacKanCuBa
Copy link
Author

Hi, encrypting boot is actually fairly simple, setting it so you don't have to enter passwords multiple times is a little trickier - involves injecting your passwords into the initram (the initram now resides in an encrypted boot partition.) Anyway, the arch wiki describes the process (albeit for arch). I also took inspiration from: here and here But basically you encypt boot with luks1 (not luks2 as grub doesn't support luks2 - as of date of writing) and set the GRUB_ENABLE_CRYPTODISK=y in the /etc/default/grub file. Getting the keys into the initram requires creating some hooks in /etc/initramfs-tools/hooks - I've put a gist together of my config files. I can't be sure that I haven't missed one! encrypted-swap-hibernation-boot-gist

Excellent, thanks!

@bohni
Copy link

bohni commented Aug 24, 2020

decrypt_derived returns on my Debian Buster (10.5) Live ISO (standard) /lib/cryptsetup/scripts/decrypt_derived: device crypt_root uses the kernel keyring

I did:

user@debian:~$ sudo cryptsetup -c aes-xts-plain64 -s 512 luksFormat /dev/sda2
user@debian:~$ sudo cryptsetup luksOpen /dev/sda2 crypt_root
user@debian:~$ sudo mkfs.ext4 -L ROOT /dev/mapper/crypt_root
user@debian:~$ sudo /lib/cryptsetup/scripts/decrypt_derived crypt_root

Can someone confirm this?

Such a key is somehow derived, but not as expected!

Using a random key without the script should work also

user@debian:~$ sudo dd if=/dev/urandom of=/root/keyfile bs=1024 count=4

@HacKanCuBa
Copy link
Author

HacKanCuBa commented Aug 25, 2020

decrypt_derived returns on my Debian Buster (10.5) Live ISO (standard) /lib/cryptsetup/scripts/decrypt_derived: device crypt_root uses the kernel keyring

I did:

user@debian:~$ sudo cryptsetup -c aes-xts-plain64 -s 512 luksFormat /dev/sda2
user@debian:~$ sudo cryptsetup luksOpen /dev/sda2 crypt_root
user@debian:~$ sudo mkfs.ext4 -L ROOT /dev/mapper/crypt_root
user@debian:~$ sudo /lib/cryptsetup/scripts/decrypt_derived crypt_root

Can someone confirm this?

Such a key is somehow derived, but not as expected!

Using a random key without the script should work also

user@debian:~$ sudo dd if=/dev/urandom of=/root/keyfile bs=1024 count=4

I'm on debian testing (bullseye) and running sudo /lib/cryptsetup/scripts/decrypt_derived crypt_root returns an hex key so I'm not sure where is the issue (although I'm not on a LIVE system, could that be? I'm not sure...)

@bohni
Copy link

bohni commented Aug 26, 2020

https://launchpad.net/debian/+source/cryptsetup/+changelog

debian/README.initramfs: Mention that keyscript=decrypt_derived normally
won't work with LUKS2 sources. (The volume key of LUKS2 devices is by
default offloaded to the kernel keyring service, hence not readable by
userspace.) Since 2:2.0.3-5 the keyscript loudly fails on such sources.

Are you using LUKS1?

@HacKanCuBa
Copy link
Author

HacKanCuBa commented Aug 27, 2020

https://launchpad.net/debian/+source/cryptsetup/+changelog

debian/README.initramfs: Mention that keyscript=decrypt_derived normally
won't work with LUKS2 sources. (The volume key of LUKS2 devices is by
default offloaded to the kernel keyring service, hence not readable by
userspace.) Since 2:2.0.3-5 the keyscript loudly fails on such sources.

Are you using LUKS1?

Good point, yes. I haven't migrated yet (I read its possible but I don't dare).

You should then not use the decrypt_derived script but a keyfile stored inside the root encrypted partition. I dare say it's the same security level and convenience... as long as you protect said key w/ proper permissions!

Simply create a key with dd if=/dev/urandom of=/root/.swap.key bs=1k count=4 iflag=fullblock && chmod 0400 .swap.key, add it to the swap encrypted device using cryptsetup's -k argument and add the description to /etc/crypttab: cryptoswap /dev/disk/by-partlabel/swap /root/.swap.key luks,tries=1
The rest of the guide remains the same.

@wzhd
Copy link

wzhd commented Aug 18, 2021

Hi, encrypting boot is actually fairly simple, setting it so you don't have to enter passwords multiple times is a little trickier - involves injecting your passwords into the initram (the initram now resides in an encrypted boot partition.) Anyway, the arch wiki describes the process (albeit for arch). I also took inspiration from: here and here But basically you encypt boot with luks1 (not luks2 as grub doesn't support luks2 - as of date of writing) and set the GRUB_ENABLE_CRYPTODISK=y in the /etc/default/grub file. Getting the keys into the initram requires creating some hooks in /etc/initramfs-tools/hooks - I've put a gist together of my config files. I can't be sure that I haven't missed one! encrypted-swap-hibernation-boot-gist

Just tried it in 2021 with Debian, it seems to be even simpler with crypttab, after setting up encrypted boot and adding the key file to initramfs for decrypting /, using encrypted swap for hibernation is as simple as adding an additional key file for the encrypted swap to initramfs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment