mthoutai

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理




android 全磁盘加密

什么是全磁盘加密?


全磁盘加密是使用一个密钥来为android设备上全部的用户数据加密的过程。一旦设备被加密,全部的用户创建的数据都将会在提交的磁盘之前自己主动加密,在读取之前都会自己主动解密。


Android 5.0中加入了啥

  • 创建了高速加密,该加密方式只加密在数据分区中使用块设备的数据来避免第一次启动耗费较长时间。只ext4和f2fs文件系统支持高速加密。
  • 在首次启动的时候加入forceencrypt标志来加密
  • 添加了对模式的支持和没有password的加密
  • 添加使用可信执行环境的密钥的硬件支持存储。

注意:升级到Android5.0而且被加密的设备可能会数据恢复出厂后返回一个未加密状态。在首次启动时加密的新的Android 5.0设备不能返回到未加密状态。


Android全磁盘加密是怎样工作的

Android全磁盘加密基于dm-crypy,他是一个内核特征,工作在块设备层。出于这个原因,加密是和嵌入式多媒体卡和作为块设备的闪存设备一起工作的。在YAFFS文件系统中,加密是不可能工作的,他是直接和NAND闪存芯片工作的。

加密算法是带有CBC和 ESSIV:SHA256的128高级加密标准。主键是通过128位AES通过OpenSSL库被加密的。你必需要使用128位或更大位数的键值。

注意:OEMs可以使用128位或更高位数来加密主键。

在Android5.0的发行版中,有四种加密状态:

  • 默认
  • PIN
  • password
  • 模式

在第一次启动的时候。设备创建一个随机生成的128位主键。然后使用默认password和存储的salt进行哈希运算。默认password是”default_password”。

然而,结果散列也需要通过TEE进行签名。他使用一个签名的散列来加密主键。

你可以在Android源代码文件cryptfs.c中找到默认password。

当用户在设备中设置了PIN或者是password。只128位的键被又一次加密和存储。

(用户PIN/password/模式的改变不会造成用户数据又一次加密)。注意管理的设备可能会受到PIN,模式或password的限制。

加密是由init和vold管理的。

init调用vold。vold设置位于init中的属性来触发事件。

系统的其它部分也会依据这些属性来进行工作,比如报告状态,查询password。或者是发生致命错误的时候及时工厂重置。

为了在vold中激活加密特征。系统使用命令行工具vdc’s cryptfs执行命令: checkpwrestartenablecryptochangepwcryptocompleteverifypw,setfieldgetfieldmountdefaultencryptedgetpwtypegetpw, 和clearpw.

为了进行加密,解密或清除/data,/data必需要被挂载。然而,为了可以显示用户接口,框架一定要执行而且框架需要/data来执行。为了解决这个难题,在/data中会挂载一个暂时的文件系统。这使得安卓可以提示password,显示进展,或在需要的时候进行数据擦除。在从暂时文件系统到真实的/data文件系统的转换的时候强加了一些限制,系统必需要停止打开暂时文件系统中文件的每个进程,然后在真实的/data文件系统中重新启动这些进程。

为了这样做,全部的服务都必须在这三个组中的一个中:core,main和late_start。

  • core:在启动后永远不关闭
  • main:在磁盘password输入后关机在重新启动
  • late_restart:在/data被解密而且挂载之前不要開始


为了激发这些行为,vold.decrypt属性被设置为各种字符串。为了杀死而且重新启动服务,init命令为:

  • class_reset:停止服务。而且同意使用class_start进行重新启动。
  • class_start:重新启动服务
  • class_stop:停止一个服务,而且加入一个SVC_DISABLED标志。

    停止的服务不向class_start做出回应。


对于一个加密的设备来说有四个流。一个设备只加密一次。然后遵循一个正常的启动流。

  • 加密一个原先未加密的设备:
    • 使用forceencrypt加密一个新的设备:在首次启动的时候强制性加密(在Android L開始)
    • 加密一个存在的设备:用户初始化加密(Android K而且更早)
  • 启动一个加密设备:
    • 启动一个不带password的加密设备:启动一个没有设置password的加密设备(相关的设备运Android 5.0或更新)
    • 開始一个带有password的加密设备:启动一个设置password的加密设备。

除了这些流,设备加密/data就会失败。每个流在以下都会详解。

加密一个带有/forceencrypt新设备

这是一个Android 5.0设备正常的首次启动。

  1. 检測到带有/forceencrypt标志的未加密文件系统

    /data没有被加密,可是必需要进行加密。由于 /forceencrypt托付他这样做。

    卸载/data

  2. 開始加密/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.)

  3. 挂载tmpfs

    vold挂载一个暂时文件系统 /data(从ro.crypto.tmpfs_options中使用tmpfs选项)。而且设置属性vold.encrypt_progress 为0.vold为启动一个加密的系统准备tmpfs /data,而且设置属性vold.decrypt为trigger_restart_min_framework

  4. 提出框架。显示运行进度

    由于该设备差点儿没有数据去加密了,进度条也不会显示了,由于加密过程会非常快。

  5. 当/data被加密之后,卸下框架

    vold设置vold.decrypt为 開始defaultcrypto 服务的trigger_default_encryption。(这个会開始以下的流用于挂载一个默认的加密用户数据)。trigger_default_encryption检查加密类型来查看/data是否是带有password被加密的。由于Android5.0设备在首次启动的时候被加密,应该没有password设置。因此我们解密而且挂载/data。

  6. 挂载/data

    init然后使用从ro.crypto.tmpfs_options中选出的參数在tmpfs的RAMDisk 中挂载/data。ro.crypto.tmpfs_options是在init.rc中被设置的。

  7. 启动框架

    设置vold为trigger_restart_framework。他继续常规的启动进程。


