android9.0上,开启了默认加密后,手机在开机的过程中,会发现开机动画在播放一会儿,突然黑屏或闪屏一下,然后重新播放,看起来像是手机ap端突然重启了一样的。针对这个问题,仔细分析后发现,这个不是手机ap端重启了,而是加密导致的framework重启。具体原因如下:
加密又分全盘加密(Android 4.4 引入)和文件级加密(Android 7.0 引入),本文将论述加密中的全盘加密的基本知识。全盘加密是使用已加密的密钥对 Android 设备上的所有用户数据进行编码的过程。设备经过加密后,所有由用户创建的数据在写入磁盘之前都会自动加密,并且所有读取操作都会在将数据返回给调用进程之前自动解密数据。
Android 全盘加密基于在块设备层运行的内核功能 dm-crypt。因此,这种加密方式适用于以块设备的形式呈现给内核的嵌入式多媒体卡 (eMMC) 和类似闪存设备。YAFFS 会直接与原始 NAND 闪存芯片交互,无法进行全盘加密。
全盘加密采用的是 128 位高级加密标准 (AES) 算法(搭配密码块链接 (CBC) 和 ESSIV:SHA256)。对主密钥进行加密时使用的是 128 位 AES 算法,并会调用 OpenSSL 库。对于该密钥,您必须使用 128 位或更多位(可以选择 256 位)。
加密操作由 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 未解密并装载之前,一直不会启动。
加密流程和启动流程
使用 forceencrypt 加密新设备
这是 Android 5.0 以后设备首次启动时的常规流程。
检测带有 forceencrypt 标记的未加密文件系统,这个标记一般在fstab文件里设置,以高通的为例,放device/qcom/项目名/fstabs-4.9/fstab_non_AB_variant.qti文件里,比如userdata分区要加密,则用forceencrypt如下配置:
/dev/block/bootdevice/by-name/userdata /data ext4 noatime,nosuid,nodev,barrier=1,noauto_da_alloc,discard wait,forceencrypt=footer,quota,reservedsize=128M
/data 未加密,但需要加密,因为 forceencrypt 强制要求进行此项加密。卸载 /data。
开始加密 /data
vold.decrypt = “trigger_encryption” 会触发 init.rc,从而使 vold 对 /data 进行无密码加密。(因为这应该是新设备,还没有设置密码。)
装载 tmpfs
vold 会装载一个 tmpfs /data(使用 ro.crypto.tmpfs_options 中的 tmpfs 选项),并会将 vold.encrypt_progress 属性设为 0。 vold 会准备 tmpfs /data 以便启动已加密的系统,并会将 vold.decrypt 属性设为 trigger_restart_min_framework
启动框架以显示进度,这里会开启第一次动画。
由于设备上几乎没有要加密的数据,加密过程很快就会完成,因此实际上通常并不会显示进度条。如需关于进度界面的更多详细信息,请参阅加密现有设备。
/data 加密后,关闭框架
vold 会将 vold.decrypt 设为 trigger_default_encryption,这会启动 defaultcrypto 服务。(这会启动以下流程来装载默认的已加密用户数据。)trigger_default_encryption 会检查加密类型,以了解 /data 加密是否使用了密码。由于 Android 5.0 设备是在首次启动时加密,应该没有设置任何密码,因此我们要解密并装载 /data。
装载 /data
接下来,init 会使用从 ro.crypto.tmpfs_options(在 init.rc 中设置)中选取的参数在 tmpfs RAMDisk 中装载 /data。
启动框架
将 vold 设为 trigger_restart_framework,这会继续常规启动过程,重新启动framework,开机动画也会重新再调一次。
这可以从system/core/rootdir/init.rc这个里面看到:
on property:vold.decrypt=trigger_restart_framework
stop surfaceflinger
start surfaceflinger
# A/B update verifier that marks a successful boot.
exec_start update_verifier
class_start main
class_start late_start
这里显示,当vold.decrypt的值为trigger_restart_framework时,会先stop surfaceflinger,然后start surfaceflinger
现在的情形就是这样,如果要开启手机加密,则开机必定会跑两次framework,一次是在装载tmpfs/data时跑,另一次是在加密完成后,重新装载data后跑,这个流程是不可能被更改的。那么为了避免跑两次开机动画,就必须另想办法了。
我们可以在frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp这个文件里做如下修改:
char voldDecryptBuf[PROPERTY_VALUE_MAX];
property_get("vold.decrypt", voldDecryptBuf, "");
ALOGI("xuhui bootanim cryptState is: %s ", voldDecryptBuf);
if(strcmp(voldDecryptBuf, "trigger_restart_framework") == 0)
{
if (mStartPropertySetThread->Start() != NO_ERROR) {
ALOGI("Run StartPropertySetThread failed!");
}
}
因为在开机加密完成后,vold一定会将"vold.decrypt"它的值设置为trigger_restart_framework,以便重新启动framework。这时会停止surfaceflinger进程,然衙再重启surfaceflinger进程。而我们的开机动画,是在这个进程里来启动的。我们可以从\frameworks\native\services\surfaceflinger\StartPropertySetThread.cpp这个文件里看到:
bool StartPropertySetThread::threadLoop() {
// Set property service.sf.present_timestamp, consumer need check its readiness
property_set(kTimestampProperty, mTimestampPropertyValue ? "1" : "0");
// Clear BootAnimation exit flag
property_set("service.bootanim.exit", "0");
// Start BootAnimation if not started
property_set("ctl.start", "bootanim");
// Exit immediately
return false;
}
这里表明, mStartPropertySetThread->Start() 的时候,会property_set("ctl.start", "bootanim"); 开启动画。 所以,我们可以通过这个值来判断,只有当"vold.decrypt"的值为trigger_restart_framework的时候,才播放开机动画,否则不播放。那么这样的效果就是,在没有启动开机动画的时候,手机界面会一直停留在开机的第一张logo那里。