全磁碟加密

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

Android 4.4 導入了全磁碟加密功能,而 Android 5.0 則導入了下列新功能:

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

注意:如果裝置升級至 Android 5.0 後加密,恢復原廠設定即可還原為未加密狀態。如果新 Android 5.0 裝置在首次啟動時加密,就無法還原為未加密狀態。

Android 全磁碟加密的運作方式

Android 全磁碟加密功能是以 dm-crypt 為基礎,這項核心功能會在區塊裝置層運作。因此,加密功能適用於嵌入式多媒體卡 (eMMC),以及向核心顯示為區塊裝置的類似快閃裝置。YAFFS 會直接與原始 NAND 快閃晶片通訊,因此無法加密。

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

注意:原始設備製造商可以使用 128 位元以上的金鑰加密主金鑰。

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

  • 預設
  • PIN 碼
  • 密碼
  • 圖案

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

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

使用者在裝置上設定 PIN 碼/密碼或密碼時,系統只會重新加密並儲存 128 位元金鑰。(也就是說,使用者 PIN 碼/密碼/解鎖圖案變更「不會」導致使用者資料重新加密)。請注意,受管理裝置可能會有 PIN 碼、解鎖圖案或密碼限制。

加密作業由「init」和「vold」管理。 init 會呼叫 vold,而 vold 會設定屬性,在 init 中觸發事件。系統的其他部分也會查看這些屬性,以執行工作,例如回報狀態、要求密碼,或在發生重大錯誤時提示恢復原廠設定。如要在 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

Flows

加密裝置有四種流程。裝置只會加密一次,之後會遵循正常的開機流程。

  • 加密先前未加密的裝置:
    • 使用 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 時不需密碼。 (由於這應該是新裝置,因此未設定任何 PIN 碼)。

  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加密時,請拆除架構

    vold 會將 vold.decrypt 設為 trigger_default_encryption,藉此啟動 defaultcrypto 服務。(這會啟動下方流程,掛接預設加密的使用者資料。)trigger_default_encryption 會檢查加密類型,判斷 /data 是否已加密,以及是否需要密碼。由於 Android 5.0 裝置會在首次啟動時加密,因此不應設定密碼;我們會解密並掛接 /data

  6. 安裝 /data

    init,然後使用從 ro.crypto.tmpfs_options 擷取的參數,在 tmpfs RAMDisk 上掛接 /data,而 ro.crypto.tmpfs_options 則是在 init.rc 中設定。

  7. 開始架構

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

加密現有裝置

如果未加密的 Android K 或更早版本裝置已遷移至 L,就會發生這種情況。

這項程序是由使用者啟動,在程式碼中稱為「就地加密」。使用者選取要加密的裝置時,使用者介面會確保電池已充飽電,且已插入 AC 變壓器,因此有足夠的電力完成加密程序。

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

如要啟用就地加密,vold 會啟動迴圈,讀取實際區塊裝置的每個磁區,然後寫入加密區塊裝置。vold 會先檢查磁區是否正在使用,再讀取及寫入磁區,因此如果新裝置幾乎沒有資料,加密速度會快上許多。

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

  1. 檢查密碼

    使用者介面會使用指令 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。使用者介面會將 cryptfs checkpw 指令傳送至 vold,藉此檢查密碼。如果密碼正確 (系統會先在暫時位置成功掛接解密的 /data,然後卸載,藉此判斷密碼是否正確),vold 會將解密區塊裝置的名稱儲存在 ro.crypto.fs_crypto_blkdev 屬性中,並向 UI 傳回狀態 0。如果密碼不正確,系統會向 UI 傳回 -1。

  5. 停止架構

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

  6. 安裝 /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 加密 DEK,並搭配金鑰 KEK 和初始化向量 IV。

變更密碼

當使用者選擇在設定中變更或移除密碼時,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 設定,其中包含 init 在掛接 tmpfs /data 檔案系統時應使用的選項。

初始化動作

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