加密一个如今的设备


这样的情况发生在你加密一个未加密的Android k或更早版本号的设备,该设备被一直到Android L的时候。

注意,这个和在K中使用的流是一样的。

这个进程是用户初始化的。而且在代码中被引用作为”就地加密”。当一个空户选择去加密一个设备的时候,UI确保电量是全然充电状态而且AC适配器被插入。所以有足够的变量来完毕加密进程。

警告:假设设备在完毕加密之前耗尽电量而且关机了,在部分加密状态下会留下文件数据。设备必需要工厂重置,全部的数据都会丢失。

为了使能就地加密,vold開始一个循环来读取真实块设备的每个扇区,然后将他写到加密块设备中。vold在读取和写数据之前。检查扇区是否正在使用。这会使得在差点儿没有数据的新设备上加密会非常快。


设备状态:设置ro.crypto.state = "unencrypted" 而且执行 on nonencrypted init触发器来继续启动。

  1. 检查password

    UI在passwd是用户锁屏password的地方调用带有命令cryptfs enablecrypto inplace的vold。

  2. 卸载框架

    vold检查错误。假设不能被加密,返回-1。而且在日志中打印原因。

    假设可以加密,设置属性 vold.decrypt为trigger_shutdown_framework。

    这会使init.rc停止位于类late_start和main中的服务。

  3. 卸载/data

    vold卸载/mnt/sdcard。然后卸载/data。

  4. 開始加密/data

    vold然后设置加密映射。该映射创建一个虚拟加密块设备。该设备映射到真实的块设备上。可是当写入的时候加密每个扇区,当读取的时候解密每个扇区。vold然后创建和编写加密元数据。

  5. 当加密的时候。挂载tmpfs

    vold挂载一个tmpfs /data(从ro.crypto.tmpfs_options中使用tmpfs选项),而且设置属性vold.encrypt_progress为0.vold为启动一个加密系统准备tmpfs /data,而且设置属性vold.decrypt为trigger_restart_min_framework。

  6. 提出框架,显示运行进度

    trigger_restart_min_framework会使init.rc開始一个服务的main类。

    当框架看到vold.encrypt_progress被设置为0,他就会显示运行进度条UI,他每5秒钟查询一下属性然后更新进度条。每次他加密了一部分分区,加密程序就会更新 vold.encrypt_progress。

  7. 当/data被加密。重新启动

    当/data被成功加密了,vold会清除位于元数据中的标志ENCRYPTION_IN_PROGRESS。然后重新启动。

    假设某些原因重新启动失败,vold会设置属性 vold.encrypt_progress为 error_reboot_failed,而且UI应该会展示一个信息询问用户按下button重新启动。这样的情况永远也不期望发生。

开启一个带有默认加密的加密设备

这样的情况发生在当你想启动一个没有password的加密设备的时候。由于Android 5.0设备在首次启动的时候被加密,应该没有设置password,因此应该是默认加密状态。

  1. 检測加密的没有password的/data

    检測Android设备已被加密,由于/data不能被挂载,而且标志flagsencryptable 或forceencrypt 之中的一个被设置了。vold设置 vold.decrypt为

    trigger_default_encryption,他用于启动defaultcrypto 。trigger_default_encryption 检查加密类型来查看/data有无password加密。

  2. 解密/data

    在块设备中,创建dm-crypt设备。所以设备已经可以使用。

  3. 挂载/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中的服务。

  4. 开启框架

    如今,框架使用加密的/data启动全部他的服务。而且系统也可以正常使用了。

开启一个不是默认加密的加密设备

