全磁碟加密是指使用加密金鑰,對 Android 裝置上的所有使用者資料進行編碼的程序。裝置加密後,所有使用者建立的資料都會在寫入磁碟前自動加密,所有讀取作業也會在將資料傳回呼叫程序前自動解密。
全磁碟加密功能是在 Android 4.4 中推出,但 Android 5.0 推出了以下新功能:
- 建立快速加密,只對資料分區中使用的區塊加密,以避免初次啟動花費過長。目前只有 ext4 和 f2fs 檔案系統支援快速加密。
- 新增
forceencrypt
fstab 標記,以便在首次啟動時加密。 - 開始支援無密碼的模式和加密功能。
- 新增採用受信任執行環境 (TEE) 簽署功能 (例如在 TrustZone 中) 的加密金鑰儲存硬體支援儲存空間。詳情請參閱「儲存加密金鑰」。
注意:升級至 Android 5.0 並加密的裝置,可以透過恢復原廠設定,恢復未加密的狀態。在首次啟動時加密的新版 Android 5.0 裝置無法還原為未加密狀態。
Android 全磁碟加密功能的運作方式
Android 全磁碟加密功能以 dm-crypt
為基礎,這是在區塊裝置層級運作的核心功能。因此,加密功能適用於內嵌式 MultiMediaCard (eMMC) 和類似的快閃裝置,並以區塊裝置的形式呈現在核心上。YAFFS 無法直接加密原始的 NAND 快閃晶片,
加密演算法為 128 進階加密標準 (AES),搭配密碼區塊鏈結 (CBC) 和 ESSIV:SHA256。系統會透過呼叫 OpenSSL 程式庫,使用 128 位元 AES 加密主金鑰。金鑰必須使用 128 位元以上的值 (256 為選用)。
注意:原始設備製造商 (OEM) 可以使用 128 位元或更高的位元加密主金鑰。
在 Android 5.0 版本中,有四種加密狀態:
- 預設值
- PIN 碼
- 密碼
- 圖案
裝置首次啟動時,會隨機產生 128 位元主金鑰,然後使用預設密碼和儲存的鹽值進行雜湊運算。預設密碼為:「default_password」 不過,產生的雜湊會透過 TEE (例如 TrustZone) 簽署,該 TEE 會使用簽章的雜湊值加密主金鑰。
您可以在 Android 開放原始碼專案的 cryptfs.cpp 檔案中找到定義的預設密碼。
當使用者在裝置上設定 PIN 碼/密碼或密碼時,系統只會重新加密及儲存 128 位元金鑰。(例如,使用者變更 PIN 碼/密碼/解鎖圖案不會導致重新加密使用者資料)。請注意,受管理的裝置可能會受到 PIN 碼、解鎖圖案或密碼限制。
加密功能由 init
和 vold
管理。init
會呼叫 vold
,並且 vold 設定屬性,以在 init 中觸發事件。系統的其他部分也會查看這些屬性,執行相關工作,例如回報狀態、要求輸入密碼,或在發生致命錯誤時提示使用者重設為原廠設定。為在 vold
中叫用加密功能,系統會使用指令列工具 vdc
的 cryptfs
指令:checkpw
、restart
、enablecrypto
、changepw
、cryptocomplete
、verifypw
、setfield
、getfield
、mountdefaultencrypted
、getpwtype
、getpw
和 clearpw
。
如要加密、解密或清除 /data
,/data
必須未掛載。不過,為了顯示任何使用者介面 (UI),架構必須啟動,而架構需要 /data
才能執行。為解決這個問題,臨時檔案系統會掛接在 /data
上。這可讓 Android 視需要提示密碼、顯示進度或建議抹除資料。但這項限制會導致系統必須停止在暫時性檔案系統中開啟檔案的每個程序,並在實際的 /data
檔案系統中重新啟動這些程序,才能從暫時性檔案系統切換至實際的 /data
檔案系統。為此,所有服務都必須位於 core
、main
和 late_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 裝置的正常首次啟動程序。
- 使用
forceencrypt
標記偵測未加密的檔案系統/data
未加密,但必須加密,因為forceencrypt
有此規定。卸載/data
。 - 開始加密
/data
vold.decrypt = "trigger_encryption"
會觸發init.rc
,導致vold
以無密碼的方式加密/data
。(未設定,因為這應該是新裝置)。 - 掛接 tmpfs
vold
會掛接 tmpfs/data
(使用ro.crypto.tmpfs_options
中的 tmpfs 選項),並將vold.encrypt_progress
屬性設為 0。vold
會準備 tmpfs/data
以啟動加密系統,並將vold.decrypt
屬性設為:trigger_restart_min_framework
- 建立架構以顯示進度
由於裝置幾乎沒有要加密的資料,因此加密作業會非常快速完成,因此進度列不會經常顯示。如要進一步瞭解進度 UI,請參閱「加密現有裝置」。
- 在
/data
加密之後,移除架構vold
會將vold.decrypt
設為trigger_default_encryption
,藉此啟動defaultcrypto
服務。(這會啟動下列流程,用於掛載預設的加密使用者資料)。trigger_default_encryption
會檢查加密類型,查看/data
是否已使用密碼加密。由於 Android 5.0 裝置會在首次啟動時加密,因此不應設定任何密碼,因此我們會解密並掛接/data
。 - 掛接
/data
接著,
init
會使用從ro.crypto.tmpfs_options
中取得的參數 (在init.rc
中設定) 將/data
掛接至 tmpfs RAMDisk。 - Start 架構
vold
會將vold.decrypt
設為trigger_restart_framework
,繼續執行一般啟動程序。
加密現有裝置
這是在加密未加密的 Android K 或已遷移至 L 的舊版裝置時會發生的情況。
這項程序是由使用者啟動,在程式碼中稱為「就地加密」。使用者選擇加密裝置時,UI 會確保電池已充飽電,且 AC 變壓器已接上電源,因此有足夠的電量可以完成加密程序。
警告:如果裝置沒電,並在完成加密前關機,檔案資料會處於部分加密狀態。必須將裝置恢復原廠設定,且所有資料都會遺失。
如要啟用原地加密功能,vold
會啟動迴圈,讀取實際區塊裝置的每個區段,然後將其寫入加密區塊裝置。在讀取及寫入資料前,vold
會檢查產業是否使用中,進而在幾乎或完全沒有資料的新裝置上大幅加快加密速度。
裝置狀態:設定 ro.crypto.state = "unencrypted"
並執行 on nonencrypted
init
觸發事件,以便繼續啟動。
- 檢查密碼
UI 會使用指令
cryptfs enablecrypto inplace
呼叫vold
,其中passwd
是使用者的螢幕鎖定密碼。 - 移除架構
vold
會檢查錯誤,如果無法加密,則傳回 -1,並在記錄中列印原因。如果可以加密,則會將屬性vold.decrypt
設為trigger_shutdown_framework
。這會讓init.rc
停止late_start
和main
類別中的服務。 - 建立加密頁尾
- 建立導覽標記檔案
- 重新啟動
- 偵測導覽標記檔案
- 開始加密
/data
vold
會設定加密編譯對應,這會建立虛擬加密區塊裝置,該裝置可對應至真實區塊裝置,但在寫入每個部門時都會加密,並在讀取每個部門時解密。接著vold
會建立並寫入加密編譯中繼資料。 - 在加密期間掛接 tmpfs
vold
會掛載 tmpfs/data
(使用ro.crypto.tmpfs_options
的 tmpfs 選項),並將屬性vold.encrypt_progress
設為 0。vold
會為啟動加密系統準備 tmpfs/data
,並將屬性vold.decrypt
設為:trigger_restart_min_framework
- 建立架構以顯示進度
trigger_restart_min_framework
會導致init.rc
啟動main
服務類別。當架構看到vold.encrypt_progress
設定為 0 時,就會開啟進度列 UI,該介面每五秒就進行屬性查詢,並更新進度列。每加密另一百分比分區時,加密迴圈就會更新vold.encrypt_progress
。 - 當
/data
遭到加密時,請更新加密頁尾當
/data
成功加密時,vold
會清除中繼資料中的標記ENCRYPTION_IN_PROGRESS
。裝置成功解鎖後,系統就會使用密碼加密主金鑰,並更新加密附註。
如果重新啟動失敗,
vold
會將屬性vold.encrypt_progress
設為error_reboot_failed
,並在 UI 中顯示訊息,要求使用者按下按鈕重新啟動。請放心,這種錯誤不會發生。
啟動使用預設加密機制的加密裝置
如果在沒有密碼的情況下啟動加密裝置,就會發生這種情形。 由於 Android 5.0 裝置會在首次啟動時進行加密,因此不應設定密碼,因此這是預設加密狀態。
- 偵測未加密密碼的加密
/data
偵測 Android 裝置是否已加密,因為
/data
無法掛接,且已設定其中一個標記encryptable
或forceencrypt
。vold
會將vold.decrypt
設為trigger_default_encryption
,啟動defaultcrypto
服務。trigger_default_encryption
會檢查加密類型,確認/data
是否使用密碼加密。 - 解密 /資料
在區塊裝置上建立
dm-crypt
裝置,讓裝置可供使用。 - 掛載 /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
類別中啟動服務。 - 啟動架構
現在架構會使用解密的
/data
啟動所有服務,且系統隨時可以使用。
啟動未採用預設加密機制的加密裝置
當您啟動設有密碼的加密裝置時,就會發生這樣的情況。裝置的密碼可以是 PIN 碼、解鎖圖案或密碼。
- 偵測使用密碼加密的裝置
偵測 Android 裝置因標記
ro.crypto.state = "encrypted"
而已加密vold
會將vold.decrypt
設為trigger_restart_min_framework
,因為/data
是用密碼加密。 - 掛接 tmpfs
init
會設定五個屬性,用來儲存/data
的初始掛載選項,並透過init.rc
傳遞參數。vold
會使用下列屬性設定加密貨幣對應項目:ro.crypto.fs_type
ro.crypto.fs_real_blkdev
ro.crypto.fs_mnt_point
ro.crypto.fs_options
ro.crypto.fs_flags
(ASCII 8 位元十六進位數字,開頭為 0x)
- 啟動架構,提示輸入密碼
架構啟動後,會發現
vold.decrypt
已設為trigger_restart_min_framework
。這會向架構說明其是在 tmpfs/data
磁碟中啟動,因此需要取得使用者密碼。不過,它必須先確認磁碟已正確加密。它會將
cryptfs cryptocomplete
指令傳送至vold
。如果加密作業順利完成,vold
會傳回 0;如果發生內部錯誤,則傳回 -1;如果加密作業未順利完成,則傳回 -2。vold
會查看CRYPTO_ENCRYPTION_IN_PROGRESS
標記的加密中繼資料,藉此決定這一點。如果已設定,則表示加密程序已中斷,且裝置上沒有可用的資料。如果vold
傳回錯誤,UI 應顯示訊息,請使用者重新啟動並將裝置恢復原廠設定,然後為使用者提供按一下操作的按鈕。 - 使用密碼解密資料
cryptfs cryptocomplete
成功後,架構會顯示 UI,要求使用者輸入磁碟密碼。UI 會將cryptfs checkpw
指令傳送至vold
,藉此檢查密碼。如果密碼正確 (透過成功在臨時位置掛接解密的/data
並卸載後確定),vold
會將已解密的區塊裝置名稱儲存在屬性ro.crypto.fs_crypto_blkdev
中,並傳回 0 狀態至 UI。如果密碼不正確,UI 會傳回 -1。 - 停止架構
UI 會顯示加密啟動圖形,然後使用
cryptfs restart
指令呼叫vold
。vold
會將屬性vold.decrypt
設為trigger_reset_main
,讓init.rc
執行class_reset main
。這會停止主類別中的所有服務,讓 tmpfs/data
可以卸載。 - 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
類別中啟動服務。 - 啟動完整架構
框架現在會使用已解密的
/data
檔案系統啟動所有服務,系統也已準備就緒。
失敗
無法解密的裝置可能會因某些原因而產生故障。裝置會從一般的啟動步驟系列開始:
- 偵測使用密碼加密的裝置
- 掛接 tmpfs
- 啟動架構以提示密碼
但在架構開啟後,裝置可能會發生一些錯誤:
- 密碼相符,但無法解密資料
- 使用者輸入錯誤密碼 30 次
如果這些錯誤仍未解決,請提示使用者恢復原廠設定:
如果 vold
在加密程序期間偵測到錯誤,且尚未刪除任何資料且架構已開始,vold
會將屬性 vold.encrypt_progress
設為 error_not_encrypted
。UI 會提示使用者重新啟動,並通知使用者未啟動的加密程序。如果錯誤發生在架構解除安裝後,但進度列 UI 顯示前,vold
會重新啟動系統。如果重新啟動失敗,系統會將 vold.encrypt_progress
設為 error_shutting_down
並傳回 -1,但不會再擷取任何錯誤。這不是預期的情況。
如果 vold
在加密程序中偵測到錯誤,就會將 vold.encrypt_progress
設為 error_partially_encrypted
並傳回 -1。接著,使用者介面應會顯示加密失敗的訊息,並提供按鈕,讓使用者將裝置恢復原廠設定。
儲存已加密的金鑰
加密金鑰會儲存在加密中繼資料中。硬體支援功能是透過使用受信任的執行環境 (TEE) 簽署功能實作。我們之前會將加密套用至使用者密碼和預存的鹽,藉此透過產生的金鑰加密主金鑰。為了讓金鑰不受現成攻擊的彈性,我們會使用儲存的 TEE 金鑰簽署最終金鑰,以擴充這個演算法。接著,再透過一次 scrypt 應用程式,將產生的簽名轉換為適當長度的金鑰。然後使用這個金鑰加密及解密主金鑰。如何儲存這個金鑰:
- 產生隨機的 16 個位元組磁碟加密金鑰 (DEK) 和 16 個位元組鹽值。
- 為使用者密碼和鹽套用鹽,產生 32 位元組中繼金鑰 1 (IK1)。
- 使用零位元組填補 IK1,使其大小等同於硬體綁定私密金鑰 (HBK)。具體來說,我們會以以下方式填充:00 || IK1 || 00..00;一個零位元組、32 個 IK1 位元組、223 個零位元組。
- 使用 HBK 為填補的 IK1 簽署,產生 256 位元組 IK2。
- 套用 IK2 和鹽 (與步驟 2 相同的鹽) 以產生 32 位元組 IK3。
- 使用前 16 個位元組的 IK3 做為 KEK,最後一個 16 個位元組做為 IV。
- 使用 AES_CBC 金鑰 (金鑰 KEK) 和初始化向量 IV 加密 DEK。
變更密碼
當使用者選擇在設定中變更或移除密碼時,UI 會將 cryptfs changepw
指令傳送至 vold
,而 vold
會使用新密碼重新加密磁碟主金鑰。
加密屬性
vold
和 init
會透過設定屬性來彼此通訊。下方是可用於加密的屬性清單。
Vold 屬性
資源 | 說明 |
---|---|
vold.decrypt trigger_encryption |
使用無密碼加密雲端硬碟。 |
vold.decrypt trigger_default_encryption |
檢查雲端硬碟是否已加密,且沒有密碼。如果已包含,請解密並掛接其他元件,否則請將 vold.decrypt 設為 trigger_restart_min_framework。 |
vold.decrypt trigger_reset_main |
設為關閉要求提供磁碟密碼的 UI。 |
vold.decrypt trigger_post_fs_data |
設定前提為 /data 準備必要的目錄等等。 |
vold.decrypt trigger_restart_framework |
由 轉變為啟動實際架構和所有服務。 |
vold.decrypt trigger_shutdown_framework |
設為關閉完整架構,啟動加密作業。 |
vold.decrypt trigger_restart_min_framework |
設為 vold 會啟動進度列 UI,用於加密或提示密碼提示 (視 ro.crypto.state 的值而定)。 |
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 設定,再將 vold.decrypt 設為 trigger_post_fs_data 。 |
vold.post_fs_data_done 1 |
由 init.rc 或 init.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 執行。 |
|
當 init 嘗試以 init.rc 傳入的參數掛載 /data 時,會設定這五個屬性。vold 會使用這些資訊設定加密對應。 |
ro.crypto.tmpfs_options |
由 init.rc 設定,且在掛接 tmpfs /data 檔案系統時應使用的選項 init。 |
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