跳至內容

dm-crypt/特殊应用

出自 Arch Linux 中文维基

保護未加密的boot分區

[編輯 | 編輯原始碼]

即使在加密了根分區的情況下,磁碟上仍存在兩處未加密的區域:/boot主引導記錄(MBR)。通常情況下,引導加載程序需要讀取/boot,BIOS需要讀取主引導記錄,若兩者被加密,則無法解密dm-crypt容器,進而完成啟動過程。不過,GRUB支持解密經過加密的/boot,詳見dm-crypt/加密整個系統#加密的boot分區(GRUB)

本節介紹了可提高啟動過程安全性的方法。

警告:雖然對/boot分區和主引導記錄進行額外保護可避免許多針對啟動過程的攻擊方法。但系統仍可能面臨其它威脅,例如BIOS/UEFI/固件被篡改,鍵盤記錄器,冷啟動攻擊等。這些攻擊方式不在本文的討論範圍中。有關全盤加密的系統信任問題,參見[1]
注意:對於UEFI啟動的系統,在配置得當時(例如,使用統一內核映像),只有EFI 系統分區是未加密的。配合安全啟動,可確保啟動鏈的安全性。該方式的安全性與本節中介紹的其它方法相同,且配置簡單。詳見dm-crypt/加密整個系統#使用TPM2和安全啟動在單一分區上配置

從可行動裝置引導

[編輯 | 編輯原始碼]

使用單獨的引導設備啟動系統非常直接,並能極大提高系統在面臨特定類型攻擊時的安全性。在加密了根分區的系統中,易受到攻擊的部分為:

要成功啟動系統,上述部分必須保持未加密狀態。要避免上述未加密區域被篡改,建議將它們存儲到可移動介質(例如,USB設備)中,並從可移動介質中引導,而不是直接從硬碟引導。只要保證相應的可移動介質的安全,就可確保相關區域不被篡改,提高解密系統時的安全性。

如下內容假定已完成系統配置,並使用單獨boot分區,掛載到/boot。若未還未完成系統配置,參見Dm-crypt/系統配置#內核參數,注意在配置過程中,用可行動裝置替換硬碟。

注意:必須確保系統支持從選擇的可移動介質(USB設備、移動硬碟、SD卡等)啟動。

準備可行動裝置(/dev/sdx):

# gdisk /dev/sdx # 按需格式化。也可以使用cgdisk、fdisk、cfdisk、gparted等分区工具
# mkfs.ext2 /dev/sdx1 # BIOS启动的系统
# mkfs.fat -F 32 /dev/sdx1 # UEFI启动的系统
# mount /dev/sdx1 /mnt

將已有/boot分區內容複製到可行動裝置中:

# cp -ai /boot/* /mnt/

將可行動裝置掛載到/boot,並更新fstab

# umount /boot
# umount /mnt
# mount /dev/sdx1 /boot
# genfstab -p -U / > /etc/fstab

更新GRUB配置。grub-mkconfig應當能自動檢測新分區的UUID。但若配置了自定義啟動選項,可能需要手動更新相應條目。

# grub-mkconfig -o /boot/grub/grub.cfg
# grub-install /dev/sdx # 安装到可移动设备,而不是硬盘中。用于BIOS启动的系统
# grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=grub # 用于UEFI启动的系统

重啟以測試新的配置。注意在BIOS/UEFI中選擇正確的啟動設備。若系統啟動失敗,仍可以從硬碟啟動以修復存在的問題。

chkboot

[編輯 | 編輯原始碼]
警告:chkboot可發現並提示/boot遭到篡改(tamper-evident),但不能防止篡改(tamper-proof)。chkboot腳本運行前,輸入的密碼可能已經被篡改過的引導加載程序、 內核、initrd讀取。因此,當chkboot完整性檢查失敗時,數據安全性已經無法保證。

根據ct-magazine中一篇文章(Issue 3/12, page 146, 01.16.2012, [2])的內容,下述腳本將檢查/boot中文件的SHA-1散列值是否改變、/boot分區的inode、占用的磁碟塊信息,以及主引導記錄。該腳本不能完全阻止特定類型的攻擊,但極大提升了攻擊難度。腳本自身的配置文件不存儲在未加密的/boot中。因此,對於攻擊者,在系統鎖定/關機的情況下,難以發現系統將在啟動時檢查/boot分區的內容。然而,對於有備而來的攻擊者,可修改固件以在系統內核之上執行代碼,劫持對boot分區的訪問,呈現出文件未被篡改的假象。通常來講,在固件以上的層級的安全措施都不能確保啟動鏈可信,也不能確保發現並提示篡改跡象。

下述腳本可在此處獲取(作者:Juergen Schmidt, ju at heisec.de;許可:GPLv2)。此外,也可通過chkbootAUR安裝。建議使用AUR包,其提供了額外的輔助腳本。

Juergen Schmidt的原始腳本

[編輯 | 編輯原始碼]

/usr/local/bin/chkboot_user.sh需要在登錄後立即執行,故需要加入自動啟動中。(例如,對於KDE:系統設置 -> 系統 -> 自動啟動;對於GNOME 3,使用gnome-session-properties)。

由於在Arch Linux中,/boot的內容經常改變(例如,更新內核),建議使用如下腳本進行系統更新,以在系統更新時同時更新chkboot設置:

