Android 文件级加密

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


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值