檔案系統級別加密 (fscrypt)

簡介

fscrypt 是一個庫,檔案系統可以掛鉤到該庫中以支援檔案和目錄的透明加密。

注意:本文件中的“fscrypt”指的是核心級別的部分,在 fs/crypto/ 中實現,而不是使用者空間工具 fscrypt。本文件僅涵蓋核心級別的部分。有關如何使用加密的命令列示例,請參閱使用者空間工具 fscrypt 的文件。此外,建議使用 fscrypt 使用者空間工具或其他現有的使用者空間工具,如 fscryptctlAndroid 的金鑰管理系統,而不是直接使用核心的 API。使用現有工具可以減少引入您自己的安全漏洞的機會。(不過,為了完整起見,本文件仍然涵蓋了核心的 API。)

與 dm-crypt 不同,fscrypt 在檔案系統級別執行,而不是在塊裝置級別執行。這使得它可以使用不同的金鑰加密不同的檔案,並且在同一檔案系統上擁有未加密的檔案。這對於多使用者系統非常有用,在多使用者系統中,每個使用者的靜態資料都需要與其他使用者進行加密隔離。但是,除了檔名之外,fscrypt 不加密檔案系統元資料。

與 eCryptfs(一種堆疊檔案系統)不同,fscrypt 直接整合到受支援的檔案系統中——目前是 ext4、F2FS、UBIFS 和 CephFS。這允許讀取和寫入加密檔案,而無需在頁快取中快取解密和加密的頁面,從而幾乎減少了一半的記憶體使用量,並使其與未加密檔案保持一致。同樣,只需要一半數量的 dentry 和 inode。eCryptfs 還將加密檔名限制為 143 位元組,從而導致應用程式相容性問題;fscrypt 允許完整的 255 位元組 (NAME_MAX)。最後,與 eCryptfs 不同,fscrypt API 可以被非特權使用者使用,而無需掛載任何東西。

fscrypt 不支援就地加密檔案。相反,它支援將空目錄標記為加密。然後,在使用者空間提供金鑰後,在該目錄樹中建立的所有常規檔案、目錄和符號連結都會被透明地加密。

威脅模型

離線攻擊

如果使用者空間選擇了一個強加密金鑰,那麼在塊裝置內容在單個時間點永久離線洩露的情況下,fscrypt 可以保護檔案內容和檔名的機密性。fscrypt 不保護非檔名元資料的機密性,例如檔案大小、檔案許可權、檔案時間戳和擴充套件屬性。此外,檔案中空洞(邏輯上包含所有零的未分配塊)的存在和位置不受保護。

如果攻擊者能夠在授權使用者稍後訪問檔案系統之前離線操縱檔案系統,則 fscrypt 不能保證保護機密性或真實性。

線上攻擊

fscrypt(以及一般的儲存加密)只能提供有限的線上攻擊保護。詳細資訊:

旁道攻擊

fscrypt 對旁道攻擊(如定時或電磁攻擊)的抵抗力僅限於底層 Linux 加密 API 演算法或內聯加密硬體的抵抗力。如果使用了易受攻擊的演算法,如基於表的 AES 實現,則攻擊者可能會對線上系統發起旁道攻擊。旁道攻擊也可能針對使用解密資料的應用程式發起。

未經授權的檔案訪問

新增加密金鑰後,fscrypt 不會向同一系統上的其他使用者隱藏純文字檔案內容或檔名。相反,應為此目的使用現有的訪問控制機制,如檔案模式位、POSIX ACL、LSM 或名稱空間。

(對於此背後的原因,請理解,在新增金鑰時,從系統本身的角度來看,資料的機密性不是由加密的數學屬性來保護的,而是僅由核心的正確性來保護的。因此,任何加密特定的訪問控制檢查都只會由核心程式碼強制執行,因此與已經存在的各種訪問控制機制在很大程度上是多餘的。)

只讀核心記憶體洩露

除非使用 硬體封裝的金鑰,否則能夠讀取任意核心記憶體的攻擊者(例如,透過發起物理攻擊或利用核心安全漏洞)可能會洩露所有當前正在使用的 fscrypt 金鑰。這也會擴充套件到冷啟動攻擊;如果系統突然斷電,系統使用的金鑰可能會在記憶體中保留一小段時間。

但是,如果使用硬體封裝的金鑰,則 fscrypt 主金鑰和檔案內容加密金鑰(而不是其他型別的 fscrypt 子金鑰,如檔名加密金鑰)將受到保護,免受任意核心記憶體洩露的影響。

此外,fscrypt 允許從核心中刪除加密金鑰,這可以保護它們免受以後的洩露。

更詳細地說,FS_IOC_REMOVE_ENCRYPTION_KEY ioctl(或 FS_IOC_REMOVE_ENCRYPTION_KEY_ALL_USERS ioctl)可以從核心記憶體中擦除主加密金鑰。如果這樣做,它還會嘗試驅逐所有已使用該金鑰“解鎖”的快取 inode,從而擦除其每個檔案的金鑰,並使它們再次顯示為“已鎖定”,即以密文或加密形式顯示。

但是,這些 ioctl 有一些限制:

  • 對於正在使用的檔案,每個檔案的金鑰將不會被刪除或擦除。因此,為了獲得最大效果,使用者空間應在刪除主金鑰之前關閉相關的加密檔案和目錄,並終止任何工作目錄位於受影響的加密目錄中的程序。

  • 核心無法神奇地擦除使用者空間可能也擁有的主金鑰副本。因此,使用者空間還必須擦除它製作的所有主金鑰副本;通常應該在 FS_IOC_ADD_ENCRYPTION_KEY 後立即執行此操作,而無需等待 FS_IOC_REMOVE_ENCRYPTION_KEY。當然,這同樣適用於金鑰層次結構中的所有更高級別。使用者空間還應遵循其他安全預防措施,例如 mlock() 記憶體包含金鑰,以防止其被換出。

  • 一般來說,核心 VFS 快取中解密的內容和檔名會被釋放,但不會被擦除。因此,即使在擦除相應的金鑰之後,也可以從釋放的記憶體中恢復其中的一部分。為了部分解決這個問題,您可以在核心命令列中新增 init_on_free=1。但是,這會帶來效能成本。

  • 金鑰可能仍然存在於 CPU 暫存器、加密加速器硬體(如果加密 API 用於實現任何演算法)或其他未在此處明確考慮的地方。

完全系統洩露

獲得“root”訪問許可權和/或執行任意核心程式碼能力的攻擊者可以自由地洩露受任何正在使用的 fscrypt 金鑰保護的資料。因此,通常 fscrypt 在這種情況下不提供有意義的保護。(受在整個攻擊過程中不存在的金鑰保護的資料仍然受到保護,但受上述金鑰刪除的限制影響,如果金鑰在攻擊之前被刪除。)

但是,如果使用 硬體封裝的金鑰,則此類攻擊者將無法以在系統斷電後可用的形式洩露主金鑰或檔案內容金鑰。如果攻擊者受到嚴重的時間限制和/或頻寬限制,因此他們只能洩露一些資料,並且需要依賴以後的離線攻擊來洩露其餘資料,這可能會很有用。

v1 策略的限制

v1 加密策略在線上攻擊方面存在一些弱點:

  • 無法驗證提供的主金鑰是否正確。因此,惡意使用者可以暫時將錯誤的金鑰與他們具有隻讀訪問許可權的另一個使用者的加密檔案相關聯。由於檔案系統快取,即使其他使用者的金鑰鏈中有正確的金鑰,該錯誤金鑰也會被其他使用者訪問這些檔案時使用。這違反了“只讀訪問”的含義。

  • 每個檔案的金鑰洩露也會洩露從中派生的主金鑰。

  • 非 root 使用者無法安全地刪除加密金鑰。

以上所有問題都透過 v2 加密策略得到修復。由於此原因和其他原因,建議在所有新的加密目錄中使用 v2 加密策略。

金鑰層次結構

注意:本節假設使用原始金鑰而不是硬體封裝的金鑰。使用硬體封裝的金鑰會稍微修改金鑰層次結構。有關詳細資訊,請參閱 硬體封裝的金鑰

主金鑰

每個加密目錄樹都由主金鑰保護。主金鑰的長度可達 64 位元組,並且必須至少與所使用的內容和檔名加密模式的安全性強度一樣長。例如,如果使用任何 AES-256 模式,則主金鑰必須至少為 256 位,即 32 位元組。如果金鑰被 v1 加密策略使用並且使用了 AES-256-XTS,則適用更嚴格的要求;此類金鑰必須為 64 位元組。

要“解鎖”加密目錄樹,使用者空間必須提供適當的主金鑰。主金鑰的數量可以是任意數量,每個主金鑰保護任意數量的檔案系統上的任意數量的目錄樹。

主金鑰必須是真正的加密金鑰,即與相同長度的隨機位元組串無法區分。這意味著使用者不得直接使用密碼作為主金鑰、零填充較短的金鑰或重複較短的金鑰。如果使用者空間犯了任何此類錯誤,則無法保證安全性,因為加密證明和分析將不再適用。