#!/bin/sh
#
# 注意:将<user> 替换为你的用户名,并使用sudo执行该脚本,以确保pacman、chkboot有所需的权限
#
echo "Pacman update [1] Quickcheck before updating" &
sudo -u <user> /usr/local/bin/chkboot_user.sh
/usr/local/bin/chkboot.sh
sudo -u <user> /usr/local/bin/chkboot_user.sh
echo "Pacman update [2] Syncing repos for pacman"
pacman -Syu
/usr/local/bin/chkboot.sh
sudo -u <user> /usr/local/bin/chkboot_user.sh
echo "Pacman update [3] All done, let us roll on ..."

AUR包

[編輯 | 編輯原始碼]

在安裝對應AUR包後,啟動chkboot.service

可能需要在mkinitcpio鉤子列表的最後添加chkboot,以便每次mkinitcpio重新生成initramfs時更新chkboot記錄的哈希值。可通過修改/etc/mkinitcpio.conf,在HOOKS列表的最後添加chkboot實現。

AUR包中還附帶了chkboot-desktopalert腳本,在檢測到意料之外的/boot修改時以圖形化窗口的形式彈出警告。可將該腳本加入桌面環境的啟動項中。

mkinitcpio-chkcryptoboot

[編輯 | 編輯原始碼]
警告:本鉤子不會加密GRUB的核心代碼(MBR部分)、EFI stub。若攻擊者通過修改引導加載程序,在運行時篡改內核或initramfs,本鉤子不能保證安全性。

mkinitcpio-chkcryptobootAUR是一個mkinitcpio鉤子,將在早期用戶空間中檢查完整性,並在發現系統可能被篡改的痕跡時建議用戶不要輸入密碼。本方案的安全性來自加密的boot分區和加密的根分區。並要求boot分區的密碼和根分區的不同。加密的boot分區將由GRUBcryptodisk.mod模塊解密。這樣,可保護initramfs內核不受到離線攻擊。並且,即使在受到篡改的機器上輸入了/boot分區的密碼,在chkcryptoboot鉤子的提示下,也能確保根分區的安全性。(假設chkcryptoboot檢測到了篡改,並且鉤子本身沒有在運行時被篡改)

本鉤子需要>=2.00版本的grub。要保證安全性,需要使用專用的、有獨立密碼的加密/boot分區。

安裝

[編輯 | 編輯原始碼]

安裝 mkinitcpio-chkcryptobootAUR。編輯/etc/default/chkcryptoboot.conf以進行配置。若要檢測攻擊者是否用自製的boot分區替換了原本的boot分區,設置CMDLINE_NAMECMDLINE_VALUE為某一秘密值。例如,可以按照安裝過程中顯示的建議使用兩個哈希值。此外,還需要在/etc/default/grub中修改內核參數,傳遞相應的CMDLINE_NAMECMDLINE_VALUE。之後,修改/etc/mkinitcpio.confHOOKS=行,在encrypt之前插入chkcryptoboot。最後重新生成 initramfs

技術概述

[編輯 | 編輯原始碼]

mkinitcpio-chkcryptobootAUR同時包含了安裝、運行時mkinitcpio鉤子。安裝鉤子將在每次重新生成initramfs時運行。對於UEFI啟動的系統,將計算EFI stub($esp/EFI/grub_uefi/grubx64.efi)的哈希值;對於BIOS啟動的系統,將計算GRUB安裝到的磁碟的首446位元組的哈希值,並將哈希值保存到initramfs中(initramfs保存在加密的/boot分區中)。當系統啟動時,在GRUB中輸入/boot分區的密碼後,運行時鉤子將重新計算對應的哈希值,將計算出的值與保存的值比對,若匹配,才提示輸入根分區密碼解密根分區。若不匹配,將顯示類似如下的錯誤:

CHKCRYPTOBOOT ALERT!
CHANGES HAVE BEEN DETECTED IN YOUR BOOT LOADER EFISTUB!
YOU ARE STRONGLY ADVISED NOT TO ENTER YOUR ROOT CONTAINER PASSWORD!
Please type uppercase yes to continue:

除了計算引導加載程序的哈希值之外,鉤子還將在運行時以及啟動過程完成後,根據/etc/default/chkcryptoboot.conf中的配置檢查當前運行內核的內核參數,以確認GRUB配置文件、boot分區未被非法替換。

注意,對於BIOS啟動的系統,本鉤子計算並檢查的是GRUB第一階段引導加載程序(安裝在引導設備的前446位元組)的哈希值,並未檢查第二階段引導加載程序(core.img)。

除了上述腳本外,也可以使用AIDE進行哈希值檢查。AIDE的配置文件非常靈活,可進行高度自定義配置。

上述介紹的其它方法雖然可以滿足大部分用戶的需求,但並未解決所有與未加密的/boot分區有關的安全問題。為了構建經過完全驗證的啟動鏈,學術界提出了一種名為POTTS的實現,旨在實施 STARK 認證框架。

POTTS 概念驗證(proof-of-concept)使用 Arch Linux 作為基礎發行版,並實現了一個包含以下組件的系統啟動鏈:

  • POTTS - 用於展示一次性驗證消息的啟動菜單。
  • TrustedGrub - GRUB Legacy實現,通過TPM晶片的PCR寄存器驗證內核和initramfs。
  • TRESOR - 內核補丁,實現了AES加密,並在啟動過程中,將主密鑰保存在CPU寄存器中,而非內存中。

基於Arch Linux (2013-01及之後版本的ISO鏡像)的安裝配置方法已作為研究的一部分發布。注意,在嘗試之前,應當了解軟體倉庫中並沒有相關實現工具,並且該方法的日常維護非常繁瑣。

