全盘加密

全盘加密是使用加密密钥对 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 闪存芯片对话。

加密算法是带有密码块链接 (CBC) 和 ESSIV:SHA256 的 128 高级加密标准 (AES)。主密钥通过调用 OpenSSL 库使用 128 位 AES 进行加密。您必须使用 128 位或更多位作为密钥(256 位是可选的)。

注意: OEM 可以使用 128 位或更高位来加密主密钥。

在Android 5.0版本中,有四种加密状态:

  • 默认
  • 别针
  • 密码
  • 图案

首次启动时,设备会创建一个随机生成的 128 位主密钥,然后使用默认密码和存储的盐对其进行哈希处理。默认密码为:“default_password” 但是,生成的哈希值也会通过 TEE(例如 TrustZone)进行签名,TEE 使用签名的哈希值来加密主密钥。

您可以在 Android 开源项目cryptfs.cpp文件中找到定义的默认密码。

当用户在设备上设置 PIN/pass 或密码时,只有 128 位密钥会被重新加密和存储。 (即,用户 PIN/密码/模式更改不会导致用户数据重新加密。)请注意,托管设备可能受到 PIN、模式或密码限制。

加密由initvold管理。 init调用vold ,vold 设置属性以触发 init 中的事件。系统的其他部分也会查看属性来执行任务,例如报告状态、询问密码或在发生致命错误时提示恢复出厂设置。为了调用vold中的加密功能,系统使用命令行工具vdccryptfs命令: checkpwrestartenablecryptochangepwcryptocompleteverifypwsetfieldgetfieldmountdefaultencryptedgetpwtypegetpwclearpw

为了加密、解密或擦除/data ,不得安装/data 。但是,为了显示任何用户界面 (UI),框架必须启动,并且框架需要/data才能运行。为了解决这个难题,在/data上安装了一个临时文件系统。这允许 Android 根据需要提示输入密码、显示进度或建议擦除数据。它确实施加了限制,为了从临时文件系统切换到真正的/data文件系统,系统必须停止临时文件系统上打开文件的每个进程,并在真正的/data文件系统上重新启动这些进程。为此,所有服务必须属于三个组之一: coremainlate_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 。下面详细解释每个流程。

使用forcecrypt加密新设备

这是 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. 提出框架以显示进度

    由于设备实际上没有要加密的数据,因此通常不会实际出现进度条,因为加密发生得如此之快。有关进度 UI 的更多详细信息,请参阅加密现有设备

  5. /data加密后,取下框架

    voldvold.decrypt设置为trigger_default_encryption ,从而启动defaultcrypto服务。 (这将启动下面用于安装默认加密用户数据的流程。) trigger_default_encryption检查加密类型以查看/data是否使用密码加密。由于Android 5.0设备在首次启动时已加密,因此不应设置密码;因此我们解密并挂载/data

  6. 挂载/data

    然后init使用从ro.crypto.tmpfs_options获取的参数(在init.rc中设置)将/data挂载到 tmpfs RAMDisk 上。

  7. 启动框架

    voldvold.decrypt设置为trigger_restart_framework ,这将继续通常的启动过程。

加密现有设备

当您加密已迁移到 L 的未加密 Android K 或更早版本的设备时,就会发生这种情况。

此过程是用户发起的,在代码中称为“就地加密”。当用户选择加密设备时,UI 会确保电池已充满电并且交流适配器已插入,以便有足够的电量来完成加密过程。

警告:如果设备在完成加密之前电量耗尽并关闭,文件数据将处于部分加密状态。设备必须恢复出厂设置,所有数据都会丢失。

为了启用就地加密, vold启动一个循环来读取真实块设备的每个扇区,然后将其写入加密块设备。 vold在读取和写入扇区之前检查该扇区是否正在使用,这使得在几乎没有数据的新设备上加密速度更快。

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

  1. 检查密码

    UI 使用命令cryptfs enablecrypto inplace调用vold ,其中passwd是用户的锁屏密码。

  2. 拆掉框架

    vold检查错误,如果无法加密则返回 -1,并在日志中打印原因。如果它可以加密,则会将属性vold.decrypt设置为trigger_shutdown_framework 。这会导致init.rc停止类late_startmain中的服务。

  3. 创建加密页脚
  4. 创建面包屑文件
  5. 重启
  6. 检测面包屑文件
  7. 开始加密/data

    然后, vold设置加密映射,该映射创建一个虚拟加密块设备,映射到真实块设备,但在写入时加密每个扇区,并在读取时解密每个扇区。 vold然后创建并写出加密元数据。

  8. 加密时挂载 tmpfs

    vold安装 tmpfs /data (使用ro.crypto.tmpfs_options中的 tmpfs 选项)并将属性vold.encrypt_progress设置为 0。 vold准备 tmpfs /data用于启动加密系统,并将属性vold.decrypt设置为: trigger_restart_min_framework

  9. 提出框架以显示进度

    trigger_restart_min_framework导致init.rc启动main服务类。当框架发现vold.encrypt_progress设置为 0 时,它会显示进度条 UI,该 UI 每五秒查询一次该属性并更新进度条。每次加密分区的另一个百分比时,加密循环都会更新vold.encrypt_progress

  10. /data被加密时,更新加密页脚

    /data成功加密后, vold会清除元数据中的ENCRYPTION_IN_PROGRESS标志。

    当设备成功解锁后,密码将用于加密主密钥并更新加密页脚。

    如果由于某种原因重新启动失败, vold会将属性vold.encrypt_progress设置为error_reboot_failed ,并且 UI 应显示一条消息,要求用户按按钮重新启动。预计这种情况永远不会发生。