相反,使用者應使用加密安全的隨機數生成器或使用 KDF(金鑰派生函式)生成主金鑰。核心不執行任何金鑰拉伸;因此,如果使用者空間從低熵機密(如密碼)派生金鑰,則必須使用為此目的設計的 KDF,如 scrypt、PBKDF2 或 Argon2。

金鑰派生函式

除了一個例外,fscrypt 永遠不會直接使用主金鑰進行加密。相反,它們僅用作 KDF(金鑰派生函式)的輸入,以派生實際金鑰。

用於特定主金鑰的 KDF 因金鑰是用於 v1 加密策略還是用於 v2 加密策略而異。使用者不得將同一金鑰用於 v1 和 v2 加密策略。(目前尚不清楚針對此特定金鑰重用案例的真實攻擊,但無法保證其安全性,因為加密證明和分析將不再適用。)

對於 v1 加密策略,KDF 僅支援派生每個檔案的加密金鑰。它的工作原理是使用 AES-128-ECB 加密主金鑰,使用檔案的 16 位元組 nonce 作為 AES 金鑰。生成的密文用作派生金鑰。如果密文比需要的長,則將其截斷到需要的長度。

對於 v2 加密策略,KDF 是 HKDF-SHA512。主金鑰作為“輸入金鑰材料”傳遞,不使用 salt,並且為每個要派生的不同金鑰使用不同的“特定於應用程式的資訊字串”。例如,當派生每個檔案的加密金鑰時,特定於應用程式的資訊字串是檔案的 nonce,字首為“fscrypt\0”和一個上下文位元組。不同的上下文位元組用於其他型別的派生金鑰。

HKDF-SHA512 優於原始的基於 AES-128-ECB 的 KDF,因為 HKDF 更靈活、不可逆,並且均勻分佈來自主金鑰的熵。HKDF 也已標準化並被其他軟體廣泛使用,而基於 AES-128-ECB 的 KDF 是臨時的。

每個檔案的加密金鑰

由於每個主金鑰可以保護許多檔案,因此有必要“調整”每個檔案的加密,以使兩個檔案中的相同純文字不會對映到相同的密文,反之亦然。在大多數情況下,fscrypt 透過派生每個檔案的金鑰來做到這一點。當建立一個新的加密 inode(常規檔案、目錄或符號連結)時,fscrypt 隨機生成一個 16 位元組的 nonce 並將其儲存在 inode 的加密 xattr 中。然後,它使用 KDF(如 金鑰派生函式 中所述)從主金鑰和 nonce 派生檔案的金鑰。

選擇金鑰派生而不是金鑰包裝,因為包裝金鑰需要更大的 xattr,這些 xattr 更不可能內聯地容納在檔案系統的 inode 表中,並且金鑰包裝似乎沒有任何明顯的優勢。特別是,目前沒有要求支援使用多個備用主金鑰解鎖檔案或支援輪換主金鑰。相反,主金鑰可以在使用者空間中包裝,例如 fscrypt 工具所做的那樣。

DIRECT_KEY 策略

Adiantum 加密模式(請參閱 加密模式和用法)適用於內容和檔名加密,並且它接受長 IV——足夠長以容納 8 位元組的資料單元索引和 16 位元組的每個檔案 nonce。此外,每個 Adiantum 金鑰的開銷大於 AES-256-XTS 金鑰的開銷。

因此,為了提高效能並節省記憶體,對於 Adiantum,支援“直接金鑰”配置。當用戶透過在 fscrypt 策略中設定 FSCRYPT_POLICY_FLAG_DIRECT_KEY 來啟用此功能時,不使用每個檔案的加密金鑰。相反,每當加密任何資料(內容或檔名)時,檔案的 16 位元組 nonce 都包含在 IV 中。而且:

  • 對於 v1 加密策略,加密直接使用主金鑰完成。因此,使用者不得將同一主金鑰用於任何其他目的,甚至用於其他 v1 策略。

  • 對於 v2 加密策略,加密使用使用 KDF 派生的每個模式金鑰完成。使用者可以將同一主金鑰用於其他 v2 加密策略。

IV_INO_LBLK_64 策略

當在 fscrypt 策略中設定 FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 時,加密金鑰從主金鑰、加密模式編號和檔案系統 UUID 派生。這通常會導致受同一主金鑰保護的所有檔案共享單個內容加密金鑰和單個檔名加密金鑰。為了仍然以不同的方式加密不同檔案的資料,inode 編號包含在 IV 中。因此,可能不允許縮小檔案系統。

此格式針對與符合 UFS 標準的內聯加密硬體一起使用進行了最佳化,該標準僅支援每個 I/O 請求 64 位 IV,並且可能只有少量金鑰槽。

IV_INO_LBLK_32 策略

IV_INO_LBLK_32 策略的工作方式與 IV_INO_LBLK_64 類似,只是對於 IV_INO_LBLK_32,inode 編號使用 SipHash-2-4(其中 SipHash 金鑰從主金鑰派生)進行雜湊處理,並新增到檔案資料單元索引 mod 2^32 以生成 32 位 IV。

此格式針對與符合 eMMC v5.2 標準的內聯加密硬體一起使用進行了最佳化,該標準僅支援每個 I/O 請求 32 位 IV,並且可能只有少量金鑰槽。此格式會導致一定程度的 IV 重用,因此僅應在由於硬體限制而必要時使用。

金鑰識別符號

對於用於 v2 加密策略的主金鑰,還會使用 KDF 派生一個唯一的 16 位元組“金鑰識別符號”。此值以明文形式儲存,因為它需要可靠地標識金鑰本身。

Dirhash 金鑰

對於使用純文字檔名上的秘密金鑰 dirhash 索引的目錄,KDF 還用於為每個目錄派生一個 128 位 SipHash-2-4 金鑰,以便對檔名進行雜湊處理。這與派生每個檔案的加密金鑰的工作方式相同,只是使用了不同的 KDF 上下文。目前,只有區分大小寫(“不區分大小寫”)的加密目錄使用這種雜湊風格。

加密模式和用法

fscrypt 允許為檔案內容指定一種加密模式,併為檔名指定一種加密模式。允許不同的目錄樹使用不同的加密模式。

支援的模式

目前,支援以下加密模式對:

  • 用於內容的 AES-256-XTS 和用於檔名的 AES-256-CBC-CTS

  • 用於內容的 AES-256-XTS 和用於檔名的 AES-256-HCTR2

  • Adiantum 用於內容和檔名

  • 用於內容的 AES-128-CBC-ESSIV 和用於檔名的 AES-128-CBC-CTS

  • 用於內容的 SM4-XTS 和用於檔名的 SM4-CBC-CTS

注意:在 API 中,“CBC”表示 CBC-ESSIV,“CTS”表示 CBC-CTS。因此,例如,FSCRYPT_MODE_AES_256_CTS 表示 AES-256-CBC-CTS。

由於難以處理密文擴充套件,因此目前不支援經過身份驗證的加密模式。因此,內容加密在 XTS 模式CBC-ESSIV 模式中使用塊密碼,或者使用寬塊密碼。檔名加密在 CBC-CTS 模式中使用塊密碼或寬塊密碼。

(AES-256-XTS, AES-256-CBC-CTS)對是推薦的預設設定。它也是保證如果核心完全支援 fscrypt 則始終支援的唯一選項;請參閱 核心配置選項

(AES-256-XTS, AES-256-HCTR2)對也是一個不錯的選擇,可以將檔名加密升級為使用寬塊密碼。(寬塊密碼,也稱為可調整的超偽隨機置換,具有更改一位會擾亂整個結果的屬性。)如 檔名加密 中所述,寬塊密碼是問題域的理想模式,儘管 CBC-CTS 是替代方案中“最不壞”的選擇。有關 HCTR2 的更多資訊,請參閱 HCTR2 論文

建議在由於 AES 缺乏 AES 硬體加速而速度太慢的系統上使用 Adiantum。Adiantum 是一種寬塊密碼,它使用 XChaCha12 和 AES-256 作為其底層元件。大部分工作由 XChaCha12 完成,當 AES 加速不可用時,XChaCha12 比 AES 快得多。有關 Adiantum 的更多資訊,請參閱 Adiantum 論文

(AES-128-CBC-ESSIV, AES-128-CBC-CTS)對的存在只是為了支援唯一的 AES 加速形式是非 CPU 加密加速器(如 CAAM 或 CESA)且不支援 XTS 的系統。

其餘的模式對是“國家自豪密碼”:

  • (SM4-XTS, SM4-CBC-CTS)

一般來說,這些密碼本身並不是“壞”的,但與 AES 和 ChaCha 等常用選擇相比,它們的安全審查有限。它們也沒有帶來太多新東西。建議僅在強制要求使用這些密碼時才使用它們。

核心配置選項

