前言
Android 的安全性问题一直备受关注,Google 在 Android 系统的安全方面也是一直没有停止过更新,努力做到更加安全的手机移动操作系统。
在 Android 的安全性方面,有很多模块:
- 内核安全性
- 应用安全性
- 应用签名
- 身份验证
- Trusty TEE
- SELinux
- 加密
等等其中,加密又分全盘加密(Android 4.4 引入)和文件级加密(Android 7.0 引入),本文将论述加密中的全盘加密的基本知识。全盘加密在 Android 4.4 中引入,在 Android 5.0 中做了比较大的更新。
本文部分片段摘自 Android 官网,融合笔者的个人理解和知识。
什么是全盘加密
全盘加密是使用已加密的密钥对 Android 设备上的所有用户数据进行编码的过程。设备经过加密后,所有由用户创建的数据在写入磁盘之前都会自动加密,并且所有读取操作都会在将数据返回给调用进程之前自动解密数据。
Android 5.0 中又引入了以下新功能:
- 创建了快速加密方式,这种加密方式只会对数据分区中已使用的分块进行加密,以免首次启动用时过长。目前只有 EXT4 和 F2FS 文件系统支持快速加密。
- 添加了 forceencrypt fstab 标记,以便在首次启动时进行加密。
- 添加了对解锁图案和无密码加密的支持。
- 添加了由硬件支持的加密密钥存储空间,该空间使用可信执行环境(TEE,例如 TrustZone)的签名功能。
全盘加密运作方式
Android 全盘加密基于在块设备层运行的内核功能 dm-crypt。因此,这种加密方式适用于以块设备的形式呈现给内核的嵌入式多媒体卡 (eMMC) 和类似闪存设备。YAFFS 会直接与原始 NAND 闪存芯片交互,无法进行全盘加密。全盘加密采用的是 128 位高级加密标准 (AES) 算法(搭配密码块链接 (CBC) 和 ESSIV:SHA256)。对主密钥进行加密时使用的是 128 位 AES 算法,并会调用 OpenSSL 库。对于该密钥,您必须使用 128 位或更多位(可以选择 256 位)。
Android 5.0 版中有以下 4 种加密状态:
- 默认
- PIN 码
- 密码
- 解锁图案
首次启动时,设备会创建一个随机生成的 128 位主密钥,然后会使用默认密码和存储的盐对其进行哈希处理。默认密码是“default_password”。不过,设备还会通过 TEE(例如 TrustZone)为生成的哈希签名。TEE 会使用相应签名的哈希来加密主密钥。
您可以在 Android 开放源代码项目 cryptfs.c 文件中找到定义的默认密码。
当用户在设备上设置 PIN 码/通行码或密码时,只有 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 不得处于装载状态。但要显示任何界面,框架都必须启动,而框架需要 /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 5.0 设备首次启动时的常规流程。
1、检测带有 forceencrypt 标记的未加密文件系统
/data 未加密,但需要加密,因为 forceencrypt 强制要求进行此项加密。卸载 /data。
2、开始加密 /data
vold.decrypt = “trigger_encryption” 会触发 init.rc,从而使 vold 对 /data 进行无密码加密。(因为这应该是新设备,还没有设置密码。)
3、装载 tmpfs
vold 会装载一个 tmpfs /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 设为 trigger_default_encryption,这会启动 defaultcrypto 服务。(这会启动以下流程来装载默认的已加密用户数据。)trigger_default_encryption 会检查加密类型,以了解 /data 加密是否使用了密码。由于 Android 5.0 设备是在首次启动时加密,应该没有设置任何密码,因此我们要解密并装载 /data。
6、装载 /data
接下来,init 会使用从 ro.crypto.tmpfs_options(在 init.rc 中设置)中选取的参数在 tmpfs RAMDisk 中装载 /data
7、启动框架
将 vold 设为 trigger_restart_framework,这会继续常规启动过程。
启动未进行默认加密的已加密设备
当您启动设有密码的已加密设备时,则会发生该流程。设备的密码可以是 PIN 码、解锁图案或密码。
1、检测设有密码的已加密设备
会发现 Android 设备已加密,因为设置了 ro.crypto.state = “encrypted” 标记
由于 /data 是使用密码加密的,因此 vold 会将 vold.decrypt 设为 trigger_restart_min_framework。
2、装载 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 位十六进制数字,以 0x 开头)
3、启动框架以提示输入密码
框架会启动并看到 vold.decrypt 已设为 trigger_restart_min_framework。这让框架知道自己是在 tmpfs /data 磁盘中启动的,并且需要获取用户密码。
不过,它首先需要确认磁盘是否已经过适当加密。它会向 vold 发送 cryptfs cryptocomplete 命令。 如果加密已成功完成,vold 会返回 0;如果发生内部错误,则会返回 -1;如果加密未成功完成,则会返回 -2。vold 通过查看 CRYPTO_ENCRYPTION_IN_PROGRESS 标记的加密元数据来确定应返回的值。如果设置了此标记,则表示加密过程中断了,并且设备上没有可用的数据。如果 vold 返回错误,界面中应显示一条消息,提示用户重新启动设备并将其恢复出厂设置,并且界面中应为用户提供一个用于执行该操作的按钮。
4、通过密码解密数据
cryptfs cryptocomplete 成功后,框架会显示一个界面,提示用户输入磁盘密码。界面会向 vold 发送 cryptfs checkpw 命令来检查用户输入的密码。如果密码正确(通过以下方式判定:在临时位置成功装载已解密的 /data,然后将其卸载),vold 会将已解密块设备的名称保存在 ro.crypto.fs_crypto_blkdev 属性中,并向界面返回状态 0。如果密码不正确,则向界面返回 -1。
5、停止框架
界面会显示加密启动图形,然后使用 cryptfs restart 命令调用 vold。vold 会将 vold.decrypt 属性设为 trigger_reset_main,这会使 init.rc 执行 class_reset main 命令。此命令会停止 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 文件系统启动其所有服务,接下来系统就可以使用了。
代码解读
结合上章节的流程,下面用代码来解析启动未进行默认加密的已加密设备这个流程。
这个配置定义在 device/lge/bullhead/fstab_fbe.bullhead 文件中。
如上面的代码,在 /data 的末尾加上 fileencryption,便会进行全盘加密。
步骤1:检测设有密码的已加密设备
这个方法定义在文件 system/core/init/builtins.cpp 中。
这个服务定义在文件 system/vold/vdc.rc 中。
这个方法定义在文件 vold/CryptCommandListener.cpp 中。
这个方法定义在文件 system/vold/cryptfs.c 中。
这个服务定义在服务 system/core/rootdir/init.rc 中。
class_start main 可知重启 main 类别的服务。main 类别的服务包括:
会重启 zygote。
步骤2:装载 tmpfs
Zygote 启动后,会 fork() system_process 进程,就是运行 SystemServer 代码了。但是 system_process 的运行需要正常的用户空间(/data),所以,需要临时挂载 tmpfs 分区,这个分区是在内存里分配的临时空间。
这个方法定义在文件 system/vold/CommandListener.cpp 中。
这个方法定义在文件 system/core/fs_mgr/fs_mgr.c 中。
步骤3:启动框架以提示输入密码
这个方法定义在文件frameworks/base/services/java/com/android/server/SystemServer.java 中。
这个方法定义在文件 frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java 中
这个方法定义在文件 frameworks/base/core/java/android/content/pm/PackageParser.java 中。
安卓中定义为 coreApp 的应用有:
Framework-res.apk 的 manifest 配置文件如下:
步骤4:通过密码解密数据
这个过程不再阐述。
步骤5:停止框架
这个 setion 定义在文件 system/core/rootdir/init.rc 中。
步骤6:装载 /data
这个方法定义在文件 system/core/init/builtins.cpp 中。
这个 setion 定义在文件 system/vold/vdc.rc 中。
这个 setion 定义在文件 system/vold/vdc.rc 中。
这个方法定义在文件 system/vold/CryptCommandListener.cpp 中。
int cryptfs_enable(char *howarg, int type, char *passwd, int no_ui)
这个方法定义在文件 system/vold/CryptCommandListener.cpp 中。
这个方法定义在文件 system/vold/cryptfs.c 中。
这个方法定义在文件 system/vold/cryptfs.c 中。
这个方法定义在文件 system/vold/cryptfs.c 中。
这个 setion 定义在文件 system/core/rootdir/init.rc 中。
这个 setion 定义在文件 system/core/rootdir/init.rc 中。
这个方法定义在文件 system/vold/cryptfs.c 中。
步骤7:启动整个框架
vold.decrypt = trigger_restart_framework, framework 就可以正常启动了。
加密属性
vold 和 init 之间通过设置属性进行通信。下面列出了可用的加密属性。
vold属性
init 属性
init 操作
Android 全盘加密分析到此为止。
参考 https://source.android.com/security/encryption/full-disk
转载:http://blog.csdn.net/myfriend0/article/details/76615114