Zxcrypt

總覽

zxcrypt 是區塊裝置篩選器驅動程式庫,其會公開加密寫入的資料,以及從區塊裝置讀取的資料解密。Zxcrypt 裝置使用的基礎區塊裝置可能幾乎是任何區塊裝置,包括原始磁碟、ram 磁碟、GPT 分區、FVM 分區,甚至是其他 Zxcrypt 裝置。唯一的限制是區塊大小以頁面對齊。繫結後,Zxcrypt 裝置就會在裝置樹狀結構中發布另一部可供消費者正常互動的區塊裝置。

使用方法

zxcrypt 包含由 libzxcrypt.so 提供的 driver程式庫,是管理 zxcrypt 裝置的四個函式。每個金鑰都會使用一或多個 zxcrypt_key_t 鍵,如有多個索引鍵,則會將索引鍵資料、長度和運算單元建立關聯。

  • zxcrypt_format 函式會使用開放式區塊裝置,並寫入必要的加密中繼資料,讓其成為 zxcrypt 裝置。提供的 zxcrypt 金鑰無法直接保護裝置上的資料,但金鑰是用來保護資料金鑰內容。
zx_status_t zxcrypt_format(int fd, const zxcrypt_key_t* key);
  • zxcrypt_bind 函式會指示驅動程式庫讀取已加密的中繼資料,並擷取要以公開透明方式轉換 I/O 資料的資料金鑰內容。
zx_status_t zxcrypt_bind(int fd, const zxcrypt_key_t *key);
  • zxcrypt_rekey 函式會使用舊金鑰來先讀取加密的中繼資料,再使用新的金鑰將其寫回。
zx_status_t zxcrypt_rekey(int fd, const zxcrypt_key_t* old_key, const zxcrypt_key_t* new_key);
  • zxcrypt_shred 函式會先使用提供的金鑰讀取加密中繼資料,驗證呼叫端是否能存取資料。如果成功執行,便會刪除包含資料金鑰內容的加密中繼資料。如此一來,日後就無法再存取這類資料。
zx_status_t zxcrypt_shred(int fd, const zxcrypt_key_t* key);

技術詳細資料

DDKTL 驅動程式

zxcrypt 是以 DDKTL 裝置驅動程式庫編寫。src/lib/ddktl 是 C++ 架構,用於在 Fuchsia 中寫入驅動程式。這可讓作者使用範本化混合項目,自動提供 src/lib/ddk 函式指標和回呼。

有兩個小型功能無法以 DDKTL 和 C++ 編寫:

  • 驅動程式繫結邏輯,使用 DDK 的 binding.h 的 C 預先處理器巨集編寫。
  • ulib/sync 的完成處理常式,用於同步 I/O,與 C++ Atomics 不相容。

工作站執行緒

裝置會啟動執行於裝置期間的工作站執行緒,並為所有 I/O 要求建立管道。每個項目都有運作的 I/O 類型、會等待的傳入要求 I/O 佇列,以及資料加密。收到要求時,如果運算碼與要找的運算碼相符,則會先使用其加密方式轉換要求中的資料,然後再傳遞。

整體管道如下:

DdkIotxnQueue -+
                \       Worker 1:        Underlying      Worker 2:        Original
    BlockRead ---+--->  Encrypter   --->   Block   --->  Decrypter  ---> Completion
                /     Acts on writes       Device      Acts on reads      Callback
   BlockWrite -+

「加密者」工作站會先加密每個 I/O 寫入要求中的資料,然後再傳送至基礎區塊裝置;而「解密工具」工作站則會解密來自基礎區塊裝置的每個 I/O 讀取回應資料。加密的金鑰長度必須至少為 16 個位元組,且具備語意上的安全 (IND-CCA2),並將區塊偏移值加入「調整」。目前使用 AES256-XTS

戒指與文字轉語音

為確保原始 I/O 要求者能公開資料加密和解密,工作站在轉換資料時必須複製資料。透過管道傳送的 I/O 要求實際上並非原始要求,而是封裝原始要求的「陰影」要求。

由於需要遮蔽要求,系統會按照 VMO 中的頁面依序分配這些要求。當工作站需要轉換資料時,會將原始資料加密、封裝的寫入要求成為陰影要求,或是將陰影要求中的資料解密至原始封裝的原始讀取要求。一旦原始要求可以傳回至原始要求者,影子要求就會被取消,並拒絕其頁面。這樣可確保使用的記憶體不會超過未解決的 I/O 要求所需的量。

超級封鎖格式

