全磁碟加密

全磁碟加密是指使用加密金鑰,對 Android 裝置上的所有使用者資料進行編碼的程序。裝置加密後,所有使用者建立的資料都會在寫入磁碟前自動加密,所有讀取作業也會在將資料傳回呼叫程序前自動解密。

全磁碟加密功能是在 Android 4.4 中推出,但 Android 5.0 推出了以下新功能:

  • 建立快速加密功能,只加密資料分割區中已使用的區塊,避免首次啟動需要很長的時間。目前只有 ext4 和 f2fs 檔案系統支援快速加密功能。
  • 新增 forceencrypt fstab 旗標,以便在首次啟動時加密。
  • 新增對無密碼的圖案和加密功能支援。
  • 使用受信任的執行環境 (TEE) 簽署功能 (例如在 TrustZone 中),新增加密金鑰的硬體支援儲存空間。詳情請參閱「儲存加密金鑰」。

注意:升級至 Android 5.0 並加密的裝置,可以透過恢復原廠設定,恢復未加密的狀態。在首次啟動時加密的新版 Android 5.0 裝置無法還原為未加密狀態。

Android 全磁碟加密功能的運作方式

Android 全磁碟加密功能以 dm-crypt 為基礎,這是在區塊裝置層級運作的核心功能。因此,加密功能可與 Embedded MultiMediaCard (eMMC) 和類似的快閃記憶體裝置搭配使用,這些裝置會將自己呈現給核心,做為區塊裝置。YAFFS 無法進行加密,因為它會直接與原始 NAND 快閃記憶體晶片通訊。

加密演算法為 128 進階加密標準 (AES),搭配密碼區塊鏈結 (CBC) 和 ESSIV:SHA256。主金鑰會透過呼叫 OpenSSL 程式庫,以 128 位元 AES 加密。金鑰長度必須為 128 位元以上 (256 位元為選項)。

注意:原始設備製造商 (OEM) 可以使用 128 位元或更高的位元加密主金鑰。

在 Android 5.0 版本中,有四種加密狀態:

  • 預設值
  • PIN 碼
  • 密碼
  • pattern

裝置首次啟動時,會隨機產生 128 位元主金鑰,然後使用預設密碼和儲存的鹽值進行雜湊運算。預設密碼為「default_password」,不過,產生的雜湊值也會透過 TEE (例如 TrustZone) 簽署,該 TEE 會使用簽名的雜湊值來加密主金鑰。

您可以在 Android 開放原始碼專案的 cryptfs.cpp 檔案中找到定義的預設密碼。

當使用者在裝置上設定 PIN 碼/密碼或密碼時,系統只會重新加密及儲存 128 位元金鑰。(例如,使用者變更 PIN 碼/密碼/解鎖圖案不會導致重新加密使用者資料)。請注意,受管理的裝置可能會受到 PIN 碼、解鎖圖案或密碼限制。

加密功能由 initvold 管理。init 會呼叫 vold,而 vold 會設定屬性,以便在初始化時觸發事件。系統的其他部分也會查看屬性,執行相關工作,例如回報狀態、要求輸入密碼,或在發生致命錯誤時提示使用者重設為原廠設定。為在 vold 中叫用加密功能,系統會使用指令列工具 vdccryptfs 指令:checkpwrestartenablecryptochangepwcryptocompleteverifypwsetfieldgetfieldmountdefaultencryptedgetpwtypegetpwclearpw

如要加密、解密或清除 /data/data 必須未掛載。不過,為了顯示任何使用者介面 (UI),架構必須啟動,而架構需要 /data 才能執行。為解決這個難題,我們在 /data 上掛載暫時性檔案系統。這樣一來,Android 就能視需要提示輸入密碼、顯示進度,或建議清除資料。但這項限制會導致系統必須停止在暫時性檔案系統中開啟檔案的每個程序,並在實際的 /data 檔案系統中重新啟動這些程序,才能從暫時性檔案系統切換至實際的 /data 檔案系統。為此,所有服務都必須位於 coremainlate_start 這三個群組之一。

  • core:啟動後絕不關閉。
  • main:輸入磁碟密碼後,請關機再重新啟動。
  • late_start:必須先解密並掛載 /data,系統才會啟動。