使用GPG、LUKS、OpenSSL加密的密鑰文件

[編輯 | 編輯原始碼]

如下論壇帖子討論了使用了雙因素身份驗證、GPG或OpenSSL加密的密鑰文件,而不是明文密鑰文件。拓展了System Encryption using LUKS with GPG encrypted keys中的討論:

注意:

  • 以上方法只適用於只有兩個主分區的情況:在配置加密時必要的boot分區以及主LVM分區。在LVM中,可以按需求任意創建邏輯卷(至少應當有根邏輯卷、swap邏輯卷、home邏輯卷)。只使用兩個主分區的好處是只需要一個密鑰文件,並且可以使用休眠功能(休眠到加密的swap分區)。若要使用休眠功能,/etc/mkinitcpio.conf中的鉤子配置應類似:
    HOOKS=( ... usb usbinput <etwo或者ssldec> encrypt(若使用openssl) lvm2 resume ... )
    ,並在內核參數中加入
    resume=/dev/<VolumeGroupName>/<LVNameOfSwap>
  • 若需要臨時在某處保存解密後的密鑰文件,不要保存到未加密的磁碟中。最好確保只保存到內存中(例如,/dev/shm)。
  • 若要使用GPG加密的密鑰文件,需要靜態編譯1.4版本的GnuPG。或者,使用gnupg1AUR並相應修改鉤子實現。
  • 對於上述第二個帖子(OpenSSL加密密鑰文件),更新OpenSSL可能破壞自定義的ssldec鉤子。

遠程解鎖分區

[編輯 | 編輯原始碼]

對於需要遠程登錄啟動中的系統,在啟動早期階段遠程提供解密LUKS加密的分區(例如,根分區及其它分區)的密碼的情況,可以使用mkinitcpio鉤子配置網絡接口並啟動SSH伺服器。下文中介紹的某些包提供了mkinitcpio 編譯鉤子,以簡化配置。下面將介紹了在本機控制台輸入密碼的基礎上,增加遠程解密的方法。

注意:
  • 需要確保使用內核對網絡設備的命名(例如,eth0),而不是udev的命名(例如,enp1s0)。
  • 默認情況下,可預測網絡接口名(Predictable Network Interface Names)在啟動過程後期才激活並修改網絡設備的命名。可使用dmesg檢查有關網絡的內核模塊的日誌以確定網絡設備的原始命名(例如,eth0)。
  • 可能需要將以太網卡無線網卡所需的模塊添加到MODULES列表中。

基於BusyBox的initramfs(使用mkinitcpio)構建

[編輯 | 編輯原始碼]

mkinitcpio-netconf和/或mkinitcpio-pppAUR為基於BusyBox的initramfs提供了網絡支持。對於SSH伺服器,則可使用mkinitcpio-dropbearmkinitcpio-tinyssh。注意,上述鉤子不會安裝shell,故可能按需安裝mkinitcpio-utils。對於上述包的任何組合方式,下述操作步驟都適用,些許不同之處將單獨指出。

  1. 若沒有已有的SSH密鑰對,在客戶端系統上生成密鑰對。生成的密鑰對將用於登錄遠程主機。
    注意:
    • tinyssh只支持不設置密碼的Ed25519ECDSA密鑰。若使用mkinitcpio-tinyssh,只能選擇這兩種密鑰類型之一。
    • 0.0.3-5版本的mkinitcpio-dropbear與現有的移除了dss的dropbear實現不兼容。詳情及修複方法參見Github
  2. 根據選擇的SSH伺服器,將SSH公鑰插入到遠程主機的/etc/dropbear/root_key//etc/tinyssh/root_key文件中(對於剛剛生成的密鑰對,SSH公鑰存儲在.pub結尾的文件中,公鑰一般被複製到遠程主機以實現無密碼登錄)。
    提示:可依照此方法添加其它所需的SSH公鑰。也可以直接複製遠程主機的~/.ssh/authorized_keys文件,但需要檢查文件內容以僅包含需要執行遠程解鎖操作的公鑰。添加公鑰後,需要使用mkinitcpio重新生成initrd。另見OpenSSH#保護
  3. 修改/etc/mkinitcpio.conf的"HOOKS"列表,在filesystems文件系統鉤子前添加<netconf 和/或 ppp> <dropbear 或 tinyssh> encryptssh三個鉤子(encryptssh鉤子代替了encrypt鉤子)。並重新生成 initramfs
    注意:不需要mkinitcpio-nfs-utils提供的net鉤子。
  4. 配置所需的cryptdevice=內核參數,並在引導加載程序配置中添加恰當的ip=內核參數。例如,若要使用DHCP分配IP位址,可設置
    ip=dhcp
    ,這一般要求DHCP伺服器始終為需要遠程解密的主機分配相同的IP位址,否則進行SSH連接較為繁瑣。也可以使用靜態IP位址:
    ip=192.168.1.1:::::eth0:none
    。此外,還可指定子網掩碼和網關:
    ip=192.168.1.1::192.168.1.254:255.255.255.0::eth0:none
    注意:若使用0.0.4及更新版本的mkinitcpio-netconf,還可通過在ip=中設置多個參數,分別配置多個網絡接口。但是,不能同時設置「全局配置」和針對某一網絡接口的配置(例如,不能與ip=dhcp混用)。必須顯式地指定網絡接口:
    ip=ip=192.168.1.1:::::eth0:none:ip=172.16.1.1:::::eth1:none
    若使用DHCP,可能需要添加netconf_timeout=內核參數,避免在DHCP服務不可用時,netconf無限期地嘗試通過DHCP獲取IP位址。ip=參數的詳細介紹可參見Mkinitcpio#使用_net。設置完成後,更新引導加載程序配置。
  5. 最後,重啟遠程主機並嘗試使用SSH連接, 顯式地指定使用root用戶連接 (即使遠程主機的系統配置中關閉了root帳戶,也可在initrd中使用,以解鎖遠程主機)。若遠程主機在啟動後使用openssh提供遠程SSH連接,並且使用mkinitcpio-dropbear提供initramfs中的SSH服務端支持,則在連接到initramfs中的SSH服務端時,不會顯示SSH主機密鑰變更的警告,因為mkinitcpio-dropbear將使用和OpenSSH相同的SSH密鑰(注意,dropbear不支持Ed25519密鑰,若openssh使用了Ed25519密鑰,連接時仍會顯示密鑰不匹配警告) 。對於mkinitcpio-tinyssh,其附帶了tinyssh-convert腳本,以使用與openssh相同的密鑰(當前僅支持使用Ed25519密鑰)。有時,可能需要手動使用tinyssh-convert將密鑰複製到/etc/tinyssh/sshkeydir。注意,無論使用何種方式複製ssh密鑰,都需要確保密鑰已由ssh守護進程生成,即,至少通過systemd單元啟動過一次OpenSSH。配置完成後,重啟遠程主機並通過SSH連接,應當能看到輸入密碼以解鎖設備的提示符;輸入密碼並等待系統啟動完成後,可以使用普通用戶進行正常的SSH連接

