全盤加密是使用加密密鑰對 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 位是可選的)。
注意: 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
。下面詳細解釋每個流程。
使用 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
設置為啟動defaultcrypto
服務的trigger_default_encryption
。 (這將啟動下面安裝默認加密用戶數據的流程。)trigger_default_encryption
檢查加密類型以查看/data
是否使用密碼進行加密。由於 Android 5.0 設備在首次啟動時已加密,因此不應設置密碼;因此我們解密並掛載/data
。 - 掛載
/data
然後
init
使用從ro.crypto.tmpfs_options
中設置的init.rc
獲取的參數將/data
掛載到 tmpfs RAMDisk 上。 - 啟動框架
將
vold
設置為trigger_restart_framework
,這將繼續通常的引導過程。
加密現有設備
當您對已遷移到 L 的未加密 Android K 或更早版本的設備進行加密時,就會發生這種情況。
這個過程是用戶發起的,在代碼中被稱為“就地加密”。當用戶選擇加密設備時,UI 會確保電池已充滿電且交流適配器已插入,因此有足夠的電量來完成加密過程。
警告:如果設備在完成加密之前電量耗盡並關閉,文件數據將處於部分加密狀態。設備必須恢復出廠設置,所有數據都將丟失。
為了啟用就地加密, vold
啟動一個循環來讀取實際塊設備的每個扇區,然後將其寫入加密塊設備。 vold
在讀取和寫入之前檢查扇區是否正在使用,這使得在幾乎沒有數據的新設備上加密速度更快。
設備狀態:設置ro.crypto.state = "unencrypted"
並執行on nonencrypted
init
trigger 以繼續引導。
- 檢查密碼
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
設備,以便設備可以使用。 - 掛載/數據
然後,
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
啟動它的所有服務,並且系統可以使用了。
啟動沒有默認加密的加密設備
當您啟動具有設置密碼的加密設備時會發生這種情況。設備的密碼可以是圖釘、圖案或密碼。
- 使用密碼檢測加密設備
檢測到安卓設備是加密的,因為標誌
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
。 - 掛載
/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) 的簽名功能來實現的。以前,我們使用通過對用戶密碼和存儲的鹽應用 scrypt 生成的密鑰來加密主密鑰。為了使密鑰能夠抵禦開箱攻擊,我們通過使用存儲的 TEE 密鑰對結果密鑰進行簽名來擴展該算法。然後通過 scrypt 的另一種應用程序將生成的簽名轉換為適當長度的密鑰。然後使用此密鑰對主密鑰進行加密和解密。要存儲此密鑰:
- 生成隨機的 16 字節磁盤加密密鑰 (DEK) 和 16 字節鹽。
- 對用戶密碼和 salt 應用 scrypt 以生成 32 字節的中間密鑰 1 (IK1)。
- 用零字節填充 IK1 到硬件綁定私鑰 (HBK) 的大小。具體來說,我們填充為:00 || IK1 || 00..00; 1 個零字節,32 個 IK1 字節,223 個零字節。
- 用 HBK 對 IK1 進行符號填充以生成 256 字節的 IK2。
- 將 scrypt 應用於 IK2 和 salt(與步驟 2 相同的 salt)以生成 32 字節的 IK3。
- 使用 IK3 的前 16 個字節作為 KEK,最後 16 個字節作為 IV。
- 使用 AES_CBC、密鑰 KEK 和初始化向量 IV 加密 DEK。
更改密碼
當用戶在設置中選擇更改或刪除他們的密碼時,UI 將命令cryptfs changepw
發送到vold
,並且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