android 全磁盘加密
什么是全磁盘加密?
全磁盘加密是使用一个密钥来为android设备上所有的用户数据加密的过程。一旦设备被加密,所有的用户创建的数据都将会在提交的磁盘之前自动加密,在读取之前都会自动解密。
Android 5.0中添加了啥
- 创建了快速加密,该加密方式仅仅加密在数据分区中使用块设备的数据来避免第一次启动耗费较长时间。仅仅ext4和f2fs文件系统支持快速加密。
- 在首次启动的时候添加forceencrypt标志来加密
- 增加了对模式的支持和没有密码的加密
- 增加使用可信执行环境的密钥的硬件支持存储。
注意:升级到Android5.0并且被加密的设备可能会数据恢复出厂后返回一个未加密状态。在首次启动时加密的新的Android 5.0设备不能返回到未加密状态。
Android全磁盘加密是如何工作的
Android全磁盘加密基于dm-crypy,他是一个内核特征,工作在块设备层。出于这个原因,加密是和嵌入式多媒体卡和作为块设备的闪存设备一起工作的。在YAFFS文件系统中,加密是不可能工作的,他是直接和NAND闪存芯片工作的。
加密算法是带有CBC和 ESSIV:SHA256的128高级加密标准。主键是通过128位AES通过OpenSSL库被加密的。你必须要使用128位或更大位数的键值。
注意:OEMs能够使用128位或更高位数来加密主键。
在Android5.0的发行版中,有四种加密状态:
- 默认
- PIN
- 密码
- 模式
在第一次启动的时候,设备创建一个随机生成的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。
- core:在启动后永远不关闭
- main:在磁盘密码输入后关机在重启
- late_restart:在/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"
triggersinit.rc
, which will causevold
to encrypt/data
with no password. (None is set because this should be a new device.) - 挂载tmpfs
vold挂载一个临时文件系统 /data(从ro.crypto.tmpfs_options中使用tmpfs选项),并且设置属性
vold.encrypt_progress
为0.vold为启动一个加密的系统准备tmpfs /data,并且设置属性vold.decrypt为trigger_restart_min_framework - 提出框架,显示进度
因为该设备几乎没有数据去加密了,进度条也不会显示了,因为加密过程会很快。 - 当/data被加密之后,卸下框架
vold设置vold.decrypt为 开始defaultcrypto
服务的trigger_default_encryption。(这个会开始下面的流用于挂载一个默认的加密用户数据)。trigger_default_encryption检查加密类型来查看/data是否是带有密码被加密的。因为Android5.0设备在首次启动的时候被加密,应该没有密码设置;因此我们解密并且挂载/data。 - 挂载/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中的服务。 - 卸载/data
vold卸载/mnt/sdcard,然后卸载/data。 - 开始加密/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,他每5秒钟查询一下属性然后更新进度条。每次他加密了一部分分区,加密程序就会更新vold.encrypt_progress。
- 当/data被加密,重启
当/data被成功加密了,vold会清除位于元数据中的标志ENCRYPTION_IN_PROGRESS,然后重启。如果某些原因重启失败,vold会设置属性
vold.encrypt_progress为
error_reboot_failed
,并且UI应该会展示一个信息询问用户按下按钮重启。这种情况永远也不期望发生。
开启一个带有默认加密的加密设备
这种情况发生在当你想启动一个没有密码的加密设备的时候。因为Android 5.0设备在首次启动的时候被加密,应该没有设置密码,因此应该是默认加密状态。
- 检测加密的没有密码的/data
检测Android设备已被加密,因为/data不能被挂载,并且标志flagsencryptable
或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设置下面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被卸载。 - 挂载/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.UI应该会展示一个信息框说加密失败,并且提供一个按钮提示用户恢复出厂重置设备。
存储加密键
加密键值存储在加密元数据中。硬件支持是通过使用可信环境签名实现的。在以前,我们通过向用户密码和存储salt应用scrypt来加密主键。为了使键抵抗攻击,我们通过使用一个存储的TEE键签名一个组合键值来继承他的算法。组合签名通过一个scrypt的应用编程一个合适的密钥长度。这个密钥被使用去加密和解密主密钥。下面是存储这个密钥的过程:
- 生成随机16位的磁盘加密密钥(DEK)和16为salt。
- 向用户密码应用scrypt和salt来生成32位的中间密钥1(IK1)。
- 使用0填充IK1到硬件绑定私钥(HBK)的大小。特别的,我们填充为: 00 || IK1 || 00..00;一个0位:32 IK1 bytes, 223 zero bytes.
- 签名带有HBK的IK1来生成256位的IK2
- 将scrypt应用到IK2和salt中来生成32为IK3
- 使用IK3的前16位作为KEK,后16位作为IV
- 加密带有AES_CBS,带有密钥KEK的DEK,并且初始化向量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的值 |
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设置 |
init属性
属性 | 描述 |
---|---|
ro.crypto.fs_crypto_blkdev |
由vold命令checkpw 设置用于后面被vold命令restart使用。 |
ro.crypto.state unencrypted |
由init设置说这个系统正在用未加密的/data ro.crypto.state encrypted运行。由init设置说这个系统正在用加密的/data运行 |
ro.crypto.fs_type
|
在他尝试去挂载带有从 init.rc . vold中传来的参数的/data的时候, 这五个属性由init设置来设置加密映射 |
ro.crypto.tmpfs_options |
当挂载tmpfs /data文件系统的时候,由init.rc使用init应该使用的参数来设置。 |
Init行为
on post-fs-dataon nonencryptedon property:vold.decrypt=trigger_reset_mainon property:vold.decrypt=trigger_post_fs_dataon property:vold.decrypt=trigger_restart_min_frameworkon property:vold.decrypt=trigger_restart_frameworkon property:vold.decrypt=trigger_shutdown_frameworkon property:vold.decrypt=trigger_encryptionon property:vold.decrypt=trigger_default_encryption