如要觸發這些動作,請將 vold.decrypt 屬性設為各種字串。如要終止及重新啟動服務,請使用 init 指令:

  • class_reset:停止服務,但允許使用 class_start 重新啟動。
  • class_start:重新啟動服務。
  • class_stop:停止服務並新增 SVC_DISABLED 標記。已停止的服務不會回應 class_start

流程

加密裝置有四個流程。裝置只會加密一次,然後按照一般開機流程進行。

  • 為先前未加密的裝置加密:
    • 使用 forceencrypt 加密新裝置:首次啟動時強制加密 (自 Android L 起)。
    • 加密現有裝置:使用者啟動的加密 (Android K 以下版本)。
  • 啟動已加密的裝置:
    • 啟動未設定密碼的加密裝置:啟動未設定密碼的加密裝置 (適用於搭載 Android 5.0 以上版本的裝置)。
    • 使用密碼啟動已加密的裝置:啟動已設定密碼的加密裝置。

除了這些流程之外,裝置也可能無法加密 /data。以下將詳細說明每個流程。

使用 forceencrypt 加密新裝置

這是 Android 5.0 裝置的正常首次啟動程序。

  1. 使用 forceencrypt 標記偵測未加密的檔案系統

    /data 未加密,但必須加密,因為 forceencrypt 有此規定。卸載 /data

  2. 開始加密 /data

    vold.decrypt = "trigger_encryption" 會觸發 init.rc,導致 vold 以無密碼的方式加密 /data。(由於這是新裝置,因此未設定任何值)。

  3. 掛接 tmpfs

    vold 會掛載 tmpfs /data (使用 ro.crypto.tmpfs_options 的 tmpfs 選項),並將屬性 vold.encrypt_progress 設為 0。vold 會為啟動加密系統準備 tmpfs /data,並將屬性 vold.decrypt 設為:trigger_restart_min_framework

  4. 顯示進度框架

    由於裝置幾乎沒有要加密的資料,因此加密作業會非常快速完成,因此進度列不會經常顯示。如要進一步瞭解進度 UI,請參閱「加密現有裝置」。

  5. /data 遭到加密時,請卸載架構

    voldvold.decrypt 設為 trigger_default_encryption,啟動 defaultcrypto 服務。(這會啟動下列流程,用於掛載預設的加密使用者資料)。trigger_default_encryption 會檢查加密類型,查看 /data 是否已使用密碼加密。由於 Android 5.0 裝置會在首次開機時進行加密,因此不應設定密碼;因此,我們會解密並掛載 /data

  6. Mount /data

    init 接著會使用從 init.rc 設定的 ro.crypto.tmpfs_options 擷取的參數,在 tmpfs RAMDisk 上掛載 /data

  7. 啟動架構

    vold 會將 vold.decrypt 設為 trigger_restart_framework,繼續執行一般啟動程序。

為現有裝置加密

這是在您加密未加密的 Android K 或已遷移至 L 的舊版裝置時會發生的情況。

這項程序是由使用者啟動,在程式碼中稱為「原地加密」。使用者選取加密裝置時,UI 會確保電池已充飽電,且已插入交流電源變壓器,以便有足夠電力完成加密程序。

警告:如果裝置電力耗盡,並在完成加密前關機,檔案資料就會處於部分加密狀態。裝置必須恢復原廠設定,且所有資料都會遺失。

如要啟用原地加密功能,vold 會啟動迴圈,讀取實際區塊裝置的每個區段,然後將其寫入加密區塊裝置。vold 會在讀取和寫入資料之前檢查區段是否已在使用,因此在資料量很少或沒有資料的新裝置上,加密作業的速度會大幅提升。