使用Wi-Fi

[編輯 | 編輯原始碼]

netconf鉤子一般用於以太網連接。若遠程主機只連接無線網絡,並且要通過Wi-Fi進行遠程連接以解鎖設備,需要在netconf鉤子前運行相關配置無線網絡的鉤子,可以使用預定義的鉤子,也可以創建自定義鉤子。

預定義鉤子
[編輯 | 編輯原始碼]

可以安裝預定義鉤子:

  1. 安裝mkinitcpio-wifiAUR
  2. 根據網絡屬性創建wpa_supplicant配置,以設置Wi-Fi連接:
    wpa_passphrase "ESSID" "passphrase" > /etc/wpa_supplicant/initcpio.conf
  3. 修改/etc/mkinitcpio.conf,在netconf鉤子前添加wifi鉤子。應當能自動檢測到與Wi-Fi相關的模塊。若沒有自動檢測到,手動將需要的模塊加入MODULES中。
  4. 添加ip=:::::wlan0:dhcp內核參數
  5. 重新生成 initramfs
  6. 更新引導加載程序配置。
自定義鉤子
[編輯 | 編輯原始碼]

下例展示了使用USB Wi-Fi適配器,連接到WPA2-PSK保護的Wi-Fi網絡的方法。對於其它配置(例如,連接到WEP網絡、使用其它initramfs生成工具),可能需要相應調整某些步驟。

  1. 修改/etc/mkinitcpio.conf
    • 添加Wi-Fi適配器所需的內核模塊。
    • 包含wpa_passphrasewpa_supplicant二進制文件。
    • net鉤子前添加wifi鉤子(該鉤子即是需要創建的自定義鉤子,也可以使用其它名稱。
      MODULES=(module)
      BINARIES=(wpa_passphrase wpa_supplicant)
      HOOKS=(base udev autodetect ... wifi net ... dropbear encryptssh ...)
  2. /etc/initcpio/hooks/wifi中創建wifi鉤子:
    run_hook ()
    {
    # Sleep a couple of seconds so wlan0 is setup by kernel
    sleep 5

    # Set wlan0 to up
    ip link set wlan0 up

    # Associate with Wi-Fi network
    # 1. Save temp config file
    wpa_passphrase "network ESSID" "pass phrase" > /tmp/wifi

    # 2. Associate
    wpa_supplicant -B -D nl80211,wext -i wlan0 -c /tmp/wifi

    # Sleep a couple of seconds so that wpa_supplicant finishes connecting
    sleep 5

    # wlan0 should now be connected and ready to be assigned an ip by the net hook
    }

    run_cleanuphook ()
    {
    # Kill wpa_supplicant running in the background
    killall wpa_supplicant

    # Set wlan0 link down
    ip link set wlan0 down

    # wlan0 should now be fully disconnected from the Wi-Fi network
    }
  3. /etc/initcpio/install/wifi中創建鉤子安裝文件:
    build ()
    {
    add_runscript
    }
    help ()
    {
    cat<<HELPEOF
    Enables Wi-Fi on boot, for dropbear ssh unlocking of disk.
    HELPEOF
    }
  4. 添加ip=:::::wlan0:dhcp內核參數。注意移除ip=:::::eth0:dhcp,以避免衝突。
  5. (可選)可額外添加使用ip=:::::eth0:dhcp內核參數以進行以太網連接的啟動加載項。
  6. 重新生成 initramfs
  7. 更新引導加載程序配置。

注意檢查系統的Wi-Fi配置,避免系統啟動完成後無法遠程連接。

若在initramfs中無法連接到Wi-Fi網絡,可嘗試增加腳本中的sleep時間。

基於systemd的initramfs(使用mkinitcpio)構建

[編輯 | 編輯原始碼]

mkinitcpio-systemd-extrasAUR為基於systemd的initramfs提供了一整套用於網絡配置、遠程SSH連接的構建鉤子。對於遠程會話,可進行配置以使用busybox的dash完全訪問initramfs環境,也可只顯示密碼輸入提示符。

最小配置如下:

/etc/mkinitcpio.conf
HOOKS=(base systemd autodetect microcode modconf kms keyboard sd-vconsole sd-network block sd-tinyssh sd-encrypt filesystems fsck)
SD_TINYSSH_COMMAND="systemd-tty-ask-password-agent --query --watch"

對於上述配置,當使用mkinitcpio構建initramfs時,將複製systemd-networkd配置文件,並嘗試從已有的TinySSH、OpenSSH配置中複製/轉換SSH密鑰。系統需要安裝tinyssh(但不需要啟用)。若系統不使用systemd-networkd,需要額外配置參數,詳見mkinitcpio-systemd-extrasAUR文檔

注意:
  • 在默認情況下,可預測網絡接口名(例如,enp2s0)在initramfs階段不可用。故直接複製的systemd-networkd配置文件很可能無法正常工作。解決方法可參見mkinitcpio-systemd-extras wiki
  • 還可使用mkinitcpio-systemd-tool實現在啟動早期遠程連接,並解密LUKS卷。該方式與上述介紹完全不同,需要額外添加systemd-tool鉤子,並且在相應systemd單元的[X-SystemdTool]節中進行配置。有關該方式實現以及配置的文檔非常有限。

基於systemd的initramfs(使用dracut)構建

[編輯 | 編輯原始碼]

若使用dracut而不是mkinitcpio構建initramfs,可能需要使用dracut-sshd

設置用於重啟的一次性密鑰

[編輯 | 編輯原始碼]

還可以通過設置臨時密鑰文件,以在無需手動終端交互輸入密碼的情況下實現遠程設備的重啟。密鑰文件需要放置在能在啟動時被內核訪問的位置,並設置cryptkey啟動參數,同時,還需要通過"cryptsetup luksAddKey"命令添加密鑰文件。

使用passless-bootAUR可方便地完成上述操作。若想要自行實現,也可參照該工具的的README。注意,在使用前,請閱讀Security considerations中的討論。

固態硬碟的Discard/TRIM支持

[編輯 | 編輯原始碼]

固態硬碟用戶應當注意,在默認情況下,除非指定discard選項,否則dm-crypt的device mapper不會啟用TRIM命令支持。

由於可能的安全問題,device mapper維護者表示永遠不會在dm-crypt設備上默認啟用TRIM支持。[3][4] 在啟用TRIM的設備上,通過釋放的磁碟塊信息,有可能推斷出加密設備上層使用的文件系統。cryptsetup開發者在該博客中介紹了啟用TRIM可能導致的問題。還需注意,相關風險可能疊加。例如,若設備仍使用之前的加密方式(cryptsetup < 1.6.0)加密(--cipher aes-cbc-essiv),相比現在的默認選項,釋放的磁碟塊信息可能洩露更多配置信息。

依據配置方式,可分情況詳細討論:

  • 若設備使用默認的dm-crypt LUKS模式加密:
    • 在默認情況下,LUKS頭存儲在設備頭部,啟用TRIM支持有利於保護對LUKS頭的修改。例如,在撤銷了一個可能洩露的LUKS密碼後,在未啟用TRIM支持的情況下,仍可能從設備上讀取舊的LUKS頭,直到其被另一操作覆蓋。若設備在此情況下失竊,則理論上,攻擊者可能能夠使用某種方法找到舊的LUKS頭,並使用洩露的LUKS密碼獲得主密鑰解密設備。詳見cryptsetup FAQ, section 5.19 What about SSDs, Flash and Hybrid Drives?Full disk encryption on an ssd
    • 若認為啟用TRIM可能造成的信息洩露比上述LUKS頭有關問題更加嚴重,則應當保持TRIM關閉的狀態。另見安全地擦除磁碟#快閃記憶體
  • 若設備使用dm-crypt plain模式加密,或者單獨存儲LUKS頭
    • 若需要可抵賴性,則根據本節開頭提到的內容,永遠不應啟用TRIM支持。否則,TRIM可能造成的信息洩露會使得plain模式加密沒有意義。
    • 若無需可抵賴性,則在可接受本節開頭提到的安全風險的情況下,可以為了性能啟用TRIM支持。
警告:在啟用TRIM前,需確保相應設備完全支持TRIM指令,否則可能造成數據丟失。詳見固態硬碟#TRIM

除了啟用dm-crypt的discard支持外,還應定期執行fstrim(8);或者,在/etc/fstab中指定discard掛載選項。詳見固態硬碟#TRIM

對於LUKS2加密的設備,可通過打開設備時使用--allow-discards --persistent掛載選項啟用TRIM支持。使用該命令後,allow-discards將寫入LUKS2頭中,之後打開設備時無需再手動指定。

注意:
  • 通過cryptsetup --persistent設置新的持久性標誌時,將替代所有已設置的持久性標誌,而不是在原有基礎上新增一個標誌。故若還設置了其它標誌,需要一同指定。
  • 若使用不經過dm-crypt的OPAL加密(詳見cryptsetup-luksFormat(8)--hw-opal-only選項),則文件系統和底層磁碟間沒有dm-crypt層,discard請求將直接傳遞到底層磁碟,無需手動啟用discard支持。
# cryptsetup --allow-discards --persistent open /dev/sdaX root

若設備已經打開,上述open操作將會報錯。此時,應當使用cryptsetup-refresh(8)命令:

# cryptsetup --allow-discards --persistent refresh root

可通過cryptsetup luksDump輸出的LUKS2頭信息確保標誌已經持久性設置:

# cryptsetup luksDump /dev/sdaX | grep Flags
Flags:          allow-discards

LUKS1及plain模式

[編輯 | 編輯原始碼]

對於LUKS1或者plain模式加密的設備,需要在每次打開設備時手動指定選項啟用TRIM支持。

要在啟動時啟用TRIM支持,設置如下內核參數

若使用encrypt鉤子:

cryptdevice=/dev/sdaX:root:allow-discards

若使用sd-encrypt鉤子(基於systemd的initramfs):

rd.luks.options=discard
注意:對於在initramfs鏡像/etc/crypttab(對應/etc/crypttab.initramfs)中掛載的文件系統,設置rd.luks.options=discard內核選項沒有作用。必須在/etc/crypttab.initramfs中設置discard選項。

對於通過/etc/crypttab解密的設備,指定discard選項,例如:

/etc/crypttab
luks-123abcdef-etc UUID=123abcdef-etc none discard

若手動使用命令解鎖設備,使用--allow-discards

例如,可使用--allow-discards手動打開設備,並手動執行fstrim命令:

# cryptsetup --allow-discards open /dev/sdaX root

總是可以通過dmsetup table的輸出判斷設備是否通過discard選項打開:

# dmsetup table
luks-123abcdef-etc: 0 1234567 crypt aes-xts-plain64 000etc000 0 8:2 4096 1 allow_discards

關閉工作隊列以提高SSD性能

[編輯 | 編輯原始碼]

固態硬碟用戶應當注意,在默認情況下,device mapper並未啟用關閉內部讀取、寫入工作隊列的功能。即,塊設備將不使用no_read_workqueueno_write_workqueue選項掛載。

在Cloudflare內部進行的關於總體磁碟加密性能的研究(Speeding up Linux disk encryption)中,提出了no_read_workqueueno_write_workqueue標誌。研究顯示,dm-crypt內部的讀取和寫入隊列降低了SSD的性能。儘管磁碟操作隊列對於機械硬碟適用,但對於SSD,禁用操作隊列,直接同步寫入數據可使吞吐量翻倍,並將IO操作等待延遲降低一半。相關補丁已經被上游接受,並在5.9及更新版本的linux中可用[5]

提示:linux-zen 默認關閉了dm-crypt工作隊列。

對於使用crypttab解密的LUKS設備,要關閉工作隊列,按需指定相應的no-read-workqueueno-write-workqueue選項。例如:

/etc/crypttab
luks-123abcdef-etc UUID=123abcdef-etc none no-read-workqueue

要同時關閉讀取和寫入工作隊列,同時指定兩個標誌:

/etc/crypttab
luks-123abcdef-etc UUID=123abcdef-etc none no-read-workqueue,no-write-workqueue

對於LUKS2加密的設備,可以使用--persistent選項,將--perf-no_read_workqueue--perf-no_write_workqueue設置為打開設備時的默認值。例如:

# cryptsetup --perf-no_read_workqueue --perf-no_write_workqueue --persistent open /dev/sdaX root

若設備已經打開,上述open操作將會報錯。此時,應當使用cryptsetup-refresh(8)命令。例如:

# cryptsetup --perf-no_read_workqueue --perf-no_write_workqueue --persistent refresh root

可通過cryptsetup luksDump輸出的LUKS2頭信息確保標誌已經持久性設置:

# cryptsetup luksDump /dev/sdaX | grep Flags
Flags:          no-read-workqueue

總是可以通過dmsetup table的輸出判斷為設備指定的選項:

# dmsetup table
luks-123abcdef-etc: 0 1234567 crypt aes-xts-plain64 000etc000 0 8:2 4096 1 no_read_workqueue
注意:通過cryptsetup --persistent設置新的持久性標誌時,將替代所有已設置的持久性標誌,而不是在原有基礎上新增一個標誌。故若還設置了其它標誌,需要一同指定。

例如,通過cryptsetup同時指定no_read_workqueueno_write_workqueue

# cryptsetup --perf-no_read_workqueue --perf-no_write_workqueue --persistent refresh root

可以通過dmsetup table的輸出判斷為設備指定的選項:

# cryptsetup luksDump /dev/sdaX | grep Flags
Flags:          no-read-workqueue no-write-workqueue

使用encrypt鉤子解密多個設備

[編輯 | 編輯原始碼]
注意:本節僅關注在早期用戶空間(initramfs階段)解密多個設備。對於後期用戶空間(在切換root後)解密的配置,參見Dm-crypt/系統配置#在後期用戶空間中解密
提示:sd-encrypt鉤子支持解密多個設備。可通過內核命令行或/etc/crypttab.initramfs指定需要解密的設備。詳見Dm-crypt/系統配置#使用systemd-cryptsetup-generator

encrypt鉤子僅支持單個cryptdevice=條目(archlinux/mkinitcpio/mkinitcpio#231)。在有多硬碟的系統中,由於dm-crypt自身不能跨越物理設備,需要配置多個加密物理分區,此時encrypt將造成限制。例如,對於「在LUKS上配置LVM」方案,整個LVM都存在於LUKS映射器之上。對於僅有單個硬碟的系統,只需解密一個設備,不會出現問題。但是,若之後想通過添加設備,增加LVM的大小呢?在不修改encrypt鉤子的情況下,無法實現這一目的。

之後的小節介紹了克服這一限制的替代方案。第一小節介紹了如何將「在LVM上配置LUKS」方案拓展到新增的硬碟上。第二小節介紹了,在不使用LVM的配置中,如何修改encrypt鉤子以解密多個磁碟。

將LVM拓展到多個磁碟上

[編輯 | 編輯原始碼]

管理多個磁碟,並以此提供分區靈活性,是LVM的特色功能。若將LVM作為dm-crypt的第一個映射器(first mapper),則LVM也可與dm-crypt共同使用(「在LVM上配置LUKS」)。在此配置中,加密設備在邏輯卷中創建(可能分別對每個加密卷使用不同的密鑰)。下面將介紹如何將本配置拓展到另一磁碟。

警告:請注意,雖然改變文件系統大小似乎是常規操作,操作過程中仍可能出現問題,同時,下面介紹的方式也可能不適用於某些特定配置。通常來講,擴容文件系統可能遇到的問題比收縮文件系統小,尤其是在類似本例的多層映射配置中。

添加新設備

[編輯 | 編輯原始碼]

首先,可能需要按照dm-crypt/準備磁碟中的步驟準備磁碟。 然後,創建用於拓展LVM的分區(例如,將設備所有空間分配到/dev/sdY1中,並設置分區類型為8E00 (Linux LVM))。 之後,將新磁碟/分區添加到已有的LVM卷組中,例如:

# pvcreate /dev/sdY1
# vgextend MyStorage /dev/sdY1

拓展邏輯卷

[編輯 | 編輯原始碼]

接下來,將最終分配新的磁碟空間到邏輯卷中。注意需要首先卸載要擴容的邏輯卷。如果需要擴容cryptdevice設定的根分區,需要在Arch安裝ISO中執行下述操作。

在本例中,假定需要拓展用於/home(邏輯卷名稱為homevol)的邏輯卷:

# umount /home
# fsck /dev/mapper/home
# cryptsetup close /dev/mapper/home
# lvextend -l +100%FREE MyStorage/homevol

在拓展邏輯卷後,拓展LUKS容器:

# cryptsetup open /dev/MyStorage/homevol home
# umount /home      # 在/home可能被自动挂载的情况下,作为安全措施
# cryptsetup --verbose resize home

最後,修改文件系統大小:

# e2fsck -f /dev/mapper/home
# resize2fs /dev/mapper/home

若需要,可重新掛載/home,其容量已被拓展:

# mount /dev/mapper/home /home

注意,cryptsetup resize操作不會影響密鑰。

修改encrypt鉤子以用於多個分區

[編輯 | 編輯原始碼]

注意,sd-encrypt原生支持解密多個分區。此外,若通過該方式解密的多個分區密碼相同,sd-encrypt還將自動嘗試輸入的密碼,而無需多次輸入。因此,使用sd-encrypt可能比下面介紹的方式更加方便。

跨越多個分區的根文件系統

[編輯 | 編輯原始碼]

可以通過修改encrypt鉤子以在啟動時解密跨越多個分區的根文件系統。一種方式如下:

# cp /usr/lib/initcpio/install/encrypt /etc/initcpio/install/encrypt2
# cp /usr/lib/initcpio/hooks/encrypt  /etc/initcpio/hooks/encrypt2
# sed -i "s/cryptdevice/cryptdevice2/" /etc/initcpio/hooks/encrypt2
# sed -i "s/cryptkey/cryptkey2/" /etc/initcpio/hooks/encrypt2

添加cryptdevice2=啟動選項(可能還需添加cryptkey2=選項),在mkinitcpio.conf中添加encrypt2鉤子,並重新生成initramfs。詳見Dm-crypt/系統配置

非根分區

[編輯 | 編輯原始碼]

可能還有使用encrypt鉤子解密非根分區的需求。Arch並不原生支持這一功能,但可通過修改/lib/initcpio/hooks/encrypt中的cryptdev、cryptname值方便地實現。(cryptdev對應要解密的分區,例如/dev/sd*;cryptname對應解密後的映射名稱)。

使用encrypt鉤子的優勢在於其自動化了非根分區的解密。雖然可通過/etc/crypttab文件達到相同目的,但若使用外部密鑰文件(即,密鑰文件不存儲在系統硬碟中,而在可行動裝置上),需要確保USB、火線(FireWire)等外部設備在嘗試掛載加密分區前被掛載,這至少需要修改/etc/fstab中的掛載順序,相關配置較為麻煩。

當然,若cryptsetup包更新,則需要重新修改上述腳本。此外,與/etc/crypttab不同,默認情況下僅能通過encrypt鉤子解密一個分區,但通過進一步修改,應當能實現解密多個分區的功能。

在加密系統時使用分離的LUKS頭

[編輯 | 編輯原始碼]

本例使用了與dm-crypt/加密整個系統#Plain_dm-crypt類似的設置,應當先閱讀其中的內容。

若使用分離的LUKS頭,則加密的塊設備自身將只含有加密數據,而不含LUKS頭信息。若攻擊者不知道分離的LUKS頭的存在,則可實現可抵賴加密。這與使用plain dm-crypt類似,但優勢在於仍可使用多密碼、密鑰派生等LUKS高級功能。此外,使用分離的LUKS頭還提供了某種程度上的雙重因素認證,並且相比使用GPG、OpenSSL加密密鑰文件更加簡單,同時可以使用內建的密碼提示符進行多次嘗試。詳見靜態數據加密#加密的元數據

在繼續之前,參見可用的加密選項。使用如下命令創建加密的系統分區和LUKS頭文件:

# dd if=/dev/zero of=header.img bs=16M count=1
# cryptsetup luksFormat --offset 32768 --header header.img /dev/sdX
提示:--offset選項指定了加密數據在設備上的起始位置。在設備開頭預留一定空間可便於之後將分離的LUKS頭重新附加到設備上。該選項的單位是512位元組的扇區,詳見cryptsetup-luksFormat(8)

打開加密容器:

# cryptsetup open --header header.img /dev/sdX enc

接下來,按照「在LUKS上配置LVM」中的步驟進行。之後,在可行動裝置上配置boot分區(若不將boot分區放置在可行動裝置上,使用分離的LUKS頭沒有意義)。

header.img移動到boot分區:

# mv header.img /mnt/boot

按照安裝指南中的步驟繼續安裝,直到配置mkinitcpio步驟前(此時應當已經arch-chroot到加密的系統中)。

提示:由於整個系統硬碟都只含有看似隨機的加密數據,沒有分區表、分區,因而沒有UUIDLABEL。但仍可通過id、路徑進行持久化命名。例如,可以使用硬碟id(可在/dev/disk/by-id/中查看)。

有兩種方法使得initramfs支持分離的LUKS頭。

使用systemd鉤子

[編輯 | 編輯原始碼]

設置如下內核參數

rd.luks.name=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX=enc rd.luks.options=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX=header=/header.img:UUID=ZZZZZZZZ-ZZZZ-ZZZZ-ZZZZ-ZZZZZZZZZZZZ rd.luks.data=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX=/dev/disk/by-id/your-disk_id
  • XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX替換為LUKS超級塊的UUID。該UUID可通過cryptsetup luksDump header.img或者blkid -s UUID -o value header.img獲得。
  • UUID=ZZZZZZZZ-ZZZZ-ZZZZ-ZZZZ-ZZZZZZZZZZZZ修改為存放分離的LUKS頭文件的塊設備的UUID。

除了指定rd.luks內核參數以外,還可通過/etc/crypttab.initramfs文件指定:

/etc/crypttab.initramfs
enc	/dev/disk/by-id/your-disk_id	none	header=/header.img:UUID=ZZZZZZZZ-ZZZZ-ZZZZ-ZZZZ-ZZZZZZZZZZZZ

接下來,修改/etc/mkinitcpio.conf使用systemd,並添加用於讀取分離的LUKS頭文件存儲的文件系統的模塊。例如,若存儲在FAT卷中:

/etc/mkinitcpio.conf
...
MODULES=(vfat)
...
HOOKS=(base systemd autodetect microcode modconf kms keyboard sd-vconsole block sd-encrypt lvm2 filesystems fsck)
...

重新生成 initramfs

注意:
  • 若使用了/etc/crypttab.initramfs進行配置,則無需添加上述內核參數。/etc/crypttab.initramfs將被複製到initramfs中的/etc/crypttab
  • 避免同時使用rd.luks內核參數和/etc/crypttab.initramfs文件,否則可能導致衝突。詳見Dm-crypt/系統配置#使用systemd-cryptsetup-generator

修改encrypt鉤子

[編輯 | 編輯原始碼]

下文介紹了如何修改encrypt鉤子,以便使用分離的LUKS頭。

目前,需要修改encrypt鉤子以使得cryptsetup使用分離的LUKS頭(archlinux/mkinitcpio/mkinitcpio#234;本方法的基本思路和框架來自該BBS帖子)。將修改鉤子的副本,以免mkinitcpio的更新覆蓋所做的改動。

# cp /usr/lib/initcpio/hooks/encrypt /etc/initcpio/hooks/encrypt2
# cp /usr/lib/initcpio/install/encrypt /etc/initcpio/install/encrypt2
/etc/initcpio/hooks/encrypt2 (around line 52)
warn_deprecated() {
    echo "The syntax 'root=${root}' where '${root}' is an encrypted volume is deprecated"
    echo "Use 'cryptdevice=${root}:root root=/dev/mapper/root' instead."
}

local headerFlag=false
for cryptopt in ${cryptoptions//,/ }; do
    case ${cryptopt} in
        allow-discards)
            cryptargs="${cryptargs} --allow-discards"
            ;;
        header)
            cryptargs="${cryptargs} --header /boot/header.img"
            headerFlag=true
            ;;
        *)
            echo "Encryption option '${cryptopt}' not known, ignoring." >&2
            ;;
    esac
done

if resolved=$(resolve_device "${cryptdev}" ${rootdelay}); then
    if $headerFlag || cryptsetup isLuks ${resolved} >/dev/null 2>&1; then
        [ ${DEPRECATED_CRYPT} -eq 1 ] && warn_deprecated
        dopassphrase=1

之後,修改mkinitcpio.conf,添加encrypt2lvm2鉤子。同時還需在FILES中添加header.img;在MODULES中添加loop。並按需添加其它所需內容:

/etc/mkinitcpio.conf
...
MODULES=(loop)
...
FILES=(/boot/header.img)
...
HOOKS=(base udev autodetect microcode modconf kms keyboard keymap consolefont block encrypt2 lvm2 filesystems fsck)
...

上述配置通過將LUKS頭文件複製到initramfs中,使得LUKS頭在啟動過程中可用,以便解密系統,而無需再掛載另一個USB設備以獲取LUKS頭文件,降低了配置的複雜性。配置完成後,重新生成initramfs。

接下來,配置引導加載程序,指定cryptdevice=,並傳遞新增的header選項:

cryptdevice=/dev/disk/by-id/your-disk_id:enc:header

對於在USB存儲介質上配置的/boot分區,可參見dm-crypt/加密整個系統#安裝完成後的操作