啟用 fscrypt 支援 (CONFIG_FS_ENCRYPTION) 會自動拉入使用 AES-256-XTS 和 AES-256-CBC-CTS 加密所需的基礎加密 API 支援。為了獲得最佳效能,強烈建議啟用任何可用的平臺特定的 kconfig 選項,這些選項為希望使用的演算法提供加速。對任何“非預設”加密模式的支援通常也需要額外的 kconfig 選項。

下面,一些相關的選項按加密模式列出。請注意,未在下面列出的加速選項可能適用於您的平臺;請參閱 kconfig 選單。還可以將檔案內容加密配置為使用內聯加密硬體而不是核心加密 API(請參閱 內聯加密支援);在這種情況下,檔案內容模式不需要在核心加密 API 中支援,但檔名模式仍然需要。

  • AES-256-XTS 和 AES-256-CBC-CTS
    • 推薦:
      • arm64:CONFIG_CRYPTO_AES_ARM64_CE_BLK

      • x86:CONFIG_CRYPTO_AES_NI_INTEL

  • AES-256-HCTR2
    • 必需:
      • CONFIG_CRYPTO_HCTR2

    • 推薦:
      • arm64:CONFIG_CRYPTO_AES_ARM64_CE_BLK

      • arm64:CONFIG_CRYPTO_POLYVAL_ARM64_CE

      • x86:CONFIG_CRYPTO_AES_NI_INTEL

      • x86:CONFIG_CRYPTO_POLYVAL_CLMUL_NI

  • Adiantum
    • 必需:
      • CONFIG_CRYPTO_ADIANTUM

    • 推薦:
      • arm32:CONFIG_CRYPTO_NHPOLY1305_NEON

      • arm64:CONFIG_CRYPTO_NHPOLY1305_NEON

      • x86:CONFIG_CRYPTO_NHPOLY1305_SSE2

      • x86:CONFIG_CRYPTO_NHPOLY1305_AVX2

  • AES-128-CBC-ESSIV 和 AES-128-CBC-CTS
    • 必需:
      • CONFIG_CRYPTO_ESSIV

      • CONFIG_CRYPTO_SHA256 或另一個 SHA-256 實現

    • 推薦:
      • AES-CBC 加速

fscrypt 還使用 HMAC-SHA512 進行金鑰派生,因此建議啟用 SHA-512 加速:

  • SHA-512
    • 推薦:
      • arm64:CONFIG_CRYPTO_SHA512_ARM64_CE

      • x86:CONFIG_CRYPTO_SHA512_SSSE3

內容加密

對於內容加密,每個檔案的內容被分成“資料單元”。每個資料單元都是獨立加密的。每個資料單元的 IV 包含資料單元在檔案中的從零開始的索引。這確保了檔案中的每個資料單元都被不同地加密,這對於防止資訊洩露至關重要。

注意:依賴於檔案偏移量的加密意味著不支援像“摺疊範圍”和“插入範圍”這樣重新排列檔案盤區對映的操作加密檔案。

資料單元的大小有兩種情況:

  • 固定大小的資料單元。這是除 UBIFS 之外的所有檔案系統的工作方式。檔案的所有資料單元大小相同;如果需要,最後一個數據單元會進行零填充。預設情況下,資料單元大小等於檔案系統塊大小。在某些檔案系統上,使用者可以透過加密策略的 log2_data_unit_size 欄位選擇子塊資料單元大小;請參閱 FS_IOC_SET_ENCRYPTION_POLICY

  • 可變大小的資料單元。這是 UBIFS 的工作方式。每個“UBIFS 資料節點”都被視為一個加密資料單元。每個資料單元包含可變長度的可能壓縮的資料,零填充到下一個 16 位元組邊界。使用者無法在 UBIFS 上選擇子塊資料單元大小。

在壓縮 + 加密的情況下,壓縮的資料會被加密。UBIFS 壓縮如上所述工作。f2fs 壓縮的工作方式略有不同;它將許多檔案系統塊壓縮為較少的檔案系統塊。因此,f2fs 壓縮的檔案仍然使用固定大小的資料單元,並且它的加密方式與包含空洞的檔案類似。

金鑰層次結構 中所述,預設加密設定使用每個檔案的金鑰。在這種情況下,每個資料單元的 IV 只是資料單元在檔案中的索引。但是,使用者可以選擇不使用每個檔案的金鑰的加密設定。對於這些設定,某種檔案識別符號按如下方式包含在 IV 中:

  • 使用 DIRECT_KEY 策略,資料單元索引放置在 IV 的 0-63 位中,檔案的 nonce 放置在 64-191 位中。

  • 使用 IV_INO_LBLK_64 策略,資料單元索引放置在 IV 的 0-31 位中,檔案的 inode 編號放置在 32-63 位中。僅當資料單元索引和 inode 編號適合 32 位時,才允許此設定。

  • 使用 IV_INO_LBLK_32 策略,檔案的 inode 編號經過雜湊處理並新增到資料單元索引中。結果值被截斷為 32 位並放置在 IV 的 0-31 位中。僅當資料單元索引和 inode 編號適合 32 位時,才允許此設定。

IV 的位元組順序始終為小端序。

如果使用者為內容模式選擇 FSCRYPT_MODE_AES_128_CBC,則會自動包含 ESSIV 層。在這種情況下,在將 IV 傳遞給 AES-128-CBC 之前,它會使用 AES-256 進行加密,其中 AES-256 金鑰是檔案內容加密金鑰的 SHA-256 雜湊。

檔名加密

對於檔名,每個完整的檔名一次加密。由於需要保留對高效目錄查詢和最大 255 位元組的檔名的支援,因此同一 IV 用於目錄中的每個檔名。

但是,每個加密目錄仍然使用唯一的金鑰,或者可替代地,IV 中包含檔案的 nonce(對於 DIRECT_KEY 策略)或 inode 編號(對於 IV_INO_LBLK_64 策略)。因此,IV 重用僅限於單個目錄內。

對於 CBC-CTS,IV 重用意味著當純文字檔名共享至少與密碼塊大小(對於 AES 為 16 位元組)一樣長的公共字首時,相應的加密檔名也將共享一個公共字首。這是不可取的。Adiantum 和 HCTR2 沒有這種弱點,因為它們是寬塊加密模式。

所有支援的檔名加密模式都接受任何明文長度 >= 16 位元組;不需要密碼塊對齊。但是,短於 16 位元組的檔名在加密之前會被 NUL 填充到 16 位元組。此外,為了減少透過檔名密文洩漏檔名長度,所有檔名都會被 NUL 填充到下一個 4、8、16 或 32 位元組邊界(可配置)。建議使用 32,因為它提供最佳的機密性,但代價是目錄條目會佔用稍微更多的空間。請注意,由於 NUL (\0) 在檔名中不是有效字元,因此填充永遠不會產生重複的明文。

符號連結目標被視為一種檔名,並以與目錄條目中的檔名相同的方式加密,但 IV 重用不是問題,因為每個符號連結都有自己的 inode。

使用者 API

設定加密策略

FS_IOC_SET_ENCRYPTION_POLICY

FS_IOC_SET_ENCRYPTION_POLICY ioctl 在一個空目錄上設定加密策略,或者驗證目錄或常規檔案是否已具有指定的加密策略。它接受一個指向 struct fscrypt_policy_v1 或 struct fscrypt_policy_v2 的指標,定義如下

#define FSCRYPT_POLICY_V1               0
#define FSCRYPT_KEY_DESCRIPTOR_SIZE     8
struct fscrypt_policy_v1 {
        __u8 version;
        __u8 contents_encryption_mode;
        __u8 filenames_encryption_mode;
        __u8 flags;
        __u8 master_key_descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE];
};
#define fscrypt_policy  fscrypt_policy_v1

#define FSCRYPT_POLICY_V2               2
#define FSCRYPT_KEY_IDENTIFIER_SIZE     16
struct fscrypt_policy_v2 {
        __u8 version;
        __u8 contents_encryption_mode;
        __u8 filenames_encryption_mode;
        __u8 flags;
        __u8 log2_data_unit_size;
        __u8 __reserved[3];
        __u8 master_key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
};