裝置狀態:設定 ro.crypto.state = "unencrypted" 並執行 on nonencrypted init 觸發事件,以便繼續啟動。

  1. 檢查密碼

    UI 會使用指令 cryptfs enablecrypto inplace 呼叫 vold,其中 passwd 是使用者的螢幕鎖定密碼。

  2. 移除架構

    vold 會檢查錯誤,如果無法加密,則傳回 -1,並在記錄中列印原因。如果可以加密,則會將屬性 vold.decrypt 設為 trigger_shutdown_framework。這會導致 init.rc 停止 late_startmain 類別中的服務。

  3. 建立加密貨幣頁尾
  4. 建立麵包屑檔案
  5. 重新啟動
  6. 偵測導覽標記檔案
  7. 開始加密 /data

    vold 接著會設定加密對應,建立虛擬加密區塊裝置,並將其對應至實際區塊裝置,但會在寫入時加密每個區塊,並在讀取時解密每個區塊。vold 接著會建立並寫出加密中繼資料。

  8. 在加密期間掛接 tmpfs

    vold 會掛載 tmpfs /data (使用 ro.crypto.tmpfs_options 的 tmpfs 選項),並將屬性 vold.encrypt_progress 設為 0。vold 會為啟動加密系統準備 tmpfs /data,並將屬性 vold.decrypt 設為:trigger_restart_min_framework

  9. 顯示進度框架

    trigger_restart_min_framework 會導致 init.rc 啟動 main 類別的服務。當架構發現 vold.encrypt_progress 已設為 0 時,就會顯示進度列 UI,每五秒查詢一次該屬性,並更新進度列。每次加密分區的百分比時,加密迴圈都會更新 vold.encrypt_progress

  10. /data 遭到加密時,請更新加密頁尾

    /data 成功加密後,vold 會清除中繼資料中的標記 ENCRYPTION_IN_PROGRESS

    裝置成功解鎖後,系統就會使用密碼加密主金鑰,並更新加密附註。

    如果重新啟動失敗,vold 會將屬性 vold.encrypt_progress 設為 error_reboot_failed,並在 UI 中顯示訊息,要求使用者按下按鈕重新啟動。這應該不會發生。

啟動使用預設加密功能的加密裝置

這是在沒有密碼的情況下啟動加密裝置時會發生的情況。由於 Android 5.0 裝置會在首次啟動時進行加密,因此不應設定密碼,因此這是預設加密狀態。

  1. 偵測未加密密碼的加密 /data

    偵測 Android 裝置是否已加密,因為無法掛載 /data,且已設定其中一個標記 encryptableforceencrypt

    vold 會將 vold.decrypt 設為 trigger_default_encryption,啟動 defaultcrypto 服務。trigger_default_encryption 會檢查加密類型,查看 /data 是否已使用或未使用密碼加密。

  2. 解密 /data

    在區塊裝置上建立 dm-crypt 裝置,讓裝置可供使用。

  3. 掛載 /data

    vold 接著會掛載已解密的實際 /data 分區,然後準備新的分區。它會將 vold.post_fs_data_done 屬性設為 0,然後將 vold.decrypt 設為 trigger_post_fs_data。這會導致 init.rc 執行其 post-fs-data 指令。這些指令碼會建立任何必要的目錄或連結,然後將 vold.post_fs_data_done 設為 1。

    vold 在該屬性中看到 1 後,就會將屬性 vold.decrypt 設為:trigger_restart_framework.。這會導致 init.rc 再次啟動 main 類別中的服務,並在開機後首次啟動 late_start 類別中的服務。

  4. 啟動架構

    此時,架構會使用已解密的 /data 啟動所有服務,系統也已準備就緒。

啟動未預設加密的加密裝置