加密及解密資料的金鑰內容稱為資料金鑰,儲存在裝置的保留部分 (稱為 superblock)。這個超級區塊是否存在至關重要;如果沒有,便無法在裝置中重新建立資料金鑰及復原資料。因此,超級區塊會複製到裝置上的多個位置,以供備援使用。Zxcrypt 區塊裝置使用者無法看到這些位置。每當 zxcrypt 驅動程式成功讀取並驗證來自某個位置的超級區塊時,它就會複製到所有其他超級區塊位置,協助「自行修復」任何損毀的超級區塊位置。

超級區塊格式如下所示,每個欄位會依序說明:

+----------------+----------------+----+-----...-----+----...----+------...------+
| Type GUID      | Instance GUID  |Vers| Sealed Key  | Reserved  | HMAC          |
| 16 bytes       | 16 bytes       | 4B | Key size    |    ...    | Digest length |
+----------------+----------------+----+-----...-----+----...----+------...------+
  • 類型 GUID:將此裝置視為 zxcrypt 裝置。與 GPT 相容。
  • 執行個體 GUID:每部裝置的 ID,用來做為 KDF 鹽,如下所述。
  • 版本:用於指出要使用的加密演算法。
  • Sealed Key:資料金鑰,由包裝金鑰加密,如下所述。
  • 保留:未使用的資料,將超級區塊與區塊邊界對齊。
  • HMAC:到目前為止的超級區塊金鑰摘要 (包括「保留」欄位)。

包裝金鑰、包裝 IV 及 HMAC 金鑰都衍生自 KDF。這個 KDF 是 RFC 5869 HKDF,予以結合所提供的金鑰、執行個體 GUID 的「鹽」與每項用途的標籤 (例如「wrap」或「hmac」)。KDF 不會嘗試任何頻率限制。KDF 可降低金鑰重複使用的風險,因為新的隨機執行個體鹽字串會引發新的衍生金鑰。HMAC 可防止意外或惡意修改內容發生,而不會洩漏任何有關 zxcrypt 金鑰的實用資訊。

_注意:KDF 不會執行任何延展鍵。我們假設攻擊者可以移除裝置,然後自行嘗試金鑰衍生金鑰,略過 HMAC 檢查和任何可能的頻率限制。為了避免這種情況,zxcrypt 取用者應加入適當限制頻率的裝置金鑰,例如來自 TPM 的裝置金鑰,並且擷取其 zxcrypt 金鑰。_

未來工作

有幾個地方可以、應該或必須進一步改善:

  • 顯示隱藏的繫結失敗情形

    目前,即使裝置無法初始化,zxcrypt_bind 仍可能表示成功。Zxcrypt 是指在執行繫結邏輯時,不會同步將裝置新增至裝置樹狀結構。這項功能必須執行 I/O,且無法封鎖對 device_bind 的呼叫無法傳回,因此會產生初始化執行緒,並在完成後新增裝置。

    自 2017 年 10 月起,這是 DDK 開發作業的有效領域,這項政策將改為要求在傳回裝置前加入裝置,並且未來可能會額外呼叫發布裝置。因此,建議您針對呼叫端同步對呼叫端呼叫 zxcrypt_bind 區塊,直到裝置準備就緒或無法明確繫結為止。

  • 使用 AEAD 而非 AES-XTS

    普遍認為,AEAD 在解密資料的完整性前,會先驗證資料的完整性,進而提供優異的加密編譯防護效果。這有必要,但每個區塊都會產生額外的負擔。這表示取用者必須耗用非頁面對齊的區塊 (一旦內部負擔移除後),或者 zxcrypt 就必須儲存不行的負擔,並處理非不可分割的寫入錯誤

  • 支援多個金鑰

    為了讓金鑰託管和/或復原功能,您可以輕鬆修改超級區塊格式,取得一系列的密碼編譯信封。預期可達到這個情況,libzxcrypt API 會使用可變的金鑰數量,但目前唯一支援的長度是 1,而唯一有效的運算單元是 0。

  • 調整工作站數量

    目前有 1 個加密者和一個解密工具。這類函式適用於任意數量的執行緒,因此效能微調作業可能需要找出最適合的工作站數量,以便平衡 I/O 頻寬和排程器流失

  • 移除內部檢查

    目前,Zxcrypt 程式碼會在內部邊界檢查許多錯誤,如果不符合這些條件,則傳回資訊錯誤。就效能而言,如果是因程式設計錯誤而產生的,而非要求者或基礎裝置的資料,都可以轉換為「偵錯」宣告,以便在發布模式中略過。