必須按如下方式初始化此結構

  • 如果使用 struct fscrypt_policy_v1,則 version 必須為 FSCRYPT_POLICY_V1 (0);如果使用 struct fscrypt_policy_v2,則必須為 FSCRYPT_POLICY_V2 (2)。(注意:我們將原始策略版本稱為“v1”,儘管它的版本程式碼實際上是 0。)對於新的加密目錄,請使用 v2 策略。

  • contents_encryption_modefilenames_encryption_mode 必須設定為來自 <linux/fscrypt.h> 的常量,這些常量標識要使用的加密模式。如果不確定,請為 contents_encryption_mode 使用 FSCRYPT_MODE_AES_256_XTS (1),為 filenames_encryption_mode 使用 FSCRYPT_MODE_AES_256_CTS (4)。有關詳細資訊,請參閱加密模式和用法

    v1 加密策略僅支援三種模式組合:(FSCRYPT_MODE_AES_256_XTS, FSCRYPT_MODE_AES_256_CTS)、(FSCRYPT_MODE_AES_128_CBC, FSCRYPT_MODE_AES_128_CTS) 和 (FSCRYPT_MODE_ADIANTUM, FSCRYPT_MODE_ADIANTUM)。v2 策略支援支援的模式中記錄的所有組合。

  • flags 包含來自 <linux/fscrypt.h> 的可選標誌

    • FSCRYPT_POLICY_FLAGS_PAD_*: 加密檔名時要使用的 NUL 填充量。如果不確定,請使用 FSCRYPT_POLICY_FLAGS_PAD_32 (0x3)。

    • FSCRYPT_POLICY_FLAG_DIRECT_KEY:請參閱DIRECT_KEY 策略

    • FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64:請參閱IV_INO_LBLK_64 策略

    • FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32:請參閱IV_INO_LBLK_32 策略

    v1 加密策略僅支援 PAD_* 和 DIRECT_KEY 標誌。其他標誌僅受 v2 加密策略支援。

    DIRECT_KEY、IV_INO_LBLK_64 和 IV_INO_LBLK_32 標誌是互斥的。

  • log2_data_unit_size 是資料單元大小的 log2(以位元組為單位),或者為 0 以選擇預設資料單元大小。資料單元大小是檔案內容加密的粒度。例如,將 log2_data_unit_size 設定為 12 會導致檔案內容以 4096 位元組的資料單元傳遞給底層加密演算法(例如 AES-256-XTS),每個資料單元都有自己的 IV。

    並非所有檔案系統都支援設定 log2_data_unit_size。ext4 和 f2fs 自 Linux v6.7 起支援它。在支援它的檔案系統上,支援的非零值包括 9 到檔案系統塊大小的 log2(包括邊界值)。預設值 0 選擇檔案系統塊大小。

    log2_data_unit_size 的主要用例是選擇小於檔案系統塊大小的資料單元大小,以便與僅支援較小資料單元大小的內聯加密硬體相容。/sys/block/$disk/queue/crypto/ 可能有助於檢查特定系統的內聯加密硬體支援哪些資料單元大小。

    除非您確定需要它,否則將此欄位留空。使用不必要的小資料單元大小會降低效能。

  • 對於 v2 加密策略,__reserved 必須為零。

  • 對於 v1 加密策略,master_key_descriptor 指定如何在金鑰環中查詢主金鑰;請參閱新增金鑰。由使用者空間為每個主金鑰選擇唯一的 master_key_descriptor。e4crypt 和 fscrypt 工具使用 SHA-512(SHA-512(master_key)) 的前 8 個位元組,但不需要此特定方案。此外,執行 FS_IOC_SET_ENCRYPTION_POLICY 時,主金鑰不必已經在金鑰環中。但是,必須先新增它,然後才能在加密目錄中建立任何檔案。

    對於 v2 加密策略,master_key_descriptor 已被 master_key_identifier 替換,後者更長並且不能任意選擇。相反,必須首先使用FS_IOC_ADD_ENCRYPTION_KEY新增金鑰。然後,核心在 struct fscrypt_add_key_arg 中返回的 key_spec.u.identifier 必須用作 struct fscrypt_policy_v2 中的 master_key_identifier

如果檔案尚未加密,則 FS_IOC_SET_ENCRYPTION_POLICY 驗證該檔案是否為空目錄。如果是,則將指定的加密策略分配給目錄,將其轉換為加密目錄。之後,在提供新增金鑰中描述的相應主金鑰之後,在該目錄中建立的所有常規檔案、目錄(遞迴)和符號連結都將被加密,並繼承相同的加密策略。目錄條目中的檔名也將被加密。

或者,如果檔案已經被加密,則 FS_IOC_SET_ENCRYPTION_POLICY 驗證指定的加密策略是否與實際的加密策略完全匹配。如果它們匹配,則 ioctl 返回 0。否則,它將失敗並顯示 EEXIST。這適用於常規檔案和目錄,包括非空目錄。

當 v2 加密策略被分配給目錄時,還需要當前使用者已新增指定的金鑰,或者呼叫者在初始使用者名稱空間中具有 CAP_FOWNER。(這是為了防止使用者使用其他使用者的金鑰加密他們的資料。)金鑰必須在 FS_IOC_SET_ENCRYPTION_POLICY 執行期間保持新增狀態。但是,如果新的加密目錄不需要立即訪問,則可以在之後立即刪除金鑰。

請注意,即使根目錄為空,ext4 檔案系統也不允許加密根目錄。想要使用一個金鑰加密整個檔案系統的使用者應該考慮使用 dm-crypt。

FS_IOC_SET_ENCRYPTION_POLICY 可能會因以下錯誤而失敗

  • EACCES: 該檔案不屬於程序的 uid,並且該程序在具有檔案所有者的 uid 對映的名稱空間中也沒有 CAP_FOWNER 能力

  • EEXIST: 該檔案已經使用與指定的加密策略不同的加密策略進行了加密

  • EINVAL: 指定了無效的加密策略(無效的版本、模式或標誌;或者設定了保留位);或者指定了 v1 加密策略,但目錄啟用了不區分大小寫標誌(不區分大小寫與 v1 策略不相容)。

  • ENOKEY: 指定了 v2 加密策略,但是具有指定的 master_key_identifier 的金鑰尚未新增,並且該程序在初始使用者名稱空間中也沒有 CAP_FOWNER 能力

  • ENOTDIR: 該檔案未加密,並且是常規檔案,而不是目錄

  • ENOTEMPTY: 該檔案未加密,並且是非空目錄

  • ENOTTY: 此型別的檔案系統未實現加密

  • EOPNOTSUPP: 核心未配置對檔案系統的加密支援,或者檔案系統超級塊未啟用加密。(例如,要在 ext4 檔案系統上使用加密,必須在核心配置中啟用 CONFIG_FS_ENCRYPTION,並且必須使用 tune2fs -O encryptmkfs.ext4 -O encrypt 在超級塊上啟用“encrypt”功能標誌。)

  • EPERM: 此目錄可能無法加密,例如,因為它是一個 ext4 檔案系統的根目錄

  • EROFS: 檔案系統是隻讀的

獲取加密策略

有兩個 ioctl 可用於獲取檔案的加密策略

ioctl 的擴充套件 (_EX) 版本更通用,建議儘可能使用。但是,在較舊的核心上,只有原始 ioctl 可用。應用程式應嘗試擴充套件版本,如果它失敗並顯示 ENOTTY,則回退到原始版本。

FS_IOC_GET_ENCRYPTION_POLICY_EX

FS_IOC_GET_ENCRYPTION_POLICY_EX ioctl 檢索目錄或常規檔案的加密策略(如果有)。除了開啟檔案的能力之外,不需要額外的許可權。它接受一個指向 struct fscrypt_get_policy_ex_arg 的指標,定義如下

struct fscrypt_get_policy_ex_arg {
        __u64 policy_size; /* input/output */
        union {
                __u8 version;
                struct fscrypt_policy_v1 v1;
                struct fscrypt_policy_v2 v2;
        } policy; /* output */
};

呼叫者必須將 policy_size 初始化為策略結構可用的空間大小,即 sizeof(arg.policy)

成功後,策略結構將在 policy 中返回,其實際大小將在 policy_size 中返回。應檢查 policy.version 以確定返回的策略版本。請注意,“v1”策略的版本程式碼實際上是 0 (FSCRYPT_POLICY_V1)。

FS_IOC_GET_ENCRYPTION_POLICY_EX 可能會因以下錯誤而失敗

  • EINVAL: 檔案已加密,但它使用無法識別的加密策略版本

  • ENODATA: 檔案未加密

  • ENOTTY: 此型別的檔案系統未實現加密,或者此核心太舊而無法支援 FS_IOC_GET_ENCRYPTION_POLICY_EX(請嘗試 FS_IOC_GET_ENCRYPTION_POLICY)

  • EOPNOTSUPP: 核心未配置對此檔案系統的加密支援,或者檔案系統超級塊未啟用加密

  • EOVERFLOW: 檔案已加密並使用可識別的加密策略版本,但策略結構不適合提供的緩衝區

注意:如果您只需要知道檔案是否已加密,則在大多數檔案系統上,也可以使用 FS_IOC_GETFLAGS ioctl 並檢查 FS_ENCRYPT_FL,或者使用 statx() 系統呼叫並在 stx_attributes 中檢查 STATX_ATTR_ENCRYPTED。

FS_IOC_GET_ENCRYPTION_POLICY

FS_IOC_GET_ENCRYPTION_POLICY ioctl 也可以檢索目錄或常規檔案的加密策略(如果有)。但是,與FS_IOC_GET_ENCRYPTION_POLICY_EX不同,FS_IOC_GET_ENCRYPTION_POLICY 僅支援原始策略版本。它直接接受指向 struct fscrypt_policy_v1 的指標,而不是 struct fscrypt_get_policy_ex_arg。

