Android 7.0 及更高版本支持文件级加密 (FBE)。采用文件级加密时,可以使用不同的密钥对不同的文件进行加密,并且可以对这些文件进行单独解密。
本文介绍了如何在新设备上启用文件级加密,以及如何更新系统应用,以充分利用新的 Direct Boot API 并尽可能为用户提供最佳、最安全的体验。
直接启动
借助文件级加密,Android 7.0 中引入了一项称为直接启动的新功能。该功能处于启用状态时,已加密设备在启动后将直接进入锁定屏幕。之前,在使用全盘加密 (FDE) 的已加密设备上,用户在访问任何数据之前都需要先提供凭据,从而导致手机无法执行除最基本操作之外的所有其他操作。例如,闹钟无法运行,无障碍服务不可用,手机无法接电话,而只能进行基本的紧急拨号操作。
引入文件级加密 (FBE) 和新 API 后,便可以将应用设为加密感知型应用,这样一来,它们将能够在受限环境中运行。这些应用将可以在用户提供凭据之前运行,同时系统仍能保护私密用户信息。
在启用了 FBE 的设备上,每位用户均有两个可供应用使用的存储位置:
- 凭据加密 (CE) 存储空间:这是默认存储位置,只有在用户解锁设备后才可用。
- 设备加密 (DE) 存储空间:在直接启动模式期间以及用户解锁设备后均可用。
这种区分能够使工作资料更加安全,因为这样一来,加密不再只基于启动时密码,从而能够同时保护多位用户。
Direct Boot API 允许加密感知型应用访问上述每个区域。应用生命周期会发生一些变化,以便在用户的 CE 存储空间因用户在锁定屏幕上首次输入凭据而解锁时,或者在工作资料提供工作挑战时,通知应用。无论是否实现了 FBE,运行 Android 7.0 的设备都必须要支持这些新的 API 和生命周期。不过,如果没有 FBE,DE 和 CE 存储空间将始终处于解锁状态。
Android 开放源代码项目 (AOSP) 中提供了 EXT4 文件系统中的文件级加密的完整实现。在满足相关要求的设备上,只需启用该实现即可使用该功能。选择使用 FBE 的制造商可能想要了解根据所用系统芯片 (SoC) 优化该功能的方法。
AOSP 中的所有必要程序包均已更新为直接启动感知型程序包。不过,如果设备制造商使用的是这些应用的定制版本,则需要确保至少存在能够提供以下服务的直接启动感知型程序包:
- 电话服务和拨号器
- 用于在锁定屏幕中输入密码的输入法
示例和源代码
Android 提供了文件级加密的参考实现,其中 vold (system/vold) 负责提供用于管理 Android 上的存储设备和存储卷的功能。添加 PDE 会为 vold 提供一些新命令,以便支持对多位用户的 CE 密钥和 DE 密钥进行密钥管理。除了为使用内核中的 EXT4 加密功能而进行的核心更改外,许多系统程序包(包括锁定屏幕和 SystemUI)也经过了修改,以支持 FBE 和“直接启动”功能。其中包括:
- AOSP 拨号器 (packages/apps/Dialer)
- 桌面时钟 (packages/apps/DeskClock)
- LatinIME (packages/inputmethods/LatinIME)*
- “设置”应用 (packages/apps/Settings)*
- SystemUI (frameworks/base/packages/SystemUI)*
*使用 defaultToDeviceProtectedStorage
清单属性的系统应用
通过在 AOSP 源代码树的框架或程序包目录中运行 mangrep directBootAware
命令,可以找到更多加密感知型应用和服务的示例。
依赖关系
要安全地使用 AOSP 提供的 FBE 实现,设备需要满足以下依赖关系:
- 对 EXT4 加密的内核支持(内核配置选项:EXT4_FS_ENCRYPTION)
- 基于 1.0 或 2.0 版 HAL 的 Keymaster 支持。不支持 Keymaster 0.3,因为它既不提供必要的功能,也不能保证为加密密钥提供充分保护。
- 必须在可信执行环境 (TEE) 中实现 Keymaster/Keystore 和 Gatekeeper,以便为 DE 密钥提供保护,从而使未经授权的操作系统(刷到设备上的定制操作系统)无法直接请求 DE 密钥。
- 内核加密性能必须要在使用 AES XTS 时至少达到 50MB/s,以确保良好的用户体验。
- 硬件信任根和验证启动需要绑定到 Keymaster 初始化进程,以确保未经授权的操作系统无法获取设备加密凭据。
注意:存储政策会应用到文件夹及其所有子文件夹。对于以未加密形式存入 OTA 文件夹以及存入系统解密密钥存放文件夹的内容,制造商应加以限制。大多数内容都应存放在凭据加密存储空间(而非设备加密存储空间)内。
实现
最重要的一点是,应根据直接启动开发者文档将诸如闹钟、电话、无障碍功能等应用设为 android:directBootAware。
内核支持
AOSP 提供的文件级加密实现会用到 Linux 4.4 内核中的 EXT4 加密功能。推荐的解决方案是使用基于 4.4 或更高版本的内核。EXT4 加密还反向移植到了 Android 公共代码库内的 3.10 内核以及受支持的 Nexus 内核。
对于希望将该功能引入到其设备内核的设备制造商来说,AOSP 内核/公共 Git 代码库中的 android-3.10.y 分支可作为一个很好的着手点。不过,务必要在最新的稳定版 Linux 内核(目前是 linux-4.6)中应用 EXT4 和 JBD2 项目提供的最新补丁程序。Nexus 设备内核已经包含其中很多补丁程序。
设备 | 内核 |
---|---|
Android Common | kernel/common android-3.10.y (Git) |
Nexus 5X (bullhead) | kernel/msm android-msm-bullhead-3.10-n-preview-2 (Git) |
Nexus 6P (angler) | kernel/msm android-msm-angler-3.10-n-preview-2 (Git) |
请注意,以上每个内核都使用了到 3.10 的反向移植。Linux 3.18 中的 EXT4 和 JBD2 驱动程序已移植到基于 3.10 的现有内核中。由于内核各个部分之间存在依赖关系,因此这种反向移植会导致系统停止支持 Nexus 设备不使用的一些功能。其中包括:
- EXT3 驱动程序,不过 EXT4 仍可以装载并使用 EXT3 文件系统
- 全局文件系统 (GFS) 支持
- EXT4 中的 ACL 支持
除了对 EXT4 加密提供功能支持外,设备制造商还可以考虑实现加密加速功能,以便加快文件级加密的速度并改善用户体验。
启用文件级加密
通过将不带参数的 fileencryption
标记添加到 userdata
分区最后一列的 fstab
行中,可以启用 FBE。要查看示例,请访问 https://android.googlesource.com/device/lge/bullhead/+/nougat-release/fstab_fbe.bullhead
测试设备上的 FBE 实现情况时,可以指定以下标记:forcefdeorfbe="<path/to/metadata/partition>"
此标记会将设备设为使用 FDE,但允许针对开发者转换为 FBE。默认情况下,此标记的行为类似于 forceencrypt
,会使设备进入 FDE 模式。不过,它将提供一个调试选项,以便在开发者预览中允许将设备切换到 FBE 模式。另外,还可以使用以下命令在 fastboot 中启用 FBE:
$ fastboot --wipe-and-use-fbe
此标记仅用于开发目的,可提供一个在实际 FBE 设备发布之前演示 FBE 功能的平台。此标记在将来可能会被弃用。
与 Keymaster 集成
vold
负责处理密钥生成和内核密钥环管理工作。AOSP 的 FBE 实现要求设备支持 1.0 或更高版本的 Keymaster HAL。更低版本的 Keymaster HAL 不受支持。
首次启动时,在启动过程的早期阶段会生成并安装用户 0 的密钥。到 init
的 on-post-fs
阶段完成时,Keymaster 必须已做好处理请求的准备。在 Nexus 设备上,这是通过设置一个脚本块处理的:
- 确保 Keymaster 在
/data
装载之前启动 - 指定文件加密算法套件:AOSP 的文件级加密实现会用到采用 XTS 模式的 AES-256 算法
注意:所有加密都基于采用 XTS 模式的 AES-256 算法。XTS 的定义方式决定了它需要两个 256 位密钥;因此实际上 CE 密钥和 DE 密钥都是 512 位密钥。
加密政策
EXT4 加密在目录级应用加密政策。首次创建设备的 userdata
分区时,会由 init
脚本应用基本结构和政策。这些脚本将触发创建首位用户(用户 0)的 CE 密钥和 DE 密钥,并定义要使用这些密钥加密哪些目录。创建其他用户和资料时,会生成必要的其他密钥并将其存储在密钥代码库中;接下来会创建它们的凭据和设备存储位置,并且加密政策会将这些密钥关联到相应目录。
在 AOSP 当前提供的文件级加密实现中,加密政策被硬编码到了以下位置:
/system/extras/ext4_utils/ext4_crypt_init_extensions.cpp
可以在该文件中添加例外情况,以彻底防止特定目录被加密,具体方法是将相应目录添加到 directories_to_exclude
列表中。如果进行了此类修改,设备制造商应添加 SELinux 政策,以便仅向需要使用未加密目录的应用授予访问权限(应排除所有不可信的应用)。
目前唯一可接受的使用这种方法的情况是在支持旧版 OTA 功能方面。
在系统应用中支持直接启动
将应用设为直接启动感知型应用
为了实现系统应用的快速迁移,新增了两个可在应用级别设置的属性。defaultToDeviceProtectedStorage
属性仅适用于系统应用,directBootAware
属性则适用于所有应用。
<application android:directBootAware="true" android:defaultToDeviceProtectedStorage="true">
应用级别的 directBootAware
属性的含义是将相应应用中的所有组件均标记为加密感知型组件。
defaultToDeviceProtectedStorage
属性用于将默认的应用存储位置重定向到 DE 存储空间(而非 CE 存储空间)。使用此标记的系统应用必须要仔细审核存储在默认位置的所有数据,并将敏感数据的路径更改为使用 CE 存储空间。使用此选项的设备制造商应仔细检查要存储的数据,以确保其中不含任何个人信息。
在这种模式下运行时,以下系统 API 可在需要时用于明确管理由 CE 存储空间支持的 Context(这些 API 与设备保护存储空间适用的同类 API 相对应)。
Context.createCredentialProtectedStorageContext()
Context.isCredentialProtectedStorage()
支持多位用户
多用户环境中的每位用户均会获得一个单独的加密密钥。每位用户均会获得两个密钥:一个 DE 密钥和一个 CE 密钥。用户 0 由于是特殊用户,因此必须要先登录设备。这部分适用于使用设备管理功能的情况。
加密感知型应用按照以下方式与各用户进行互动:INTERACT_ACROSS_USERS
和 INTERACT_ACROSS_USERS_FULL
允许应用与设备上的所有用户互动。不过,这些应用只能访问已解锁用户的 CE 加密目录。
应用或许能够与各个 DE 区域自由互动,但一位用户已解锁并不意味着设备上的所有用户均已解锁。应用在尝试访问这些区域之前,应先检查该状态。
每个工作资料用户 ID 也会获得两个密钥:一个 DE 密钥和一个 CE 密钥。当满足工作挑战时,资料用户会被解锁,并且 Keymaster(在 TEE 中)可以提供资料的 TEE 密钥。
处理更新
恢复分区无法访问用户数据分区中采用 DE 保护的存储空间。强烈建议实现 FBE 的设备支持新版 OTA 机制(采用即将推出的 A/B 系统更新方式)。由于可以在正常操作期间应用 OTA 更新,因此恢复分区无需访问已加密存储卷中的数据。
如果使用旧版 OTA 解决方案(该解决方案要求恢复分区访问用户数据分区中的 OTA 文件),则需要执行以下操作:
- 在用户数据分区中创建一个顶级目录(例如“misc_ne”)。
- 将该顶级目录添加到加密政策例外情况中(请参阅上文中的加密政策)。
- 在该目录中创建一个用于存放 OTA 更新包的目录。
- 添加 SELinux 规则和文件环境,以便控制对该文件夹及其内容的访问。应当只有接收 OTA 更新的进程或应用能够对该文件夹进行读取和写入操作。
- 任何其他应用或进程都不应具有访问该文件夹的权限。
在该文件夹中创建一个目录,以便存放 OTA 更新包。
验证
为了确保实现的 FBE 功能版本能够按预期工作,需要部署多种 CTS 加密测试。
可以顺利为您的主板编译内核后,请另行为 x86 编译内核并在 QEMU 下运行该内核,然后您就可以使用以下命令通过 xfstest 进行测试了:
$ kvm-xfstests -c encrypt -g auto
此外,设备制造商可以在启用了 FBE 的设备上进行以下手动测试:
- 检查
ro.crypto.state
是否存在- 确认
ro.crypto.state
是否已加密
- 确认
- 检查
ro.crypto.type
是否存在- 确认
ro.crypto.type
是否已设为file
- 确认
此外,测试人员可以在主用户已设置锁定屏幕的情况下启动一个 userdebug
实例。然后通过 adb
shell 命令进入设备,并使用 su
获得 root 权限。确认 /data/data
中是否包含加密的文件名;如果没有,则表示存在问题。
AOSP 实现详情
本部分详细介绍了 AOSP 的文件级加密实现,并讲解了文件级加密的运作方式。设备制造商应该无需执行任何更改,即可在其设备上使用 FBE 和“直接启动”功能。
EXT4 加密
AOSP 的文件级加密实现会用到内核中的 EXT4 加密功能,并配置为:
- 借助采用 XTS 模式的 AES-256 算法加密文件内容
- 借助采用 CBC-CTS 模式的 AES-256 算法加密文件名
密钥派生
硬盘加密密钥(512 位 AES-XTS 密钥)以加密形式存储:通过另一个存放在 TEE 中的密钥(256 位 AES-GCM 密钥)进行加密。要使用该 TEE 密钥,需要具备以下三项:
- 身份验证令牌
- 扩展凭据
- secdiscardable hash
身份验证令牌是一个经过加密和身份验证的令牌,由 Gatekeeper 在用户成功登录时生成。除非用户提供的身份验证令牌正确无误,否则 TEE 将拒绝用户使用该密钥。如果用户没有任何凭据,则不使用也不需要使用身份验证令牌。
扩展凭据是使用 scrypt
算法进行加盐和扩展处理的用户凭据。实际上,凭据在被传递到 vold
(以便传递到 scrypt
)之前,会在锁定设置服务中接受一次哈希处理。扩展凭据会以加密形式绑定到 TEE 中的相应密钥,并享有适用于 KM_TAG_APPLICATION_ID
的所有保证。如果用户没有凭据,则不使用也不需要使用扩展凭据。
secdiscardable hash
是 16 KB 随机文件的 512 位哈希,和用于重建相应密钥的其他信息(例如种子)存储在一起。在相应密钥被删除时,该文件会一并被安全地删除,或以新的方式被加密;采用这种附加的保护措施后,攻击者要恢复相应密钥,必须要先恢复这个被安全删除的文件中的每一个位。secdiscardable hash 同样会以加密形式绑定到 TEE 中的相应密钥,并享有适用于 KM_TAG_APPLICATION_ID
的所有保证。请参阅面向 Keystore 实现人员的参考资料。
---------------------------------------------------------------------------------------------------------
这边通过一个示例来验证上面的相关操作,创建接收 android.intent.action.LOCKED_BOOT_COMPLETED和android.intent.action.BOOT_COMPLETED的receiver,并在android.intent.action.LOCKED_BOOT_COMPLETED的receiver中创建相关文件。比如在确认机器 在:
m get-fbe-mode 为emulated
的机器上,机器设置PIN加密,然后重启,可以在/data/user_de/0/com.android.cts.splitapp/files/ 目录中确认有相关文件创建。
AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.callen">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".LockedBootReceiver" android:exported="true" android:directBootAware="true">
<intent-filter>
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name=".BootReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>
LockedBootReceiver.java:
package com.example.callen;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class LockedBootReceiver extends BaseBootReceiver {
private static final String TAG = "LockedBootReceiver";
}
BaseBootReceiver.java:
package com.example.callen;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.provider.Settings;
import android.util.Log;
import java.io.File;
public class BaseBootReceiver extends BroadcastReceiver {
private static final String TAG = "BaseBootReceiver";
@Override
public void onReceive(Context context, Intent intent) {
try {
context = context.createDeviceProtectedStorageContext();
final File probe = new File(context.getFilesDir(),
getBootCount(context) + "." + intent.getAction());
Log.d(TAG, "=================shine Touching probe " + probe);
probe.createNewFile();
exposeFile(probe);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static int getBootCount(Context context) throws Exception {
return Settings.Global.getInt(context.getContentResolver(), Settings.Global.BOOT_COUNT);
}
private static File exposeFile(File file) throws Exception {
Log.d(TAG, "exposeFile rockchip!");
file.setReadable(true, false);
file.setReadable(true, true);
File dir = file.getParentFile();
do {
dir.setExecutable(true, false);
dir.setExecutable(true, true);
dir = dir.getParentFile();
} while (dir != null);
return file;
}
}
BootReceiver.java
package com.example.callen;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class BootReceiver extends BaseBootReceiver {
private static final String TAG = "BootReceiver";
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "=================================BootReceiver: " + intent.getAction());
}
}
~
代码下载地址:
http://download.csdn.net/download/cigogo/10180511