全碟加密是使用加密金鑰對 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 快閃記憶體晶片對話。
加密演算法是帶有密碼區塊連結 (CBC) 和 ESSIV:SHA256 的 128 高級加密標準 (AES)。主密鑰透過呼叫 OpenSSL 庫使用 128 位元 AES 進行加密。您必須使用 128 位元或更多位元作為密鑰(256 位元是可選的)。
注意: OEM 可以使用 128 位元或更高位元來加密主金鑰。
在Android 5.0版本中,有四種加密狀態:
- 預設
- 別針
- 密碼
- 圖案
首次啟動時,裝置會建立一個隨機產生的 128 位元主金鑰,然後使用預設密碼和儲存的鹽進行雜湊處理。預設密碼為:「default_password」 但是,產生的雜湊值也會透過 TEE(例如 TrustZone)進行簽名,TEE 使用簽署的雜湊值來加密主金鑰。
您可以在 Android 開源專案cryptfs.cpp檔案中找到定義的預設密碼。
當使用者在裝置上設定 PIN/pass 或密碼時,只有 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
。下面詳細解釋每個流程。
使用forcecrypt加密新設備
這是 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 上。 - 啟動框架
vold
將vold.decrypt
設定為trigger_restart_framework
,這將繼續通常的啟動過程。
加密現有設備
當您加密已移轉到 L 的未加密 Android K 或更早版本的裝置時,就會發生這種情況。
此過程是用戶發起的,在程式碼中稱為「就地加密」。當使用者選擇加密裝置時,UI 會確保電池已充滿電且交流電源供應器已插入,以便有足夠的電量來完成加密過程。
警告:如果裝置在完成加密之前電量耗盡並關閉,檔案資料將處於部分加密狀態。設備必須恢復出廠設置,所有資料都會遺失。
為了啟用就地加密, 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,該 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
設備,以便該設備可供使用。 - 掛載/數據
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;如果加密未成功完成,則 vold 傳回 -2。vold
透過在加密元資料中尋找CRYPTO_ENCRYPTION_IN_PROGRESS
標誌來確定這一點。如果已設置,則加密過程將中斷,並且設備上沒有可用的資料。如果vold
回傳錯誤,UI 應向使用者顯示一則訊息,要求使用者重新啟動裝置並恢復出廠設置,並為使用者提供一個按鈕來執行此操作。 - 使用密碼解密資料
一旦
cryptfs cryptocomplete
成功,框架就會顯示一個 UI,要求輸入磁碟密碼。 UI 透過將指令cryptfs checkpw
傳送到vold
來檢查密碼。如果密碼正確(透過在臨時位置成功掛載解密的/data
然後卸載它來確定),vold
會將解密的區塊裝置的名稱保存在屬性ro.crypto.fs_crypto_blkdev
中,並向 UI 傳回狀態 0 。如果密碼不正確,則向 UI 傳回 -1。 - 停止框架
UI 會顯示一個加密啟動圖形,然後使用指令
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 會提示使用者重新啟動並提醒他們加密過程從未啟動。如果錯誤發生在框架被拆除之後,但在進度條 UI 啟動之前, vold
將重新啟動系統。如果重新啟動失敗,則將vold.encrypt_progress
設為error_shutting_down
並返回-1;但不會有任何東西來捕捉錯誤。預計不會發生這種情況。
如果vold
在加密過程中偵測到錯誤,則會將vold.encrypt_progress
設為error_partially_encrypted
並傳回 -1。然後,UI 應顯示一則訊息,說明加密失敗,並為使用者提供一個按鈕以將裝置重設為原廠設定。
儲存加密金鑰
加密的密鑰儲存在加密元資料中。硬體支援是透過使用可信任執行環境 (TEE) 簽章功能來實現的。之前,我們使用透過對使用者密碼和儲存的鹽套用 scrypt 產生的金鑰來加密主金鑰。為了使金鑰能夠抵禦離機攻擊,我們透過使用儲存的 TEE 金鑰對結果金鑰進行簽章來擴展此演算法。然後,透過另一次 scrypt 應用程式將所得簽章轉換為適當長度的金鑰。然後使用該密鑰來加密和解密主密鑰。若要儲存此密鑰:
- 產生隨機 16 位元組磁碟加密金鑰 (DEK) 和 16 位元組鹽。
- 將 scrypt 應用於使用者密碼和鹽以產生 32 位元組中間金鑰 1 (IK1)。
- 將 IK1 以零位元組填入硬體綁定私鑰 (HBK) 的大小。具體來說,我們填充為:00 || IK1 || 00..00; 1 個零位元組、32 個 IK1 位元組、223 個零位元組。
- 以 HBK 對 IK1 進行符號填充,產生 256 位元組的 IK2。
- 將 scrypt 應用於 IK2 和鹽(與步驟 2 相同的鹽)以產生 32 位元組 IK3。
- 使用 IK3 的前 16 個位元組作為 KEK,後 16 個位元組作為 IV。
- 使用 AES_CBC、金鑰 KEK 和初始化向量 IV 加密 DEK。
更改密碼
當使用者選擇在設定中變更或刪除其密碼時,UI 會向vold
傳送指令cryptfs changepw
,然後vold
使用新密碼重新加密磁碟主金鑰。
加密屬性
vold
和init
透過設定屬性來相互通訊。以下是可用於加密的屬性的清單。
沃爾德屬性
財產 | 描述 |
---|---|
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 設定以啟動加密進度條 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.decrypt 設定為trigger_post_fs_data 之前由vold 設定。 |
vold.post_fs_data_done 1 | 在完成任務post-fs-data 後由init.rc 或init.rc 設定。 |
初始化屬性
財產 | 描述 |
---|---|
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