FS_IOC_GET_ENCRYPTION_POLICY 的錯誤程式碼與 FS_IOC_GET_ENCRYPTION_POLICY_EX 的錯誤程式碼相同,但如果檔案使用較新的加密策略版本加密,則 FS_IOC_GET_ENCRYPTION_POLICY 也會返回 EINVAL

獲取每個檔案系統的 salt

某些檔案系統(例如 ext4 和 F2FS)也支援已棄用的 ioctl FS_IOC_GET_ENCRYPTION_PWSALT。此 ioctl 檢索儲存在檔案系統超級塊中的隨機生成的 16 位元組值。此值旨在用作從密碼或其他低熵使用者憑據派生加密金鑰時的 salt。

FS_IOC_GET_ENCRYPTION_PWSALT 已棄用。相反,更喜歡在使用者空間中生成和管理任何所需的 salt。

獲取檔案的加密 nonce

自 Linux v5.7 起,支援 ioctl FS_IOC_GET_ENCRYPTION_NONCE。在加密的檔案和目錄上,它獲取 inode 的 16 位元組 nonce。在未加密的檔案和目錄上,它會失敗並顯示 ENODATA。

此 ioctl 對於驗證加密是否正確完成的自動化測試很有用。fscrypt 的正常使用不需要它。

新增金鑰

FS_IOC_ADD_ENCRYPTION_KEY

FS_IOC_ADD_ENCRYPTION_KEY ioctl 向檔案系統新增主加密金鑰,使檔案系統上所有使用該金鑰加密的檔案都顯示為“已解鎖”,即以純文字形式顯示。它可以在目標檔案系統上的任何檔案或目錄上執行,但建議使用檔案系統的根目錄。它接受一個指向 struct fscrypt_add_key_arg 的指標,定義如下

struct fscrypt_add_key_arg {
        struct fscrypt_key_specifier key_spec;
        __u32 raw_size;
        __u32 key_id;
#define FSCRYPT_ADD_KEY_FLAG_HW_WRAPPED 0x00000001
        __u32 flags;
        __u32 __reserved[7];
        __u8 raw[];
};

#define FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR        1
#define FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER        2

struct fscrypt_key_specifier {
        __u32 type;     /* one of FSCRYPT_KEY_SPEC_TYPE_* */
        __u32 __reserved;
        union {
                __u8 __reserved[32]; /* reserve some extra space */
                __u8 descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE];
                __u8 identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
        } u;
};

struct fscrypt_provisioning_key_payload {
        __u32 type;
        __u32 flags;
        __u8 raw[];
};

struct fscrypt_add_key_arg 必須歸零,然後按如下方式初始化

  • 如果要新增金鑰以供 v1 加密策略使用,則 key_spec.type 必須包含 FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR,並且 key_spec.u.descriptor 必須包含要新增的金鑰的描述符,對應於 struct fscrypt_policy_v1 的 master_key_descriptor 欄位中的值。要新增此型別的金鑰,呼叫程序必須在初始使用者名稱空間中具有 CAP_SYS_ADMIN 能力。

    或者,如果要新增金鑰以供 v2 加密策略使用,則 key_spec.type 必須包含 FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER,並且 key_spec.u.identifier 是一個輸出欄位,核心使用金鑰的加密雜湊填充它。要新增此型別的金鑰,呼叫程序不需要任何許可權。但是,可以新增的金鑰數量受到使用者金鑰環服務配額的限制(請參閱 Documentation/security/keys/core.rst)。

  • raw_size 必須是以位元組為單位的 raw 金鑰的大小。或者,如果 key_id 非零,則此欄位必須為 0,因為在這種情況下,大小由指定的 Linux 金鑰環金鑰暗示。

  • 如果金鑰直接在 raw 欄位中給出,則 key_id 為 0。否則,key_id 是型別為“fscrypt-provisioning”的 Linux 金鑰環金鑰的 ID,其有效負載是 struct fscrypt_provisioning_key_payload,其 raw 欄位包含金鑰,其 type 欄位與 key_spec.type 匹配,並且其 flags 欄位與 flags 匹配。由於 raw 是可變長度的,因此此金鑰有效負載的總大小必須是 sizeof(struct fscrypt_provisioning_key_payload) 加上金鑰位元組數。程序必須對此金鑰具有搜尋許可權。

    大多數使用者應將其保留為 0 並直接指定金鑰。指定 Linux 金鑰環金鑰的支援主要旨在允許在解除安裝和重新掛載檔案系統後重新新增金鑰,而無需將金鑰儲存在使用者空間記憶體中。

  • flags 包含來自 <linux/fscrypt.h> 的可選標誌

    • FSCRYPT_ADD_KEY_FLAG_HW_WRAPPED:這表示金鑰是硬體包裝的金鑰。請參閱硬體包裝的金鑰。如果使用了 FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR,則不能使用此標誌。

  • raw 是一個可變長度的欄位,必須包含實際金鑰,長度為 raw_size 位元組。或者,如果 key_id 非零,則此欄位未使用。請注意,儘管命名為 raw,但如果指定了 FSCRYPT_ADD_KEY_FLAG_HW_WRAPPED,則它將包含包裝的金鑰,而不是原始金鑰。

對於 v2 策略金鑰,核心會跟蹤哪個使用者(由有效使用者 ID 標識)添加了金鑰,並且僅允許該使用者刪除金鑰 --- 或者“root”,如果他們使用FS_IOC_REMOVE_ENCRYPTION_KEY_ALL_USERS

但是,如果另一個使用者添加了金鑰,則可能需要防止該其他使用者意外刪除金鑰。因此,即使金鑰已經由其他使用者新增,FS_IOC_ADD_ENCRYPTION_KEY 也可以用於再次新增 v2 策略金鑰。在這種情況下,FS_IOC_ADD_ENCRYPTION_KEY 將僅為當前使用者安裝對金鑰的宣告,而不是再次實際新增金鑰(但金鑰仍然必須作為知識證明提供)。

如果金鑰或對金鑰的宣告被新增或已經存在,則 FS_IOC_ADD_ENCRYPTION_KEY 返回 0。

FS_IOC_ADD_ENCRYPTION_KEY 可能會因以下錯誤而失敗

  • EACCES: 指定了 FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR,但呼叫者在初始使用者名稱空間中沒有 CAP_SYS_ADMIN 能力;或者金鑰由 Linux 金鑰 ID 指定,但程序缺少金鑰的搜尋許可權。

  • EBADMSG: 無效的硬體包裝金鑰

  • EDQUOT: 新增金鑰將超出此使用者的金鑰配額

  • EINVAL: 無效的金鑰大小或金鑰說明符型別,或者設定了保留位

  • EKEYREJECTED: 金鑰由 Linux 金鑰 ID 指定,但金鑰型別錯誤

  • ENOKEY: 金鑰由 Linux 金鑰 ID 指定,但不存在具有該 ID 的金鑰

  • ENOTTY: 此型別的檔案系統未實現加密

  • EOPNOTSUPP: 核心未配置對此檔案系統的加密支援,或者檔案系統超級塊未啟用加密;或者指定了硬體包裝金鑰,但檔案系統不支援內聯加密或硬體不支援硬體包裝金鑰

舊方法

對於 v1 加密策略,也可以透過將主加密金鑰新增到程序訂閱的金鑰環(例如,會話金鑰環,或者如果使用者金鑰環連結到會話金鑰環,則新增到使用者金鑰環)來提供主加密金鑰。

由於多種原因,此方法已被棄用(並且不支援 v2 加密策略)。首先,它不能與 FS_IOC_REMOVE_ENCRYPTION_KEY 結合使用(請參閱刪除金鑰),因此對於刪除金鑰,必須使用諸如 keyctl_unlink() 與 sync; echo 2 > /proc/sys/vm/drop_caches 結合使用的解決方法。其次,它與加密檔案的鎖定/未鎖定狀態(即它們顯示為純文字形式還是密文形式)是全域性的事實不符。這種不匹配導致了許多混亂以及當以不同 UID 執行的程序(例如 sudo 命令)需要訪問加密檔案時出現的實際問題。

但是,要將金鑰新增到程序訂閱的金鑰環之一,可以使用 add_key() 系統呼叫(請參閱:Documentation/security/keys/core.rst)。金鑰型別必須為“logon”;此型別的金鑰儲存在核心記憶體中,使用者空間無法讀取。金鑰描述必須為“fscrypt:”,後跟加密策略中設定的 master_key_descriptor 的 16 個字元的小寫十六進位制表示形式。金鑰有效負載必須符合以下結構

#define FSCRYPT_MAX_KEY_SIZE            64

struct fscrypt_key {
        __u32 mode;
        __u8 raw[FSCRYPT_MAX_KEY_SIZE];
        __u32 size;
};