這是在啟動已設定密碼的加密裝置時會發生的情況。裝置的密碼可以是 PIN 碼、解鎖圖案或密碼。

  1. 偵測使用密碼加密的裝置

    偵測 Android 裝置是否已加密,因為標記為 ro.crypto.state = "encrypted"

    vold 會將 vold.decrypt 設為 trigger_restart_min_framework,因為 /data 已使用密碼加密。

  2. 掛接 tmpfs

    init 會設定五個屬性,用來儲存 /data 的初始掛載選項,並透過 init.rc 傳遞參數。vold 會使用下列屬性設定加密貨幣對應項目:

    1. ro.crypto.fs_type
    2. ro.crypto.fs_real_blkdev
    3. ro.crypto.fs_mnt_point
    4. ro.crypto.fs_options
    5. ro.crypto.fs_flags (ASCII 8 位元十六進位數字,開頭為 0x)
  3. 啟動架構,提示輸入密碼

    架構啟動後,會發現 vold.decrypt 已設為 trigger_restart_min_framework。這會告訴架構,它是在 tmpfs /data 磁碟上啟動,且需要取得使用者密碼。

    不過,它必須先確認磁碟已正確加密。它會將 cryptfs cryptocomplete 指令傳送至 vold。如果加密作業順利完成,vold 會傳回 0;如果發生內部錯誤,則傳回 -1;如果加密作業未順利完成,則傳回 -2。vold 會在加密中繼資料中查看 CRYPTO_ENCRYPTION_IN_PROGRESS 標記,藉此判斷這項資訊。如果已設定,則表示加密程序已中斷,且裝置上沒有可用的資料。如果 vold 傳回錯誤,使用者介面應向使用者顯示訊息,要求他們重新啟動裝置並將裝置恢復原廠設定,並提供按鈕供使用者按下執行。

  4. 使用密碼解密資料

    cryptfs cryptocomplete 成功後,架構會顯示 UI,要求使用者輸入磁碟密碼。UI 會傳送 cryptfs checkpw 指令至 vold,藉此檢查密碼。如果密碼正確 (系統會在暫時位置成功掛載解密的 /data,然後卸載),vold 會在屬性 ro.crypto.fs_crypto_blkdev 中儲存解密的區塊裝置名稱,並將狀態 0 傳回至 UI。如果密碼不正確,則會將 -1 傳回至 UI。

  5. 停止架構

    UI 會顯示加密啟動圖形,然後使用 cryptfs restart 指令呼叫 voldvold 將屬性 vold.decrypt 設為 trigger_reset_main,導致 init.rc 執行 class_reset main。這會停止主類別中的所有服務,讓 tmpfs /data 可以卸載。

  6. Mount /data

    vold 接著會掛載已解密的實際 /data 分區,並準備新的分區 (如果使用清除選項加密,則可能從未準備,因為第一版不支援此選項)。它會將 vold.post_fs_data_done 屬性設為 0,然後將 vold.decrypt 設為 trigger_post_fs_data。這會導致 init.rc 執行其 post-fs-data 指令。這些指令碼會建立任何必要的目錄或連結,然後將 vold.post_fs_data_done 設為 1。當 vold 在該屬性中看到 1 時,就會將屬性 vold.decrypt 設為 trigger_restart_framework。這會導致 init.rc 再次啟動 main 類別中的服務,並在開機後首次啟動 late_start 類別中的服務。

  7. 啟動完整架構

    框架現在會使用已解密的 /data 檔案系統啟動所有服務,系統也已準備就緒。

失敗

裝置無法解密可能有幾個原因。裝置會先執行一系列正常的啟動步驟:

  1. 偵測使用密碼加密的裝置
  2. 掛接 tmpfs
  3. 啟動架構,提示輸入密碼

但在架構開啟後,裝置可能會發生一些錯誤:

  • 密碼相符,但無法解密資料
  • 使用者輸入錯誤密碼 30 次

如果這些錯誤無法解決,請提示使用者恢復原廠設定

如果 vold 在加密程序期間偵測到錯誤,且尚未銷毀任何資料且架構已啟用,vold 會將屬性 vold.encrypt_progress 設為 error_not_encrypted。使用者介面會提示使用者重新啟動裝置,並提醒他們加密程序從未啟動。如果錯誤發生在架構解除安裝後,但進度列 UI 顯示前,vold 會重新啟動系統。如果重新啟動失敗,系統會將 vold.encrypt_progress 設為 error_shutting_down 並傳回 -1;但不會有任何東西擷取錯誤。這不是預期的情況。

如果 vold 在加密程序中偵測到錯誤,就會將 vold.encrypt_progress 設為 error_partially_encrypted 並傳回 -1。接著,使用者介面應會顯示加密失敗的訊息,並提供按鈕,讓使用者將裝置恢復原廠設定。

儲存已加密的金鑰

加密金鑰會儲存在加密中繼資料中。硬體支援功能是透過使用受信任的執行環境 (TEE) 簽署功能實作。先前,我們會使用由 scrypt 套用至使用者密碼和儲存的鹽值所產生的金鑰,加密主金鑰。為了讓金鑰能抵禦外部攻擊,我們會使用儲存的 TEE 金鑰為產生的金鑰簽署,以擴充這項演算法。接著,再透過一次 scrypt 應用程式,將產生的簽名轉換為適當長度的金鑰。然後使用這個金鑰加密及解密主金鑰。如何儲存這個金鑰:

  1. 產生隨機的 16 個位元組磁碟加密金鑰 (DEK) 和 16 個位元組鹽值。
  2. 將 scrypt 套用至使用者密碼和鹽值,產生 32 位元中繼金鑰 1 (IK1)。
  3. 使用零位元組填補 IK1,使其大小等同於硬體綁定私密金鑰 (HBK)。具體來說,我們會以以下方式填充:00 || IK1 || 00..00;一個零位元組、32 個 IK1 位元組、223 個零位元組。
  4. 使用 HBK 為填補的 IK1 簽署,產生 256 位元組的 IK2。
  5. 將 scrypt 套用至 IK2 和鹽 (與步驟 2 相同的鹽),產生 32 位元組的 IK3。
  6. 請使用 IK3 的前 16 位元組做為 KEK,並將最後 16 位元組做為 IV。
  7. 使用 AES_CBC、金鑰 KEK 和初始化向量 IV 加密 DEK。

