全磁碟加密程序是指使用加密金鑰,將 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
中叫用加密功能,系統會使用指令列工具 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
。
Flows
加密裝置有四種流程。裝置只會加密一次,之後會遵循正常的開機流程。
- 加密先前未加密的裝置:
- 使用
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
時不需密碼。 (由於這應該是新裝置,因此未設定任何 PIN 碼)。 - 掛接 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
擷取的參數,在 tmpfs RAMDisk 上掛接/data
,而ro.crypto.tmpfs_options
則是在init.rc
中設定。 - 開始架構
vold
會將vold.decrypt
設為trigger_restart_framework
,繼續執行一般啟動程序。
加密現有裝置
如果未加密的 Android K 或更早版本裝置已遷移至 L,就會發生這種情況。
這項程序是由使用者啟動,在程式碼中稱為「就地加密」。使用者選取要加密的裝置時,使用者介面會確保電池已充飽電,且已插入 AC 變壓器,因此有足夠的電力完成加密程序。
警告:如果裝置在加密完成前沒電並關機,檔案資料會處於部分加密狀態。裝置必須恢復原廠設定,且所有資料都會遺失。
如要啟用就地加密,vold
會啟動迴圈,讀取實際區塊裝置的每個磁區,然後寫入加密區塊裝置。vold
會先檢查磁區是否正在使用,再讀取及寫入磁區,因此如果新裝置幾乎沒有資料,加密速度會快上許多。
裝置狀態:設定 ro.crypto.state = "unencrypted"
並執行 on nonencrypted
init
觸發程序,繼續啟動。
- 檢查密碼
使用者介面會使用指令
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
是否已加密 (有密碼或沒有密碼)。 - 解密 /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
傳回錯誤,使用者介面應向使用者顯示訊息,請他們重新啟動裝置並恢復原廠設定,並提供按鈕供使用者執行這項操作。 - 使用密碼解密資料
cryptfs cryptocomplete
成功後,架構會顯示要求輸入磁碟密碼的 UI。使用者介面會將cryptfs checkpw
指令傳送至vold
,藉此檢查密碼。如果密碼正確 (系統會先在暫時位置成功掛接解密的/data
,然後卸載,藉此判斷密碼是否正確),vold
會將解密區塊裝置的名稱儲存在ro.crypto.fs_crypto_blkdev
屬性中,並向 UI 傳回狀態 0。如果密碼不正確,系統會向 UI 傳回 -1。 - 停止架構
使用者介面會顯示加密啟動圖形,然後使用
cryptfs restart
指令呼叫vold
。vold
會將vold.decrypt
屬性設為trigger_reset_main
,導致init.rc
執行class_reset main
。這會停止主類別中的所有服務,以便卸載 tmpfs/data
。 - 安裝
/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 顯示前,vold
會重新啟動系統。如果重新啟動失敗,系統會將 vold.encrypt_progress
設為 error_shutting_down
並傳回 -1,但不會有任何項目可擷取錯誤。這不應發生。
如果 vold
在加密程序中偵測到錯誤,就會將 vold.encrypt_progress
設為 error_partially_encrypted
,並傳回 -1。使用者介面應顯示加密失敗的訊息,並提供按鈕讓使用者將裝置恢復原廠設定。
儲存加密金鑰
加密金鑰會儲存在加密中繼資料中。硬體支援是透過受信任執行環境 (TEE) 的簽署功能實作。先前,我們是透過將 scrypt 套用至使用者密碼和儲存的鹽,藉此產生金鑰來加密主金鑰。為確保金鑰能抵禦裝置外部攻擊,我們擴充了這項演算法,使用儲存的 TEE 金鑰簽署產生的金鑰。然後,系統會再套用一次 scrypt,將產生的簽章轉換為適當長度的金鑰。這個金鑰隨後會用來加密及解密主金鑰。如要儲存這項金鑰,請按照下列步驟操作:
- 產生隨機 16 位元組的磁碟加密金鑰 (DEK) 和 16 位元組的鹽。
- 將 scrypt 套用至使用者密碼和鹽,產生 32 位元組的中間金鑰 1 (IK1)。
- 以零位元組填補 IK1,使其大小與硬體繫結私密金鑰 (HBK) 相同。具體來說,我們會補齊以下內容:00 || IK1 || 00..00;一個零位元組、32 個 IK1 位元組、223 個零位元組。
- 使用 HBK 簽署補零的 IK1,產生 256 位元組的 IK2。
- 將 scrypt 應用於 IK2 和鹽 (與步驟 2 相同的鹽),產生 32 位元組的 IK3。
- 將 IK3 的前 16 個位元組做為 KEK,後 16 個位元組做為 IV。
- 使用 AES_CBC 加密 DEK,並搭配金鑰 KEK 和初始化向量 IV。
變更密碼
當使用者選擇在設定中變更或移除密碼時,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 |
由 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.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 設定,其中包含 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