mode 被忽略;只需將其設定為 0。實際金鑰在 raw 中提供,size 指示其大小(以位元組為單位)。也就是說,位元組 raw[0..size-1](包括邊界值)是實際金鑰。

金鑰描述字首“fscrypt:”也可以替換為檔案系統特定的字首,例如“ext4:”。但是,檔案系統特定的字首已棄用,不應在新程式中使用。

刪除金鑰

有兩個 ioctl 可用於刪除透過FS_IOC_ADD_ENCRYPTION_KEY新增的金鑰

這兩個 ioctl 僅在非 root 使用者新增或刪除 v2 策略金鑰的情況下有所不同。

這些 ioctl 不適用於透過舊的程序訂閱金鑰環機制新增的金鑰。

在使用這些 ioctl 之前,請閱讀線上攻擊部分,以瞭解這些 ioctl 的安全目標和限制。

FS_IOC_REMOVE_ENCRYPTION_KEY

FS_IOC_REMOVE_ENCRYPTION_KEY ioctl 從檔案系統中刪除對主加密金鑰的宣告,並可能刪除金鑰本身。它可以在目標檔案系統上的任何檔案或目錄上執行,但建議使用檔案系統的根目錄。它接受一個指向 struct fscrypt_remove_key_arg 的指標,定義如下

struct fscrypt_remove_key_arg {
        struct fscrypt_key_specifier key_spec;
#define FSCRYPT_KEY_REMOVAL_STATUS_FLAG_FILES_BUSY      0x00000001
#define FSCRYPT_KEY_REMOVAL_STATUS_FLAG_OTHER_USERS     0x00000002
        __u32 removal_status_flags;     /* output */
        __u32 __reserved[5];
};

此結構必須歸零,然後按如下方式初始化

  • 要刪除的金鑰由 key_spec 指定

    • 要刪除 v1 加密策略使用的金鑰,請將 key_spec.type 設定為 FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR 並填寫 key_spec.u.descriptor。要刪除此型別的金鑰,呼叫程序必須在初始使用者名稱空間中具有 CAP_SYS_ADMIN 能力。

    • 要刪除 v2 加密策略使用的金鑰,請將 key_spec.type 設定為 FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER 並填寫 key_spec.u.identifier

對於 v2 策略金鑰,非 root 使用者可以使用此 ioctl。但是,為了使這成為可能,它實際上只是刪除了當前使用者對金鑰的宣告,從而撤消了對 FS_IOC_ADD_ENCRYPTION_KEY 的單個呼叫。只有在刪除所有聲明後,金鑰才會被真正刪除。

例如,如果使用 uid 1000 呼叫了 FS_IOC_ADD_ENCRYPTION_KEY,則金鑰將被 uid 1000“宣告”,並且 FS_IOC_REMOVE_ENCRYPTION_KEY 只能以 uid 1000 的身份成功。或者,如果 uid 1000 和 2000 都添加了金鑰,則對於每個 uid,FS_IOC_REMOVE_ENCRYPTION_KEY 將僅刪除他們自己的宣告。只有在兩者都被刪除後,金鑰才會被真正刪除。(可以將其視為取消連結可能具有硬連結的檔案。)

如果 FS_IOC_REMOVE_ENCRYPTION_KEY 真正刪除了金鑰,它還會嘗試“鎖定”所有已使用金鑰解鎖的檔案。它不會鎖定仍在使用的檔案,因此預計此 ioctl 將與使用者空間合作使用,以確保沒有檔案仍處於開啟狀態。但是,如有必要,可以稍後再次執行此 ioctl 以重試鎖定任何剩餘檔案。

如果金鑰被刪除(但可能仍有檔案需要鎖定)、使用者對金鑰的宣告被刪除或金鑰已被刪除但仍有檔案需要鎖定,因此 ioctl 重試鎖定它們,則 FS_IOC_REMOVE_ENCRYPTION_KEY 返回 0。在任何這些情況下,removal_status_flags 都將填充以下資訊狀態標誌

  • FSCRYPT_KEY_REMOVAL_STATUS_FLAG_FILES_BUSY:如果某些檔案仍在執行,則設定。不能保證在僅刪除使用者對金鑰的宣告的情況下設定。

  • FSCRYPT_KEY_REMOVAL_STATUS_FLAG_OTHER_USERS:如果僅刪除使用者對金鑰的宣告,而不是金鑰本身,則設定

FS_IOC_REMOVE_ENCRYPTION_KEY 可能會因以下錯誤而失敗

  • EACCES: 指定了 FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR 金鑰說明符型別,但呼叫者在初始使用者名稱空間中沒有 CAP_SYS_ADMIN 能力

  • EINVAL: 無效的金鑰說明符型別,或者設定了保留位

  • ENOKEY: 根本找不到金鑰物件,即它從未被新增過,或者已經被完全刪除,包括所有鎖定的檔案;或者,使用者沒有對金鑰的宣告(但其他人有)。

  • ENOTTY: 此型別的檔案系統未實現加密

  • EOPNOTSUPP: 核心未配置對此檔案系統的加密支援,或者檔案系統超級塊未啟用加密

FS_IOC_REMOVE_ENCRYPTION_KEY_ALL_USERS

FS_IOC_REMOVE_ENCRYPTION_KEY_ALL_USERS 與FS_IOC_REMOVE_ENCRYPTION_KEY完全相同,只是對於 v2 策略金鑰,ioctl 的 ALL_USERS 版本將刪除所有使用者對金鑰的宣告,而不僅僅是當前使用者的宣告。也就是說,金鑰本身將始終被刪除,無論有多少使用者添加了它。僅當非 root 使用者新增和刪除金鑰時,此差異才有意義。

因此,FS_IOC_REMOVE_ENCRYPTION_KEY_ALL_USERS 也需要“root”,即初始使用者名稱空間中的 CAP_SYS_ADMIN 能力。否則,它將失敗並顯示 EACCES。

獲取金鑰狀態

FS_IOC_GET_ENCRYPTION_KEY_STATUS

FS_IOC_GET_ENCRYPTION_KEY_STATUS ioctl 檢索主加密金鑰的狀態。它可以在目標檔案系統上的任何檔案或目錄上執行,但建議使用檔案系統的根目錄。它接受一個指向 struct fscrypt_get_key_status_arg 的指標,定義如下

struct fscrypt_get_key_status_arg {
        /* input */
        struct fscrypt_key_specifier key_spec;
        __u32 __reserved[6];

        /* output */
#define FSCRYPT_KEY_STATUS_ABSENT               1
#define FSCRYPT_KEY_STATUS_PRESENT              2
#define FSCRYPT_KEY_STATUS_INCOMPLETELY_REMOVED 3
        __u32 status;
#define FSCRYPT_KEY_STATUS_FLAG_ADDED_BY_SELF   0x00000001
        __u32 status_flags;
        __u32 user_count;
        __u32 __out_reserved[13];
};

呼叫者必須將所有輸入欄位歸零,然後填寫 key_spec

  • 要獲取 v1 加密策略的金鑰狀態,請將 key_spec.type 設定為 FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR 並填寫 key_spec.u.descriptor

  • 要獲取 v2 加密策略的金鑰狀態,請將 key_spec.type 設定為 FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER 並填寫 key_spec.u.identifier

成功後,返回 0,並且核心將填寫輸出欄位

  • status 指示金鑰是不存在、存在還是未完全刪除。未完全刪除意味著已啟動刪除,但某些檔案仍在被使用;即,FS_IOC_REMOVE_ENCRYPTION_KEY 返回 0 但設定了資訊狀態標誌 FSCRYPT_KEY_REMOVAL_STATUS_FLAG_FILES_BUSY。

  • status_flags 可以包含以下標誌

    • FSCRYPT_KEY_STATUS_FLAG_ADDED_BY_SELF 指示金鑰已由當前使用者新增。這僅針對由 identifier 而不是 descriptor 標識的金鑰設定。

  • user_count 指定新增金鑰的使用者數。這僅針對由 identifier 而不是 descriptor 標識的金鑰設定。

FS_IOC_GET_ENCRYPTION_KEY_STATUS 可能會因以下錯誤而失敗

  • EINVAL: 無效的金鑰說明符型別,或者設定了保留位

  • ENOTTY: 此型別的檔案系統未實現加密

  • EOPNOTSUPP: 核心未配置對此檔案系統的加密支援,或者檔案系統超級塊未啟用加密

在其他用例中,FS_IOC_GET_ENCRYPTION_KEY_STATUS 可用於確定在提示使用者輸入派生金鑰所需的密碼之前,是否需要為給定的加密目錄新增金鑰。

FS_IOC_GET_ENCRYPTION_KEY_STATUS 只能獲取檔案系統級別金鑰環中的金鑰狀態,即由FS_IOC_ADD_ENCRYPTION_KEYFS_IOC_REMOVE_ENCRYPTION_KEY管理的金鑰環。它無法獲取僅使用舊機制(涉及程序訂閱金鑰環)為 v1 加密策略新增的金鑰的狀態。