變更密碼

當使用者選擇在設定中變更或移除密碼時,UI 會將 cryptfs changepw 指令傳送至 vold,而 vold 會使用新密碼重新加密磁碟主金鑰。

加密屬性

voldinit 會透過設定屬性來相互通訊。以下列出可用於加密的屬性清單。

Vold 屬性

資源 說明
vold.decrypt trigger_encryption 使用無密碼加密雲端硬碟。
vold.decrypt trigger_default_encryption 檢查雲端硬碟是否已加密,且沒有密碼。如果是,請解密並掛載,否則將 vold.decrypt 設為 trigger_restart_min_framework。
vold.decrypt trigger_reset_main 由 vold 設定,用於關閉要求磁碟密碼的 UI。
vold.decrypt trigger_post_fs_data 由 vold 設定,以便使用必要的目錄等準備 /data
vold.decrypt trigger_restart_framework 由 vold 設定,用於啟動實際架構和所有服務。
vold.decrypt trigger_shutdown_framework 由 vold 設定,用於關閉整個架構以啟動加密程序。
vold.decrypt trigger_restart_min_framework 由 vold 設定,視 ro.crypto.state 的值而定,可啟動加密進度列 UI 或密碼提示。
vold.encrypt_progress 當架構啟動時,如果已設定此屬性,就會進入進度列 UI 模式。
vold.encrypt_progress 0 to 100 進度列 UI 應顯示設定的百分比值。
vold.encrypt_progress error_partially_encrypted 進度列 UI 應顯示加密失敗的訊息,並提供使用者重設裝置的選項。
vold.encrypt_progress error_reboot_failed 進度列 UI 應顯示加密完成的訊息,並提供使用者重新啟動裝置的按鈕。這不是預期的錯誤。
vold.encrypt_progress error_not_encrypted 進度列 UI 應顯示訊息,說明發生錯誤,但沒有任何資料遭到加密或遺失,並提供使用者按鈕重新啟動系統。
vold.encrypt_progress error_shutting_down 進度列 UI 並未執行,因此無法確定誰會回應這個錯誤。而且這種情況也不應發生。
vold.post_fs_data_done 0 在將 vold.decrypt 設為 trigger_post_fs_data 之前,由 vold 設定。
vold.post_fs_data_done 1 init.rcinit.rc 在完成 post-fs-data 工作後設定。

init 屬性

資源 說明
ro.crypto.fs_crypto_blkdev vold 指令 checkpw 設定,供 vold 指令 restart 日後使用。
ro.crypto.state unencrypted init 設定,表示系統正在使用未加密的 /data ro.crypto.state encrypted 執行。由 init 設定,表示這個系統正在使用加密的 /data 執行。

ro.crypto.fs_type
ro.crypto.fs_real_blkdev
ro.crypto.fs_mnt_point
ro.crypto.fs_options
ro.crypto.fs_flags

init 嘗試以 init.rc 傳入的參數掛載 /data 時,會設定這五個屬性。vold 會使用這些值來設定加密對應項目。
ro.crypto.tmpfs_options init.rc 會在掛接 tmpfs /data 檔案系統時,使用 init.rc 設定的選項。

init 動作

on post-fs-data
on nonencrypted
on property:vold.decrypt=trigger_reset_main
on property:vold.decrypt=trigger_post_fs_data
on property:vold.decrypt=trigger_restart_min_framework
on property:vold.decrypt=trigger_restart_framework
on property:vold.decrypt=trigger_shutdown_framework
on property:vold.decrypt=trigger_encryption
on property:vold.decrypt=trigger_default_encryption