android 全磁盘加密
全磁盘加密是使用一个密钥来为android设备上所有的用户数据加密的过程。一旦设备被加密,所有的用户创建的数据都将会在提交的磁盘之前自动加密,在读取之前都会自动解密。
注意:升级到Android5.0并且被加密的设备可能会数据恢复出厂后返回一个未加密状态。在首次启动时加密的新的Android 5.0设备不能返回到未加密状态。
Android全磁盘加密基于dm-crypy,他是一个内核特征,工作在块设备层。出于这个原因,加密是和嵌入式多媒体卡和作为块设备的闪存设备一起工作的。在YAFFS文件系统中,加密是不可能工作的,他是直接和NAND闪存芯片工作的。
加密算法是带有CBC和 ESSIV:SHA256的128高级加密标准。主键是通过128位AES通过OpenSSL库被加密的。你必须要使用128位或更大位数的键值。
注意:OEMs能够使用128位或更高位数来加密主键。
在Android5.0的发行版中,有四种加密状态:
在第一次启动的时候,设备创建一个随机生成的128位主键,然后使用默认密码和存储的salt进行哈希运算。默认密码是”default_password”。然而,结果散列也需要通过TEE进行签名,他使用一个签名的散列来加密主键。
你可以在Android源码文件cryptfs.c中找到默认密码。
当用户在设备中设置了PIN或者是密码,仅仅128位的键被重新加密和存储。(用户PIN/密码/模式的改变不会造成用户数据重新加密)。注意管理的设备可能会受到PIN,模式或密码的限制。
加密是由init和vold管理的。init调用vold,vold设置位于init中的属性来触发事件。系统的其他部分也会根据这些属性来进行工作,例如报告状态,查询密码,或者是发生致命错误的时候及时工厂重置。为了在vold中激活加密特征,系统使用命令行工具vdc
’s cryptfs运行命令:
checkpw
, restart
, enablecrypto
, changepw
, cryptocomplete
, verifypw
,setfield
, getfield
, mountdefaultencrypted
, getpwtype
, getpw
, 和clearpw
.
为了进行加密,解密或清除/data,/data必须要被挂载。然而,为了能够显示用户接口,框架一定要运行并且框架需要/data来运行。为了解决这个难题,在/data中会挂载一个临时的文件系统。这使得安卓能够提示密码,显示进展,或在需要的时候进行数据擦除。在从临时文件系统到真实的/data文件系统的转换的时候强加了一些限制,系统必须要停止打开临时文件系统中文件的每一个进程,然后在真实的/data文件系统中重启这些进程。为了这样做,所有的服务都必须在这三个组中的一个中:core,main和late_start。
为了激发这些行为,vold.decrypt属性被设置为各种字符串。为了杀死并且重启服务,init命令为:
对于一个加密的设备来说有四个流。一个设备仅仅加密一次,然后遵循一个正常的启动流。
除了这些流,设备加密/data就会失败。每一个流在下面都会详细解释。
这是一个Android 5.0设备正常的首次启动。
/forceencrypt标志的未加密文件系统
/data没有被加密,但是必须要进行加密。因为
/forceencrypt委托他这样做。卸载/data
vold.decrypt = "trigger_encryption"
triggers init.rc
, which will cause vold
to encrypt /data
with no password. (None is set because this should be a new device.)
vold挂载一个临时文件系统 /data(从ro.crypto.tmpfs_options中使用tmpfs选项),并且设置属性
vold.encrypt_progress
为0.vold为启动一个加密的系统准备tmpfs /data,并且设置属性vold.decrypt为trigger_restart_min_framework
因为该设备几乎没有数据去加密了,进度条也不会显示了,因为加密过程会很快。
vold设置vold.decrypt为 开始defaultcrypto
服务的trigger_default_encryption。(这个会开始下面的流用于挂载一个默认的加密用户数据)。trigger_default_encryption检查加密类型来查看/data是否是带有密码被加密的。因为Android5.0设备在首次启动的时候被加密,应该没有密码设置;因此我们解密并且挂载/data。
init然后使用从ro.crypto.tmpfs_options中选出的参数在tmpfs的
RAMDisk 中挂载/data,ro.crypto.tmpfs_options是在init.rc中被设置的。
设置vold为trigger_restart_framework,他继续常规的启动进程。
这种情况发生在你加密一个未加密的Android k或更早版本的设备,该设备被一直到Android L的时候。注意,这个和在K中使用的流是一样的。
这个进程是用户初始化的,并且在代码中被引用作为”就地加密”。当一个空户选择去加密一个设备的时候,UI确保电量是完全充电状态并且AC适配器被插入,所以有足够的变量来完成加密进程。
警告:如果设备在完成加密之前耗尽电量并且关机了,在部分加密状态下会留下文件数据。设备必须要工厂重置,所有的数据都会丢失。
为了使能就地加密,vold开始一个循环来读取真实块设备的每一个扇区,然后将他写到加密块设备中。vold在读取和写数据之前,检查扇区是否正在使用,这会使得在几乎没有数据的新设备上加密会很快。
设备状态:设置ro.crypto.state = "unencrypted"
并且运行 on nonencrypted
init触发器来继续启动。
UI在passwd是用户锁屏密码的地方调用带有命令cryptfs enablecrypto inplace的vold。
vold检查错误,如果不能被加密,返回-1,并且在日志中打印原因。如果能够加密,设置属性 vold.decrypt为
trigger_shutdown_framework。这会使init.rc停止位于类late_start和main中的服务。
vold卸载/mnt/sdcard,然后卸载/data。
vold然后设置加密映射,该映射创建一个虚拟加密块设备,该设备映射到真实的块设备上,但是当写入的时候加密每一个扇区,当读取的时候解密每一个扇区。vold然后创建和编写加密元数据。
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,他每5秒钟查询一下属性然后更新进度条。每次他加密了一部分分区,加密程序就会更新 vold.encrypt_progress。
当/data被成功加密了,vold会清除位于元数据中的标志ENCRYPTION_IN_PROGRESS,然后重启。
如果某些原因重启失败,vold会设置属性 vold.encrypt_progress为
error_reboot_failed
,并且UI应该会展示一个信息询问用户按下按钮重启。这种情况永远也不期望发生。
这种情况发生在当你想启动一个没有密码的加密设备的时候。因为Android 5.0设备在首次启动的时候被加密,应该没有设置密码,因此应该是默认加密状态。
检测Android设备已被加密,因为/data不能被挂载,并且标志flagsencryptable
或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是使用密码加密的。
init设置下面5个属性来保存初始化挂载选项,这些选项用于带参数的/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-digit hex number preceded by 0x)框架启动,然后查看vold.decrypt是否被设置为 trigger_restart_min_framework。这告诉框架这是在tmpfs /data磁盘中启动的,他需要获取用户密码。
首先,他需要确定磁盘是否被正确加密。他发送命令cryptfs cryptocomplete到vold。如果加密完全成功,则返回0.如果内部错误,返回-1.或者是如果加密没有完全成功返回-2。vold通过查看用于CRYPTO_ENCRYPTION_IN_PROGRESS标志的加密元数据来决定这个。如果被设置了,加密进程被中断了,在设备中没有可用的数据。如果vold返回一个错误,UI应该向用户展示一个信息来重启和工厂重置设备,并且给用户一个按钮来这样做。
一旦cryptfs cryptocomplete成功了,框架就会展示一个UI询问磁盘密码。UI通过向vold发送命令cryptfs checkpw
来检查密码。如果密码正确(这是由正确挂载到临时位置的加密/data决定的,然后卸载塔),vold在属性ro.crypto.fs_crypto_blkdev中保存解密块设备的名称,并且向UI返回状态0.如果密码不正确,向UI返回-1。
UI弹出一个加密启动图形,然后使用命令 cryptfs restart
. vold设置属性
vold.decrypt为trigger_reset_main来调用vold,这样会使init.rc
去处理class_reset main。这会在主类中停止所有的服务,这允许tmpfs /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文件系统启动他所有的服务,并且系统准备投入使用。
一个解密失败的设备可能是下面几个原因。设备使用常规系列步骤开始启动:
但是框架打开之后,设备可能会遇到一些错误:
如果这些错误没有解决,提示用户进行工厂清除:
如果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.UI应该会展示一个信息框说加密失败,并且提供一个按钮提示用户恢复出厂重置设备。
加密键值存储在加密元数据中。硬件支持是通过使用可信环境签名实现的。在以前,我们通过向用户密码和存储salt应用scrypt来加密主键。为了使键抵抗攻击,我们通过使用一个存储的TEE键签名一个组合键值来继承他的算法。组合签名通过一个scrypt的应用编程一个合适的密钥长度。这个密钥被使用去加密和解密主密钥。下面是存储这个密钥的过程:
当用户选择在设置中修改或移除密码的时候,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设置开始进度条来加密或者是提示用户输入密码,依赖于ro.crypto.state的值 |
vold.encrypt_progress |
当框架启动的时候,如果这个属性被设置,进入进度条模式 |
vold.encrypt_progress 0 to 100 |
进度条应该会展示百分值集合。 |
vold.encrypt_progress error_partially_encrypted |
进度条应该展示一个加密失败提示信息,并且给用户一个选项来工厂重置设备。 |
vold.encrypt_progress error_reboot_failed |
进度条应该显示一个信息说加密完成,并且给用户提供一个按钮提示用户重启设备。这个错误我们不期望发生。 |
vold.encrypt_progress error_not_encrypted |
进度条应该展示一个信息框说发生了一个错误,没有数据被加密,并且给用户一个按钮提示用户重启系统。 |
vold.encrypt_progress error_shutting_down |
进度条没有运行,所以不清楚谁回应这个错误。他应该不会发生。 |
vold.post_fs_data_done 0 |
在设置vold.decrypt到trigger_post_fs_data之前 由vold设置 |
vold.post_fs_data_done 1 |
在完成任务post-fs-data之后由init.rc设置 |
属性 | 描述 |
---|---|
ro.crypto.fs_crypto_blkdev |
由vold命令checkpw 设置用于后面被vold命令restart使用。 |
ro.crypto.state unencrypted |
由init设置说这个系统正在用未加密的/data ro.crypto.state encrypted运行。由init设置说这个系统正在用加密的/data运行 |
|
在他尝试去挂载带有从 init.rc . vold中传来的参数的/data的时候, 这五个属性由init设置来设置加密映射 |
ro.crypto.tmpfs_options |
当挂载tmpfs /data文件系统的时候,由init.rc使用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