訪問語義

使用金鑰

使用加密金鑰,加密的常規檔案、目錄和符號連結的行為與其未加密的對應物非常相似 --- 畢竟,加密旨在是透明的。但是,精明的使用者可能會注意到行為上的一些差異

  • 未加密的檔案或使用不同加密策略(即不同的金鑰、模式或標誌)加密的檔案無法重新命名或連結到加密目錄中;請參閱加密策略執行。嘗試這樣做將失敗並顯示 EXDEV。但是,可以在加密目錄中或移動到未加密目錄中重新命名加密檔案。

    注意:將未加密檔案“移動”到加密目錄中(例如,使用 mv 程式)是在使用者空間中透過複製然後刪除來實現的。請注意,原始未加密資料可能仍然可以從磁碟上的可用空間中恢復;最好從一開始就保持所有檔案加密。shred 程式可用於覆蓋原始檔,但不保證在所有檔案系統和儲存裝置上都有效。

  • 僅在某些情況下支援加密檔案的直接 I/O。有關詳細資訊,請參閱直接 I/O 支援

  • fallocate 操作 FALLOC_FL_COLLAPSE_RANGE 和 FALLOC_FL_INSERT_RANGE 在加密檔案上不受支援,並且將失敗並顯示 EOPNOTSUPP。

  • 不支援加密檔案的線上碎片整理。EXT4_IOC_MOVE_EXT 和 F2FS_IOC_MOVE_RANGE ioctl 將失敗並顯示 EOPNOTSUPP。

  • ext4 檔案系統不支援使用加密的常規檔案進行資料日誌記錄。它將回退到有序資料模式。

  • 不支援在加密檔案上使用 DAX(直接訪問)。

  • 加密符號連結的最大長度比未加密符號連結的最大長度短 2 個位元組。例如,在塊大小為 4K 的 EXT4 檔案系統上,未加密的符號連結最大長度可達 4095 位元組,而加密的符號連結的最大長度僅可達 4093 位元組(兩種長度均不包括終止空字元)。

請注意,支援 mmap。這是可能的,因為加密檔案的頁面快取包含純文字,而不是密文。

沒有金鑰

即使在檔案系統的加密金鑰新增之前,或者加密金鑰移除之後,某些檔案系統操作也可以在加密的常規檔案、目錄和符號連結上執行。

  • 檔案元資料可以被讀取,例如使用 stat()。

  • 目錄可以被列出,在這種情況下,檔名將以一種從其密文匯出的編碼形式列出。當前的編碼演算法在檔名雜湊和編碼中描述。該演算法可能會更改,但保證呈現的檔名不會超過 NAME_MAX 位元組,不會包含 /\0 字元,並且將唯一標識目錄條目。

    ... 目錄條目是特殊的。它們始終存在,並且未加密或編碼。

  • 檔案可以被刪除。也就是說,非目錄檔案可以像往常一樣使用 unlink() 刪除,空目錄可以像往常一樣使用 rmdir() 刪除。因此,rmrm -r 將按預期工作。

  • 符號連結目標可以被讀取和跟隨,但它們將以加密形式呈現,類似於目錄中的檔名。因此,它們不太可能指向任何有用的位置。

在沒有金鑰的情況下,常規檔案無法開啟或截斷。嘗試這樣做將會失敗並返回 ENOKEY。這意味著任何需要檔案描述符的常規檔案操作,例如 read()、write()、mmap()、fallocate() 和 ioctl(),也是被禁止的。

同樣,在沒有金鑰的情況下,任何型別的檔案(包括目錄)都無法在加密目錄中建立或連結,加密目錄中的名稱也不能作為重新命名的源或目標,也不能在加密目錄中建立 O_TMPFILE 臨時檔案。所有此類操作都將失敗並返回 ENOKEY。

目前,在沒有加密金鑰的情況下,無法備份和恢復加密檔案。這將需要尚未實現的特殊 API。

加密策略強制執行

在目錄上設定加密策略後,在該目錄中建立的所有常規檔案、目錄和符號連結(遞迴地)都將繼承該加密策略。特殊檔案 --- 即命名管道、裝置節點和 UNIX 域套接字 --- 不會被加密。

除了這些特殊檔案外,不允許在加密目錄樹中存在未加密的檔案,或使用不同加密策略加密的檔案。嘗試將此類檔案連結或重新命名到加密目錄將會失敗並返回 EXDEV。這也在 ->lookup() 期間強制執行,以提供有限的保護,防止離線攻擊嘗試停用或降級應用程式稍後可能寫入敏感資料的已知位置中的加密。建議實施某種形式的“驗證啟動”的系統透過驗證所有頂級加密策略來利用這一點,然後再進行訪問。

內聯加密支援

預設情況下,fscrypt 使用核心 crypto API 進行所有加密操作(HKDF 除外,fscrypt 部分實現了它)。核心 crypto API 支援硬體加密加速器,但僅支援以傳統方式工作的加速器,其中所有輸入和輸出(例如,明文和密文)都在記憶體中。 fscrypt 可以利用這種硬體,但傳統的加速模型效率不高,並且 fscrypt 尚未針對它進行最佳化。

相反,許多較新的系統(尤其是移動 SoC)具有內聯加密硬體,可以在資料傳輸到/從儲存裝置的過程中加密/解密資料。 Linux 透過一組對塊層的擴充套件(稱為 blk-crypto)支援內聯加密。 blk-crypto 允許檔案系統將加密上下文附加到 bios(I/O 請求),以指定資料將如何線上加密或解密。有關 blk-crypto 的更多資訊,請參見 Documentation/block/inline-encryption.rst

在受支援的檔案系統(目前為 ext4 和 f2fs)上,fscrypt 可以使用 blk-crypto 而不是核心 crypto API 來加密/解密檔案內容。要啟用此功能,請在核心配置中設定 CONFIG_FS_ENCRYPTION_INLINE_CRYPT=y,並在掛載檔案系統時指定“inlinecrypt”掛載選項。

請注意,“inlinecrypt”掛載選項僅指定在可能的情況下使用內聯加密;它不會強制使用。對於內聯加密硬體不具備所需加密功能(例如,對所需加密演算法和資料單元大小的支援)並且 blk-crypto-fallback 不可用的檔案,fscrypt 仍然會回退到使用核心 crypto API。(要使 blk-crypto-fallback 可用,必須在核心配置中使用 CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK=y 啟用它,並且該檔案必須受到原始金鑰而不是硬體封裝金鑰的保護。)

目前,fscrypt 始終使用檔案系統塊大小(通常為 4096 位元組)作為資料單元大小。因此,它只能使用支援該資料單元大小的內聯加密硬體。

內聯加密不會影響磁碟格式的密文或其他方面,因此使用者可以自由地在使用“inlinecrypt”和不使用“inlinecrypt”之間來回切換。一個例外是,受硬體封裝金鑰保護的檔案只能由內聯加密硬體加密/解密,因此只能在使用“inlinecrypt”掛載選項時訪問。有關硬體封裝金鑰的更多資訊,請參見下文。

硬體封裝金鑰

當內聯加密硬體支援時,fscrypt 支援使用硬體封裝金鑰。此類金鑰僅以封裝(加密)形式存在於核心記憶體中;它們只能由內聯加密硬體解封裝(解密),並且在時間上繫結到當前啟動。這可以防止金鑰在核心記憶體洩漏時遭到洩露。這是在不限制可以使用的金鑰數量的情況下完成的,同時仍然允許執行繫結到同一金鑰但無法使用內聯加密硬體的加密任務,例如檔名加密。

請注意,硬體封裝金鑰並非 fscrypt 特有;它們是塊層功能(blk-crypto 的一部分)。有關硬體封裝金鑰的更多詳細資訊,請參見塊層文件 Documentation/block/inline-encryption.rst。本節的其餘部分僅關注 fscrypt 如何使用硬體封裝金鑰的詳細資訊。

fscrypt 支援硬體封裝金鑰,允許 fscrypt 主金鑰作為硬體封裝金鑰,以替代原始金鑰。要使用 FS_IOC_ADD_ENCRYPTION_KEY 新增硬體封裝金鑰,使用者空間必須在 struct fscrypt_add_key_arg 的 flags 欄位中以及適用的 struct fscrypt_provisioning_key_payload 的 flags 欄位中指定 FSCRYPT_ADD_KEY_FLAG_HW_WRAPPED。金鑰必須採用臨時封裝形式,而不是長期封裝形式。

一些限制適用。首先,受硬體封裝金鑰保護的檔案繫結到系統的內聯加密硬體。因此,它們只能在使用“inlinecrypt”掛載選項時訪問,並且不能包含在可移植檔案系統映像中。其次,目前硬體封裝金鑰支援僅與 IV_INO_LBLK_64 策略IV_INO_LBLK_32 策略相容,因為它假定每個 fscrypt 主金鑰只有一個檔案內容加密金鑰,而不是每個檔案一個。未來的工作可能會透過將每個檔案的隨機數傳遞到儲存堆疊來解決此限制,以允許硬體匯出每個檔案的金鑰。

