When individuals speak of doing “big fist pumps” after their Arch installs successfully boot it can be hard to contain one’s curiosity about the path that led them there. But it’s hard to understand until you try it yourself.
This was my journey to first install. It was an encrypted one.
In this tutorial I will show you how I repurposed an old MacBook Pro to double-down on privacy using deniable encryption and how you can too.
In cryptography and steganography, deniable encryption is encryption that allows its users to convincingly deny the fact that the data is encrypted or, assuming that the data is obviously encrypted, its users can convincingly deny that they are able to decrypt it. Source: cryptography.fandom.com.
While you could deniably encrypt any system the mid-2014 Mac is special because it had a battery recall. The recall repair work gave owners the perfect excuse to shred their devices using crypto-randomness prior to service. And in that randomness it’s possible to install and run an invisible operating system.
Here’s a gist of the setup:
- Deniable encryption on Apple SSD[1,2,3,4]
- Arch Linux full offline installation
- Ciphertext and unlock keys kept separate
- 2FA via pre-boot password authentication
- Large, easy to read font for the console
- Support for multiple encrypted disks
And some technical details:
- LUKS2 with detached LUKS header
- LVM on LUKS with 2 logical volumes:
- 1 logical /root (ext4)
- 1 logical /home (ext4)
- UEFI boot using systemd-boot
- Dedicated USB header backup partition
Are there reasons not to use deniable encryption? Gentoo mentions technical ones. What they don’t stay, however, is that it’s possible you may be breaking laws in some countries. But I won’t tell if you don’t.
Requirements
You will need the following hardware to complete setup:
- 1 2GB+ USB flash drive with archiso installed usb1
- 1 256MB+ USB flash drive to boot the system usb2
- 1 mid-2014 MacBook Pro (MacBookPro11,5) sda
Expect a deep discount on the Mac if its battery has yet to be replaced or if the previous owner cannot prove the recall work was performed on the machine.
Breaks
During this tutorial you may power down your machine after completing any section, reboot and pick up where you left off using this script:
Expand to view script
test -b /dev/sdd1 && \
mkdir -p /mnt/{usb2,vg1/home} && \
mount -r /dev/sdd1 /mnt/usb2 && \
test -f /mnt/usb2/ob1 && \
cryptsetup open --header /mnt/usb2/ob1 --type luks /dev/sda c1 && \
sleep 1 && \
mount /dev/mapper/vg1-root /mnt/vg1 && \
mount /dev/mapper/vg1-home /mnt/vg1/home && \
test -b /dev/sdd2 && \
mkdir -p /mnt/boot && \
mount /dev/sdd2 /mnt/boot
Verify with lsblk -o NAME,FSTYPE,TYPE,MOUNTPOINT. You should see output like:
Expand to view output
NAME FSTYPE TYPE MOUNTPOINT
loop0 squashfs loop
sda disk
└─c1 LVM2_member crypt
├──vg1-root ext4 lvm /mnt/vg1
└──vg1-home ext4 lvm /mnt/vg1/home
sdb iso9660 disk
├──sdb1 iso9660 part /run/archiso/bootmnt
└──sdb2 vfat part
sdd disk
├──sdd1 vfat part /mnt/usb2
└──sdd2 vfat part /mnt/boot
Note: Partition named sdd2 is created during Booting and should not appear beforehand unless usb2 already contained it prior to starting tutorial.
Assumes you have completed Setup and reinserted usb2 after booting Archiso.
Setup
First boot from usb1 and erase the Apple SSD. One of the fastest, more secure ways to do this is to perform a zero-write inside a DMCrypt container.
Then connect and mount usb2, and use cryptsetup to prepare a LUKS container where sdd1 is a writable partition on usb2 for detached LUKS header file ob1:
mkdir /mnt/usb2 && mount /dev/sdd1 /mnt/usb2 && \
cryptsetup --header /mnt/usb2/ob1 luksFormat /dev/sda
Now run lsblk -I 8 -o NAME,FSTYPE,SIZE,FSUSED. You should see output like:
Expand to view output
NAME FSTYPE SIZE FSUSED
sda 465.9G
sdb iso9660 7.5G
├──sdb1 iso9660 639M 639M
└──sdb2 vfat 64M
sdd 250M
└──sdd1 vfat 250M 16M
Confirm FSTYPE of sda is not crypto_LUKS and FSUSED of usb2 is 16M.
If everything looks good, continue. Otherwise, run wipefs -a /dev/sda to wipe the SSD file system info, rm /mnt/usb2/ob1 and restart the setup.
Map LUKS container using detached header and prepare logical volumes:
cryptsetup open --header /mnt/usb2/ob1 --type luks /dev/sda c1 && \
pvcreate /dev/mapper/c1 && vgcreate vg1 /dev/mapper/c1 && \
lvcreate -L 32G vg1 -n root && lvcreate -l 100%FREE vg1 -n home
Then format and mount root and home filesystems:
mkfs.ext4 /dev/vg1/root && mkfs.ext4 /dev/vg1/home && \
mkdir /mnt/vg1 && mount /dev/vg1/root /mnt/vg1 && \
mkdir /mnt/vg1/home && mount /dev/vg1/home /mnt/vg1/home
And run lsblk -I 8 -o NAME,FSTYPE,MOUNTPOINT. You should see output like:
Expand to view output
NAME FSTYPE MOUNTPOINT
sda
└─c1 LVM2_member
├──vg1-root ext4 /mnt/vg1
└──vg1-home ext4 /mnt/vg1/home
sdb iso9660
├──sdb1 iso9660 /run/archiso/bootmnt
└──sdb2 vfat
sdd
└──sdd1 vfat /mnt/usb2
Confirm FSTYPE of c1 of sda is LVM2_member, FSTYPE of vg1-root and vg1-home of c1 is ext4, and MOUNTPOINT of vg1-root and vg1-home are /mnt/vg1 and /mnt/vg1/home.
(Optional) If everything checks out, unplug usb2 and follow the steps in Breaks so you can be confident you won’t lose any work and have to start over again.
Installation
Everything needed to install and run Arch is already available on Archiso. Steps in this section from Offline Installation and licensed under GFDL-1.3-or-later.
Install Archiso to new root:
cp -ax / /mnt/vg1 && \
cp -vaT /run/archiso/bootmount/arch/boot/x86_64/vmlinuz \
/mnt/vg1/boot/vmlinuz-linux && \
genfstab -L /mnt/vg1 >> /mnt/vg1/etc/fstab
Configure base system:
arch-chroot /mnt/vg1 /bin/bash -c "\
sed -i 's/Storage=volatile/#Storage=auto/' /etc/systemd/journald.conf && \
rm /etc/udev/rules.d/81-dhcpcd.rules && \
systemctl disable pacman-init.service choose-mirror.service && \
rm -r /etc/systemd/system/{choose-mirror.service,pacman-init.service,etc-pacman.d-gnupg.mount,getty@tty1.service.d} && \
rm /etc/systemd/scripts/choose-mirror && \
rm /root/{.automated_script.sh,.zlogin} && \
rm /etc/mkinitcpio-archiso.conf && \
rm -r /etc/initcpio && \
pacman-key --init && pacman-key --populate archlinux"
This is a scripted approach and it omits one redundant step listed in the ArchWiki as retrieved on 21 Dec 2019. Tested and known to work with archiso 5.3.13-arch1-1. Modifications may be required for other Archiso versions.
Now run lsblk -I 8 -o NAME,SIZE,FSUSED,FSUSE%. You should see output like:
Expand to view output
NAME SIZE FSUSED FSUSE%
sda 465.9G
└─c1 465.9G
├──vg1-root 32G 1.7G 5%
└──vg1-home 433.9G 72M 0%
sdb 7.5G
├──sdb1 639M 639M 100%
└──sdb2 64M
sdd 250M
└──sdd1 250M 16M 6%
Confirm FSUSE% of vg1-root is 5% and FSUSED of vg1-home is 72M.
Booting
With Arch installed you need a way to boot it. Assuming only one partition exists on usb2 repurpose it for both detached LUKS header and bootability.
Start by reformating usb2 with 16MiB hidden partition:
test -f /mnt/usb2/ob1 && \
cp /mnt/usb2/ob1 /mnt/vg1/home && \
umount /mnt/usb2 && \
parted /dev/sdd rm 1 mkpart primary fat16 2048s 17463KiB \
set 1 lba off set 1 hidden on && \
mkfs.fat /dev/sdd1 && \
mount /dev/sdd1 /mnt/usb2 && \
mv /mnt/vg1/home/ob1 /mnt/usb2
Check the partition size with df /dev/sdd. You should see output like:
Expand to view output
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sdd1 16384 16384 0 100% /mnt/usb2
Confirm Use% is 100% and Available is 0.
Prepare your boot partition and update the file system table:
parted mkpart /dev/sdd primary fat16 36864s 228720000B \
set 2 esp on && \
mkfs.fat /dev/sdd2 && \
mkdir -p /mnt/boot && \
mount /dev/sdd2 /mnt/boot && \
genfstab -U /mnt/boot >> /mnt/vg1/etc/fstab
Show partitions with parted /dev/sdd print. You should see output like:
Expand to view output
Number Start End Size Type File system Flags
1 1049kB 17.9MB 16.8MB primary fat16 hidden
2 18.9MB 229MB 210MB primary fat16 esp
Confirm Size of 2 is greater than 209.72MB, Type of 1 and 2 is primary with File system of fat16, and Flags of 1 and 2 are hidden and esp, respectively.
Make USB bootable, relaxing validation for boot from non-GPT partition table:
export SYSTEMD_RELAX_ESP_CHECKS=1 && \
test "yes" != "$(bootctl --esp-path=/mnt/boot is-installed)" && \
bootctl --esp-path=/mnt/boot install
You should see output like:
Expand to view output
Created "/mnt/boot/EFI".
Created "/mnt/boot/EFI/systemd".
Created "/mnt/boot/EFI/BOOT".
Created "/mnt/boot/loader".
Created "/mnt/boot/loader/entries".
Created "/mnt/boot/EFI/Linux".
Copied "/usr/lib/systemd/boot/efi/systemd-bootx64.efi" to "/mnt/boot/EFI/systemd/systemd-bootx64.efi".
Copied "/usr/lib/systemd/boot/efi/systemd-bootx64.efi" to "/mnt/boot/EFI/BOOT/BOOTX64.EFI".
Created "/mnt/boot/01234567890abcdef1234567890abdf0".
Random seed file /mnt/boot/loader/random-seed successfully written (512 bytes).
Created EFI boot entry "Linux Boot Manager".
Where 01234567890abcdef1234567890abdf0 is a pseudo-random default boot loader and currently specified in the loader config generated by bootctl in the last step.
Create an entry for the default loader using a subshell:
(
default="$(grep '^default' /mnt/boot/loader/loader.conf)"
prefix="${default:8:33}"
version=1
cp "/usr/share/systemd/bootctl/arch.conf \
/mnt/boot/loader/entries/$prefix$version.conf" && \
sed -in '/options\|##\^$/d' /mnt/boot/loader/entries/$prefix$version.conf && \
echo "options root=/dev/vg1/root" >> /mnt/boot/loader/entries/$prefix$version.conf
)
Update mkinitcpio.conf to use systemd and configure suggested settings:
(
conf="$(</mnt/vg1/etc/mkinitcpio.conf)"
hooks="$(grep '^HOOKS' <<< $conf)"
files="$(grep '^FILES' <<< $conf)"
conf="${conf//$hooks/#$hooks\\nHOOKS=(base systemd autodetect keyboard sd-vconsole modconf block sd-encrypt sd-lvm2 filesystems fsck)}"
conf="${conf//$files/#$files\\nFILES=(/boot/ob1)}"
echo "$conf" > /mnt/vg1/etc/mkinitcpio.conf
)
Compare the updated file against the Archiso original:
diff -u0 /etc/mkinitcpio.conf /mnt/vg1/etc/mkinitcpio.conf
You should see output like:
Expand to view output
--- /etc/mkinitcpio.conf 1970-01-01 00:32:50.000000000 +0000
+++ /mnt/vg1/etc/mkinitcpio.conf 1970-01-01 00:32:50.000000000 +0000
@@ -19 +19,2 @@
-FILES=()
+#FILES=()
+FILES=(/boot/ob1)
@@ -52 +53,2 @@
-HOOKS=(base udev autodetect modconf block filesystems keyboard fsck)
+#HOOKS=(base udev autodetect modconf block filesystems keyboard fsck)
+HOOKS=(base systemd autodetect keyboard sd-vconsole modconf block sd-encrypt sd-lvm2 filesystems keyboard fsck)
Verify new FILES and HOOKS were added and their previous values commented out.
Afterwards create the vconsole.conf and crypttab.initramfs files for the sd-vconsole and sd-encrypt hooks, respectively, as noted in their help docs:
echo "FONT=latarcyrheb-sun32\nKEYMAP=us" > /mnt/vg1/etc/vconsole.conf && \
echo "c1 $(find /dev/disk/by-id -name '*APPLE_SSD*') - header=/boot/ob1" \
> /mnt/vg1/etc/crypttab.initramfs
Copy LUKS header and mount the ESP inside the chroot jail:
cp /mnt/usb2/ob1 /mnt/vg1/boot && \
mkdir -p /mnt/vg1/boot/efi && \
arch-chroot /mnt/vg1 /bin/bash -c "\
findmnt /boot/efi || mount /dev/sdd2 /boot/efi"
Confirm mountpoint exists and ESP installed:
arch-chroot /mnt/vg1 /bin/bash -c "\
export SYSTEMD_RELAX_ESP_CHECKS=1 && \
bootctl is-installed || findmnt /boot/efi || lsblk"
Verify result is yes otherwise use the findmnt or lsblk output to help debug. And with the ESP installed generate the image required to boot the system:
arch-chroot /mnt/vg1 /bin/bash -c "mkinitcpio -P"
You should see output like:
Expand to view output
==> Buildiing image from preset: /etc/mkinitcpio.d/linux.preset: 'default'
-> -k /boot/vmlinux-linux -c /etc/mkinitcpio.conf -g /boot/initramfs-linux.img
==> Starting build: 5.3.13-arch1-1
-> Running build hook: [base]
-> Running build hook: [systemd]
-> Running build hook: [autodetect]
-> Running build hook: [keyboard]
-> Running build hook: [sd-vconsole]
-> Running build hook: [modconf]
-> Running build hook: [block]
-> Running build hook: [sd-encrypt]
-> Running build hook: [sd-lvm2]
-> Running build hook: [filesystems]
-> Running build hook: [fsck]
==> Generating module dependencies
==> Creating gzip-compressed initcpio image: /boot/initramfs-linux.img
==> Image generation successful
==> Buildiing image from preset: /etc/mkinitcpio.d/linux.preset: 'fallback'
-> -k /boot/vmlinux-linux -c /etc/mkinitcpio.conf -g /boot/initramfs-linux-fallback.img -S autodetect
==> Starting build: 5.3.13-arch1-1
-> Running build hook: [base]
-> Running build hook: [systemd]
-> Running build hook: [autodetect]
-> Running build hook: [keyboard]
-> Running build hook: [sd-vconsole]
-> Running build hook: [modconf]
-> Running build hook: [block]
==> WARNING: Possibly missing firmware for module: wd719x
==> WARNING: Possibly missing firmware for module: aic94xx
-> Running build hook: [sd-encrypt]
-> Running build hook: [sd-lvm2]
-> Running build hook: [filesystems]
-> Running build hook: [fsck]
==> Generating module dependencies
==> Creating gzip-compressed initcpio image: /boot/initramfs-linux-fallback.img
==> Image generation successful
arch-chroot /mnt/vg1 /bin/bash -c "mkinitcpio -P 20.24s user 4.82s system 107% cpu 23.390 total
Copy both initcpio image files generated in the last step to /mnt/boot and then remove the duplicate LUKS header now embedded in the images:
cp -ai /mnt/vg1/boot/initramfs-linux* /mnt/boot && \
cp -ai /mnt/vg1/boot/vmlinuz-linux /mnt/boot && \
rm /mnt/vg1/boot/ob1
Finally, set a root password and power down the machine:
arch-chroot /mnt/vg1 /bin/bash -c "passwd" && shutdown -P now
Reboot
Unplug both usb1 and usb2 then power on the machine.
As the computer boots you should see a blinking folder icon with a question mark inside as described in Troubleshooting. If you do, go ahead and insert usb2 into one of the USB ports on the machine.
At this point the folder will stop blinking and the boot process will begin. Once authenticated and booted you will be presented with a login prompt:
Arch Linux 5.3.13-arch1-1 (tty1)
archlinux login: _
Type root for the login and press Enter. A password prompt will then appear where you can enter the password created in the last step of Booting. Once logged in you are finished and may begin using your invisible arch.
Summary
In this tutorial you learned how to use a mid-2014 Mac to achieve a form of deniable encryption by installing Arch Linux offline. For improved deniability bear in mind UEFI boot loaders are stored in firmware at /sys/firmware/efi and you may wish to use efibootmgr to clear some of those out at some point.
Once the Mac has reached EOL it is important you dispose of it properly. I have prearranged a drop-off location where you may safely destroy the device:
08°20'34.6"S, 115°30'26.32"EJust be sure to tip the local Banjar appropriately.
Troubleshooting
If during system boot, all you see is a blinking folder with a question mark:
- You need to install an operating system
- You need to boot to your invisible operating system
Note the blinking folder is the expected behavior after after performing a zero-write as described in the first step of Setup. It is also the expected behavior when attempting to start your machine without usb2 attached. If you decide to bail on your Arch install there’s always Manjaro.
If during Reboot you do not receive a password prompt and see:
Expand to view output
[ TIME ] Timed out waiting for device /dev/disk/by-id/dm-uuid/CRYPT-LUKS2-fe06f149a69e844682323674ee70d4bf-c1.
[DEPEND] Dependency failed for Cryptography Setup for c1.
[DEPEND] Dependency failed for Local Encrypted Volumes.
...
[FAILED] Failed to start Switch Root.
You likely need to modify crypttab.initramfs to point to your physical SSD. When finished, regenerate your RAM disk and try booting from usb2 again.
If during Reboot you do receive a password prompt and see:
Expand to view output
[ OK ] Created slice system-lvm2\x2dpvscan.slice.
Starting LVM2 PV scan on device 254:0...
[ OK ] Started Cryptography Setup for c1.
[ OK ] ...
[ OK ] Create list of static device nodes in /dev.
[ OK ] ...
[FAILED] Failed to start Switch Root.
You likely need to modify the root in your ESP loader entry like options root=/dev/vg1/root, regenerate your RAM disk and try booting from usb2 again.
To confirm the expected mappings open the emergency shell and then run command lvm vgscan and lvm lvscan and review the output. The expected device node for root should be listed in the output.
-
A detached header affords “a system on an unpartitioned, encrypted disk that will be indistinguishable from a disk filled with random data, which could allow deniable encryption”. ArchWiki, Encrypting an entire system, 6 (Plain dm-crypt). Retrieved 2019-12-24. ↩︎
-
“By using a detached header the encrypted blockdevice itself only carries encrypted data, which gives deniable encryption as long as the existence of a header is unknown to the attackers.” ArchWiki, dm-crypt/Specialties, 6 (Encrypted system usng a detached LUKS header). Retrieved 2019-12-24. ↩︎
-
Solid-state drives with detached LUKS header afford “the same security as on a magnetic disk.” DMCrypt FAQ 5.19 (Security Aspects). Retrieved 2019-12-24. ↩︎
-
“If plausible deniability is required, TRIM should never be used […] or the use of encryption will be given away.” ArchWiki, dm-crypt/Specialties, 4 (Discard/TRIM support for solid state drives (SSD)). Retrieved 2019-12-24. ↩︎