使用默认加密启动加密设备

这就是当您在没有密码的情况下启动加密设备时会发生的情况。由于 Android 5.0 设备在首次启动时进行加密,因此不应设置密码,因此这是默认加密状态。

  1. 检测没有密码的加密/data

    检测到 Android 设备已加密,因为无法挂载/data并且设置了encryptableforceencrypt标志之一。

    voldvold.decrypt设置为trigger_default_encryption ,这会启动defaultcrypto服务。 trigger_default_encryption检查加密类型以查看/data是否使用密码加密。

  2. 解密/数据

    在块设备上创建dm-crypt设备,以便该设备可供使用。

  3. 挂载/数据

    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启动其所有服务,并且系统已准备好使用。

启动没有默认加密的加密设备

当您启动具有设置密码的加密设备时,就会发生这种情况。设备的密码可以是 PIN、图案或密码。

  1. 使用密码检测加密设备

    检测到 Android 设备已加密,因为标志ro.crypto.state = "encrypted"

    voldvold.decrypt设置为trigger_restart_min_framework ,因为/data是用密码加密的。

  2. 挂载tmpfs

    init设置五个属性来保存为/data指定的初始安装选项以及从init.rc传递的参数。 vold使用这些属性来设置加密映射:

    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 位十六进制数字,前面带有 0x)
  3. 启动框架提示输入密码

    框架启动并看到vold.decrypt设置为trigger_restart_min_framework 。这告诉框架它正在 tmpfs /data磁盘上启动,并且需要获取用户密码。

    然而,首先需要确保磁盘已正确加密。它将命令cryptfs cryptocomplete发送到vold 。如果加密成功完成,则vold返回 0;如果发生内部错误,则返回 -1;如果加密未成功完成,则 vold 返回 -2。 vold通过在加密元数据中查找CRYPTO_ENCRYPTION_IN_PROGRESS标志来确定这一点。如果已设置,则加密过程将被中断,并且设备上没有可用的数据。如果vold返回错误,UI 应向用户显示一条消息,要求用户重新启动设备并恢复出厂设置,并为用户提供一个按钮来执行此操作。

  4. 使用密码解密数据

    一旦cryptfs cryptocomplete成功,框架就会显示一个 UI,要求输入磁盘密码。 UI 通过将命令cryptfs checkpw发送到vold来检查密码。如果密码正确(通过在临时位置成功挂载解密的/data然后卸载它来确定), vold会将解密的块设备的名称保存在属性ro.crypto.fs_crypto_blkdev中,并向 UI 返回状态 0 。如果密码不正确,则向 UI 返回 -1。

  5. 停止框架

    UI 会显示一个加密启动图形,然后使用命令cryptfs restart调用voldvold将属性vold.decrypt设置为trigger_reset_main ,这会导致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. 使用密码检测加密设备
  2. 挂载tmpfs
  3. 启动框架提示输入密码

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

  • 密码匹配但无法解密数据
  • 用户输入错误密码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。然后,UI 应显示一条消息,说明加密失败,并为用户提供一个按钮以将设备重置为出厂设置。

存储加密密钥

加密的密钥存储在加密元数据中。硬件支持是通过使用可信执行环境 (TEE) 签名功能来实现的。之前,我们使用通过对用户密码和存储的盐应用 scrypt 生成的密钥来加密主密钥。为了使密钥能够抵御离机攻击,我们通过使用存储的 TEE 密钥对结果密钥进行签名来扩展此算法。然后,通过另一次 scrypt 应用程序将所得签名转换为适当长度的密钥。然后使用该密钥来加密和解密主密钥。要存储此密钥:

  1. 生成随机 16 字节磁盘加密密钥 (DEK) 和 16 字节盐。
  2. 将 scrypt 应用于用户密码和盐以生成 32 字节中间密钥 1 (IK1)。
  3. 将 IK1 用零字节填充到硬件绑定私钥 (HBK) 的大小。具体来说,我们填充为:00 || IK1 || 00..00; 1 个零字节、32 个 IK1 字节、223 个零字节。
  4. 用 HBK 对 IK1 进行符号填充,生成 256 字节的 IK2。
  5. 将 scrypt 应用于 IK2 和盐(与步骤 2 相同的盐)以生成 32 字节 IK3。
  6. 使用 IK3 的前 16 个字节作为 KEK,后 16 个字节作为 IV。
  7. 使用 AES_CBC、密钥 KEK 和初始化向量 IV 加密 DEK。

更改密码

当用户选择更改或删除设置中的密码时,UI 会将命令cryptfs changepw发送到vold ,然后vold使用新密码重新加密磁盘主密钥。

加密属性

voldinit通过设置属性来相互通信。以下是可用于加密的属性的列表。

沃尔德属性

财产描述
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.rcinit.rc设置。

初始化属性

财产描述
ro.crypto.fs_crypto_blkdev通过vold命令checkpw设置,以便稍后通过vold命令restart使用。
ro.crypto.state unencryptedinit设置表示该系统正在使用未加密的/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在尝试使用从init.rc传入的参数挂载/data时设置。 vold使用这些来设置加密映射。
ro.crypto.tmpfs_optionsinit.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