在實現方面,為了加密/解密受硬體封裝金鑰保護的檔案的內容,fscrypt 使用 blk-crypto,將硬體封裝金鑰附加到 bio 加密上下文。與原始金鑰一樣,當塊層金鑰不在金鑰槽中時,塊層會將金鑰程式設計到金鑰槽中。但是,在程式設計硬體封裝金鑰時,硬體不會將給定的金鑰直接程式設計到金鑰槽中,而是會解封裝它(使用硬體的臨時封裝金鑰)並從中匯出內聯加密金鑰。內聯加密金鑰是實際被程式設計到金鑰槽中的金鑰,並且永遠不會暴露給軟體。

但是,fscrypt 不僅僅進行檔案內容加密;它還使用其主金鑰來匯出檔名加密金鑰、金鑰識別符號,有時還會匯出一些更模糊的子金鑰型別,例如 dirhash 金鑰。因此,即使沒有檔案內容加密,fscrypt 仍然需要一個原始金鑰才能工作。為了從硬體封裝金鑰中獲取這樣的金鑰,fscrypt 要求內聯加密硬體從硬體封裝金鑰中匯出一個加密隔離的“軟體金鑰”。 fscrypt 使用此“軟體金鑰”來為 KDF 設定金鑰,以匯出除檔案內容金鑰之外的所有子金鑰。

請注意,這意味著硬體封裝金鑰功能僅保護檔案內容加密金鑰。它不保護其他 fscrypt 子金鑰,例如檔名加密金鑰。

直接 I/O 支援

要使加密檔案的直接 I/O 工作,必須滿足以下條件(除了未加密檔案的直接 I/O 的條件):

  • 該檔案必須使用內聯加密。通常,這意味著檔案系統必須使用 -o inlinecrypt 掛載,並且必須存在內聯加密硬體。但是,軟體回退也可用。有關詳細資訊,請參見內聯加密支援

  • I/O 請求必須完全對齊到檔案系統塊大小。這意味著 I/O 目標的檔案位置、所有 I/O 段的長度以及所有 I/O 緩衝區的記憶體地址必須是該值的倍數。請注意,檔案系統塊大小可能大於塊裝置的邏輯塊大小。

如果上述任一條件未滿足,則加密檔案的直接 I/O 將回退到緩衝 I/O。

實現細節

加密上下文

加密策略在磁碟上由 struct fscrypt_context_v1 或 struct fscrypt_context_v2 表示。由各個檔案系統決定將其儲存在何處,但通常將其儲存在隱藏的擴充套件屬性中。由於加密 xattr 的特殊語義,不應透過 xattr 相關的系統呼叫(例如 getxattr() 和 setxattr())公開它。(特別是,如果將加密策略新增到除空目錄之外的任何內容或從中刪除,將會造成很大的混亂。)這些結構定義如下:

#define FSCRYPT_FILE_NONCE_SIZE 16

#define FSCRYPT_KEY_DESCRIPTOR_SIZE  8
struct fscrypt_context_v1 {
        u8 version;
        u8 contents_encryption_mode;
        u8 filenames_encryption_mode;
        u8 flags;
        u8 master_key_descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE];
        u8 nonce[FSCRYPT_FILE_NONCE_SIZE];
};

#define FSCRYPT_KEY_IDENTIFIER_SIZE  16
struct fscrypt_context_v2 {
        u8 version;
        u8 contents_encryption_mode;
        u8 filenames_encryption_mode;
        u8 flags;
        u8 log2_data_unit_size;
        u8 __reserved[3];
        u8 master_key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
        u8 nonce[FSCRYPT_FILE_NONCE_SIZE];
};

上下文結構包含與相應策略結構相同的資訊(請參見設定加密策略),不同之處在於上下文結構還包含一個隨機數。隨機數由核心隨機生成,並用作 KDF 輸入或作為調整,以使不同的檔案以不同的方式加密;請參見每個檔案的加密金鑰DIRECT_KEY 策略

資料路徑更改

使用內聯加密時,檔案系統只需將加密上下文與 bios 相關聯,以指定塊層或內聯加密硬體如何加密/解密檔案內容。

不使用內聯加密時,檔案系統必須自行加密/解密檔案內容,如下所述:

對於常規檔案的讀取路徑 (->read_folio()),檔案系統可以將密文讀取到頁面快取中,並就地解密。必須保持 folio 鎖,直到解密完成,以防止 folio 過早地對使用者空間可見。

對於常規檔案的寫入路徑 (->writepages()),檔案系統不能就地加密頁面快取中的資料,因為必須保留快取的明文。相反,檔案系統必須加密到臨時緩衝區或“bounce page”中,然後寫出臨時緩衝區。一些檔案系統(例如 UBIFS)已經使用臨時緩衝區,而不管加密如何。其他檔案系統(例如 ext4 和 F2FS)必須專門為加密分配 bounce page。

檔名雜湊和編碼

現代檔案系統透過使用索引目錄來加速目錄查詢。索引目錄組織為由檔名雜湊鍵控的樹。當請求 ->lookup() 時,檔案系統通常會雜湊正在查詢的檔名,以便它可以快速找到相應的目錄條目(如果有)。

使用加密時,無論有無加密金鑰,都必須支援查詢並且效率很高。顯然,雜湊明文檔名是行不通的,因為沒有金鑰就無法獲得明文檔名。(雜湊明文檔名也會使檔案系統的 fsck 工具無法最佳化加密目錄。)相反,檔案系統會雜湊密文檔名,即實際儲存在目錄條目中的磁碟上的位元組。當被要求使用金鑰執行 ->lookup() 時,檔案系統只需加密使用者提供的名稱即可獲得密文。

沒有金鑰的查詢更為複雜。原始密文可能包含 \0/ 字元,這在檔名中是非法的。因此,readdir() 必須對密文進行 base64url 編碼以進行呈現。對於大多數檔名,這都可以正常工作;在 ->lookup() 上,檔案系統只需對使用者提供的名稱進行 base64url 解碼即可返回原始密文。

但是,對於很長的檔名,base64url 編碼會導致檔名長度超過 NAME_MAX。為了防止這種情況,readdir() 實際上以縮寫形式呈現長檔名,該縮寫形式編碼了密文檔名的強“雜湊”,以及目錄查詢所需的可選檔案系統特定雜湊。這允許檔案系統仍然以很高的置信度將 ->lookup() 中給出的檔名映射回先前由 readdir() 列出的特定目錄條目。有關更多詳細資訊,請參見原始碼中的 struct fscrypt_nokey_name。

請注意,在沒有金鑰的情況下向使用者空間呈現檔名的確切方式可能會在將來更改。這只是作為一種臨時呈現有效檔名的方式,以便像 rm -r 這樣的命令可以按預期在加密目錄上工作。

測試

要測試 fscrypt,請使用 xfstests,它是 Linux 的事實上的標準檔案系統測試套件。首先,在相關檔案系統上執行“encrypt”組中的所有測試。也可以使用“inlinecrypt”掛載選項執行測試,以測試內聯加密支援的實現。例如,要使用 kvm-xfstests 測試 ext4 和 f2fs 加密

kvm-xfstests -c ext4,f2fs -g encrypt
kvm-xfstests -c ext4,f2fs -g encrypt -m inlinecrypt

也可以透過這種方式測試 UBIFS 加密,但應在單獨的命令中完成,並且 kvm-xfstests 需要一些時間來設定模擬的 UBI 卷

kvm-xfstests -c ubifs -g encrypt

不應有任何測試失敗。但是,如果所需的演算法未構建到核心的 crypto API 中,則將跳過使用非預設加密模式的測試(例如 generic/549 和 generic/550)。此外,將在 UBIFS 上跳過訪問原始塊裝置的測試(例如 generic/399、generic/548、generic/549、generic/550)。

除了執行“encrypt”組測試之外,對於 ext4 和 f2fs,還可以使用“test_dummy_encryption”掛載選項執行大多數 xfstests。此選項會導致自動使用虛擬金鑰加密所有新檔案,而無需進行任何 API 呼叫。這更徹底地測試了加密的 I/O 路徑。要使用 kvm-xfstests 執行此操作,請使用“encrypt”檔案系統配置

kvm-xfstests -c ext4/encrypt,f2fs/encrypt -g auto
kvm-xfstests -c ext4/encrypt,f2fs/encrypt -g auto -m inlinecrypt

因為這執行的測試比“-g encrypt”多得多,所以執行時間要長得多;因此,還請考慮使用 gce-xfstests 而不是 kvm-xfstests

gce-xfstests -c ext4/encrypt,f2fs/encrypt -g auto
gce-xfstests -c ext4/encrypt,f2fs/encrypt -g auto -m inlinecrypt