这样的情况发生在,当你启动一个设置password的加密设备的时候。这个设备的password可以是pin,模式或password。

  1. 检測带有password的加密设备

    检測到Android设备被加密了,由于标志ro.crypto.state = “encrypted”

    vold设置vold.decrypt为trigger_restart_min_framework,由于/data是使用password加密的。

  2. 挂载tmpfs

    init设置以下5个属性来保存初始化挂载选项,这些选项用于带參数的/data。

    他们是从init.rcvold中传过来的,使用这些属性来设置加密映射。

    1. ro.crypto.fs_type
    2. ro.crypto.fs_real_blkdev
    3. ro.crypto.fs_mnt_point
    4. ro.crypto.fs_options
    5. ro.crypto.fs_flags (ASCII 8-digit hex number preceded by 0x)
  3. 启动框架以提示输入password

    框架启动,然后查看vold.decrypt是否被设置为 trigger_restart_min_framework。

    这告诉框架这是在tmpfs /data磁盘中启动的,他需要获取用户password。

    首先,他需要确定磁盘是否被正确加密。他发送命令cryptfs cryptocomplete到vold。

    假设加密全然成功,则返回0.假设内部错误,返回-1.或者是假设加密没有全然成功返回-2。vold通过查看用于CRYPTO_ENCRYPTION_IN_PROGRESS标志的加密元数据来决定这个。假设被设置了。加密进程被中断了,在设备中没有可用的数据。

    假设vold返回一个错误。UI应该向用户展示一个信息来重新启动和工厂重置设备,而且给用户一个button来这样做。

  4. 解密带有password的数据

    一旦cryptfs cryptocomplete成功了。框架就会展示一个UI询问磁盘password。

    UI通过向vold发送命令cryptfs checkpw 来检查password。假设password正确(这是由正确挂载到暂时位置的加密/data决定的,然后卸载塔),vold在属性ro.crypto.fs_crypto_blkdev中保存解密块设备的名称,而且向UI返回状态0.假设password不对。向UI返回-1。

  5. 停止框架

    UI弹出一个加密启动图形。然后使用命令 cryptfs restartvold设置属性vold.decrypt为trigger_reset_main来调用vold,这样会使init.rc 去处理class_reset main。这会在主类中停止全部的服务。这同意tmpfs /data被卸载。

  6. 挂载/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中的服务。

  7. 开启全框架

    如今,框架就会使用解密/data文件系统启动他全部的服务,而且系统准备投入使用。

失败

一个解密失败的设备可能是以下几个原因。设备使用常规系列步骤開始启动:

  1. 检測带有password的加密设备
  2. 挂载tmpfs
  3. 开启框架提示输入password

可是框架打开之后,设备可能会遇到一些错误:

  • password匹配可是不能解密数据
  • 用户输入错误password30次

假设这些错误没有解决,提示用户进行工厂清除:

假设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应该会展示一个信息框说加密失败,而且提供一个button提示用户恢复出厂重置设备。

存储加密键

加密键值存储在加密元数据中。

硬件支持是通过使用可信环境签名实现的。在曾经,我们通过向用户password和存储salt应用scrypt来加密主键。为了使键抵抗攻击,我们通过使用一个存储的TEE键签名一个组合键值来继承他的算法。组合签名通过一个scrypt的应用编程一个合适的密钥长度。这个密钥被使用去加密和解密主密钥。以下是存储这个密钥的过程:

  1. 生成随机16位的磁盘加密密钥(DEK)和16为salt。

  2. 向用户password应用scrypt和salt来生成32位的中间密钥1(IK1)。

  3. 使用0填充IK1到硬件绑定私钥(HBK)的大小。

    特别的。我们填充为: 00 || IK1 || 00..00;一个0位:32 IK1 bytes, 223 zero bytes.

  4. 签名带有HBK的IK1来生成256位的IK2
  5. 将scrypt应用到IK2和salt中来生成32为IK3
  6. 使用IK3的前16位作为KEK,后16位作为IV
  7. 加密带有AES_CBS,带有密钥KEK的DEK,而且初始化向量IV
改动password

当用户选择在设置中改动或移除password的时候,UI发送命令cryptfs changepw到vold中,vold又一次加密带有新password的磁盘主密钥。

加密属性


vold和init通过设置属性相互交流。

以下一些用于加密的可用属性。


Vold属性

属性描写叙述
vold.decrypt trigger_encryption加密没有password的设备
vold.decrypt trigger_default_encryption检查设备是否是在没有password的情况下加密的。假设是,解密而且挂载他。假设不是,设置vold.decrypt为trigger_restart_min_framework.
vold.decrypt trigger_reset_main由vold设置关闭UI来询问磁盘password
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设置開始进度条来加密或者是提示用户输入password,依赖于ro.crypto.state的值
vold.encrypt_progress当框架启动的时候,假设这个属性被设置,进入进度条模式
vold.encrypt_progress 0 to 100进度条应该会展示百分值集合。
vold.encrypt_progress error_partially_encrypted进度条应该展示一个加密失败提示信息。而且给用户一个选项来工厂重置设备。

vold.encrypt_progress error_reboot_failed进度条应该显示一个信息说加密完毕,而且给用户提供一个button提示用户重新启动设备。

这个错误我们不期望发生。

vold.encrypt_progress error_not_encrypted进度条应该展示一个信息框说发生了一个错误,没有数据被加密。而且给用户一个button提示用户重新启动系统。

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
ro.crypto.fs_real_blkdev 
ro.crypto.fs_mnt_point
ro.crypto.fs_options
ro.crypto.fs_flags 

在他尝试去挂载带有从 init.rcvold中传来的參数的/data的时候。这五个属性由init设置来设置加密映射
ro.crypto.tmpfs_options当挂载tmpfs /data文件系统的时候。由init.rc使用init应该使用的參数来设置。

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

posted on 2017-06-22 21:55  mthoutai  阅读(741)  评论(0编辑  收藏  举报