说明

参考英文文章: http://source.android.com/tech/encryption/android_crypto_implementation.html

参考翻译:http://blog.sina.com.cn/s/blog_70753a4801012i4b.html

参考书籍《Android安全机制解析与应用实践》第7章 Android加密文件系统

分析的源代码使用 Android4.4.3

个人对加密的算法没有什么研究,本文只分析Kitkat的磁盘加密功能的流程分析。


Kitkat上的磁盘加密

如果你想在Kitkat设备上打开加密功能,那么需要:

/data文件系统必须基于块设备,eMMC是首选。这是因为磁盘加密功能使用kernel的dm-crypt模块,工作在块设备层。

system/vold/cryptfs.c中的 get_fs_size()函数会假定/data的文件系统是ext4,异常检查确定文件系统没有使用分区上最后16Kbytes,因为 cryptofooter将会保持到该存储空间。对开发人员而言是有用的,因为它的大小会变化,但是无法释放它。如果你使用的不是ext4文件系统,要么删除这个函数及其调用,要么改造它以适用你的文件系统。

大部分控制临时framework装载和卸载的代码都是在通常与设备无关的代码文件中。但是init.<device>.rc文件需要一些修改。所有的services必须属于三个类中的一个:core,main或late_start。Core类中的所有Services在临时 framework 获得磁盘密码时不会关闭或重启。main类中的Services会在真正的framwork重启时重启,late_start中的Services直到临时 framework被重启后才会被启动,放在late_start中的Services在临时framework获得磁盘密码的过程中不会运行。

需要在/data目录下创建的所有目录都是需要写在post-fs-data的Action中,并且在该Action中必须用命令"setpropvold.post_fs_data_done1"来结束。如果你的init.<device>.rc文件没有post-fs-data Action,那么主init.rc的post-fs-data Action结束时必须执行"setpropvold.post_fs_data_done1",三星的manta设备的init.manta.rc文件中的代码如下所示。

/KitKat/device/samsung/manta/init.manta.rc

on post-fs-data    mkdir /data/media 0770 media_rw media_rw    setprop vold.post_fs_data_done 1

备注:

tmpfs是一种虚拟内存的文件系统,典型的tmpfs文件系统完全驻留在RAM中,读写速度远快于内存或硬盘文件系统。

vold会在停止main class服务后将/data 挂载为tmpfs,然后触发post-fs-data action的操作并等待"setprop vold.post_fs_data_done 1",然后继续启动临时framework并开始加密过程。如果 init.rc中不执行"setprop vold.post_fs_data_done 1",那么vold会认为异常从而重启整个系统。

Android加密如何工作

Android的磁盘加密基于linux kernel的dm-crypt模块,它是一个linux kernel的一个工作于块设备上的功能。因为YAFFS它会直接访问原始NAND Flash芯片,所以在基于YAFFS文件系统不起作用,但是可以工作在被linux kernel识别为块设备的eMMC及其相似的 Flash设备上。尽管ext4文件系统与设备加密与否无关,但对于加密设备来说推荐使用的ext4文件系统。

实际上加密功能是linux kernel的标准功能,在Android设备上开启加密功能,会有些小问题。Android系统试图尽量避免包含GPL成分,所以不能使用cryptsetup命令并且libdevmapper不是可选功能。所以使用 ioctl(2)通知内核是最好的选择。Android的Vold已经支持了移动应用到SD卡的功能,所以利用它进行整个磁盘的加密。实际用于文件系统加密的首先是128AES 算法,CBC模式和ESSIV:SHA256算法。主键通过调用openssl库使用128bit AES加密。

一旦决定将这个加密功能添加到vold模块中,我们要做的事情就十分明显了:

将一个名字为 cryptfs的新模块添加到Vold中. 并仿照Vold的命令方式,添加相应的加密相关命令,并使用相同的调用方式。在Kitkat中加密相关的命令有 checkpw , restart, enablecrypto, changepw 和cryptocomplete, verifypw, getfield ,setfield . 下面会详细说明。


另外的一个重要问题是如何在boot阶段得到密码. 最初设计在ramdisk实现一个能够被init调用的轻量级UI,并且初始化解密功能和mount/data。然而,UI工程师认为这种做法,工作量太大,并建议在init初始化启动时,通知framework来弹出 password输入对话框,获得密码,然后关闭framework并重新启动真正的framework。这个方案的确定引出下面的设计。详细点说,init通过设置property来告诉framework进入密码输入模式,同时使用properties 在vold,init和framework之间作为平台进行更多通信。详细描述如下所示:

最后,涉及的问题围绕在杀掉服务和重启哪些服务,在进行这些操作过程中,可能会导致/data分区的卸载和重新挂载。启动一个临时framework来获得密码,需要/data挂载tmpfs临时文件系统,否则framework无法启动。但是当卸载/data临时文件系统tmpfs,挂载真正的/data加密文件系统时,所有在/data临时文件系统tmpfs上打开文件的进程会被kill掉,并在真正/data文件系统上重新启动。

这个魔术需要所有属于core, main, late_start三组中的一组Services来完成。 Core Services一旦启动,就不会被关闭,main Services会先关闭,并在输入完磁盘密码后重新启动,late_start Services直到/data挂载并解密后才会启动。

这个魔术去触发Actioin是通过设置vold.decrypt属性的各种字符串来实现,这些字符串在接下来的内容中会说明。

同时,一个新的init命令"class_reset"被发明,作用是停止一个服务,但允许它调用"class_start"命令重启服务。如果用"class_stop"替代"class_reset"命令,则会将SVC_DISABLED标志添加到该已经停止服务的状态中,无论这个服务时什么类型的,这意味着当服务所属class使用class_start时,它也不会被启动。

引导加密系统

当init 挂载/data失败时,它假定文件系统已经加密,并设置几个properties:

ro.crypto.state = "encrypted"vold.decrypt = 1


这意味着/data被挂载为tmpfs ramdisk.

如果init能够正常挂载/data,会设置.

ro.crypto.state ="unencrypted"

framework启动时会检查 vold.decrypt的值,如果值成"1",说明此时 /data 被挂载为tmpfs,必须获得用户密码。 首先,需要先要确定disk是否已经加密。该操作是通过给vold 发送命令 "cryptfscryptocomplete",vold返回0 表示成功加密完毕,返回 -1 表示内部错误,-2 表示加密没操作有完成。Vold 通过crypto footer的CRYPTO_ENCRYPTION_IN_PROGRESS标志来确定“cryptfs cryptocomplete”的返回值。如果它被设置了,那么表示加密过程被中断了,同时存储设备上没有有用的数据。当vold返回一个错误时,UI界面应该弹出提示消息框,通知用户需要重启并将设备进行恢复出厂设置,用户只能通过点击提示框中的按钮来被强制进行此操作。

假设"cryptfs cryptocomplete"命令返回成功,那么framework应该弹出UI询问用户磁盘密码,这个UI会发送命令"cryptfs checkpw" 给vold,如果密码正确(密码正确与否是由是否可以成功挂载已解密磁盘到临时路径,然后再卸载它决定的), vold保存解密块设备的名字到ro.crypto.fs_crypto_blkdev,然后返回状态0 给 UI,如果密码错误,则返回 -1 给UI。

UI显示一个加密启动画面,然后调用"cryptfs restart",vold设置property

调用cryptfs.c的cryptfs_restart(void)函数设置的该值。


这个操作会导致init.rc执行"class_reset main"操作。

system/core/rootdir/init.rc

    /* The init files are setup to stop the class main when vold.decrypt is     * set to trigger_reset_main.     */    property_set("vold.decrypt", "trigger_reset_main");    class_reset main

该操作会导致停止所有属于main class的Services,

接着执行cryptfs_restart()函数,开始调用wait_and_unmount()方法卸载/data挂载的文件系统:

<pre name="code" class="cpp">#define DATA_MNT_POINT "/data"if (! (rc = wait_and_unmount(DATA_MNT_POINT)) ) {   /* If that succeeded, then mount the decrypted filesystem */   fs_mgr_do_mount(fstab, DATA_MNT_POINT, crypto_blkdev, 0)


  

卸载tmpfs的/data文件系统,如果卸载成功,则调用fs_mgr_do_mount()方法,vold会挂载解密的真实/data分区,然后准备一个新的分区(如果通过wipe选项加密,可能永远不会准备这个新的分区。首次发布的加密功能的版本中时不包含wipe功能的)。

接着调用如下方法,来加载系统的properties,这里不赘述:

property_set("vold.decrypt", "trigger_load_persist_props");


从下面代码的注释可以看出,是在/data分区中创建一些必须的目录:

     /* Create necessary paths on /data */        if (prep_data_fs()) {            return -1;        }


调用的prep_data_fs()函数的代码如下:

static int prep_data_fs(void){……    property_set("vold.post_fs_data_done", "0");    property_set("vold.decrypt", "trigger_post_fs_data");    SLOGD("Just triggered post_fs_data\n");    …….}


首先设置vold.post_fs_data_done为字符串“0”,同时设置vold.decrypt为"trigger_post_fs_data",

on property:vold.decrypt=trigger_post_fs_data    trigger post-fs-data


它导致init.rc和init.<device>.rc执行post-fs-data命令,创建必要的目录、连接及设置vold.post_fs_data_done为"1"。

在prep_data_fs()函数中使用了一个for循环来等待vold.post_fs_data_done被设置为"1",代码如下:

static int prep_data_fs(void){……     /* Wait a max of 50 seconds, hopefully it takes much less */    for (i=0; i<DATA_PREP_TIMEOUT; i++) {        char p[PROPERTY_VALUE_MAX];         property_get("vold.post_fs_data_done", p, "0");        if (*p == '1') {            break;        } else {            usleep(250000);        }    }    …….}


当终于将vold.post_fs_data_done被设置为"1",则prep_data_fs()执行完毕,返回,接着执行cryptfs_restart()函数:

    /* startup service classes main and late_start */        property_set("vold.decrypt", "trigger_restart_framework");


最后 vold设置vold.decrypt为"trigger_restart_framework",

on property:vold.decrypt=trigger_restart_framework    class_start main    class_start late_start


通知 init.rc启动main class和从系统启动后首次启动late_startclass所属services。

现在,framework已经使用解密的/data文件系统启动完成,并准备好可以使用了。

在设备上启用加密

首次发布时,我们只支持inplace加密,要求framework关闭,/data 卸载,然后加密每个区块,以上操作需要reboot设备之后开始执行。详细如下:

用户从UI选择加密设备,UI确认电池电量充足并且插入电源,以确保有足够电量完成加密过程,因为如果设备在加密过程完成前因为没电而关机,数据会停留在一个不完整的加密状态,设备必须进行恢复出厂设置(所有数据将丢失)。

一旦用户按下按钮开始加密设备,UI将通过给vold发送命令 "cryptfs enable cryptoinplace",密码使用用户锁屏密码。

Vold接到命令,经过处理会调用cryptfs_enable()函数来进行加密的处理操作。

vold会进行一些错误检查,返回 -1代表不能加密并输出包含原因到log中,如果确定可以加密,执行如下语句:

  /* The init files are setup to stop the class main and late start when     * vold sets trigger_shutdown_framework.     */    property_set("vold.decrypt", "trigger_shutdown_framework");


它会设置vold.decrypt为"trigger_shutdown_framework"字符串,

on property:vold.decrypt=trigger_shutdown_framework    class_reset late_start    class_reset main


此操作会触发init.rc关闭late_start和main的所属Services。

if (vold_unmountAllAsecs()) {        /* Just report the error.  If any are left mounted,         * umounting /data below will fail and handle the error.         */        SLOGE("Error unmounting internal asecs");    }


调用vold_unmountAllAsecs()函数,卸载/mnt/secure/asec和/data/app-asec目录下的文件系统。system/vold/VolumeManager.cpp

extern "C" int vold_unmountAllAsecs(void) {    int rc;     VolumeManager *vm = VolumeManager::Instance();    rc = vm->unmountAllAsecsInDir(Volume::SEC_ASECDIR_EXT);    if (vm->unmountAllAsecsInDir(Volume::SEC_ASECDIR_INT)) {        rc = -1;    }    return rc;}


获取ro.crypto.fuse_sdcard的值,看系统是否使用fuse模式的存储管理模式,代码如下:

    property_get("ro.crypto.fuse_sdcard", fuse_sdcard, "");    if (!strcmp(fuse_sdcard, "true")) {        /* This is a device using the fuse layer to emulate the sdcard semantics         * on top of the userdata partition.  vold does not manage it, it is managed         * by the sdcard service.  The sdcard service was killed by the property trigger         * above, so just unmount it now.  We must do this _AFTER_ killing the framework,         * unlike the case for vold managed devices above.         */        if (wait_and_unmount(sd_mnt_point)) {            goto error_shutting_down;        }    }


如果不是fuse模式,则调用wait_and_unmount()方法卸载SD卡。

接着调统一个方法卸载/data文件系统,源码如下所示:

   /* Now unmount the /data partition. */    if (wait_and_unmount(DATA_MNT_POINT)) {        goto error_shutting_down;    }


至此,已经卸载/mnt/sdcard 和 /data文件系统。

   /* Now unmount the /data partition. */    if (wait_and_unmount(DATA_MNT_POINT)) {        goto error_shutting_down;    }

如果进行一个inplace类型的加密,即how 的值为CRYPTO_ENABLE_INPLACE,此时则会调用fs_mgr_do_tmpfs_mount()方法,让 vold将tmpfs挂载/data目录下,并且设置vold.encrypt_progress="0".

已经卸载完所有的存储设备,并将tmpfs挂载到/data目录下了,接下来的就应该重启framework了,进行真正的加密操作了。

代码如下:


   /* restart the framework. */        /* Create necessary paths on /data */        if (prep_data_fs()) {            goto error_shutting_down;        }


调用prep_data_fs()函数,来进行framework重启的一些操作,上文已经分析过该方法,这里就不再分析了,由于tmpfs文件系统被挂载后,/data目录下什么内容也没有,所以需要在/data目录下创建必要的一些目录。

睡眠了2S后,接着执行如下代码:

    /* startup service classes main and late_start */        property_set("vold.decrypt", "trigger_restart_min_framework");

创建好目录,就可以重启framework了,这里设置vold.decrypt= "trigger_restart_min_framework",

    /* startup service classes main and late_start */        property_set("vold.decrypt", "trigger_restart_min_framework");


这将触发init.rc启动main class服务进程,当framework看到vold.encrypt_progress设置为 "0",会启动显示进度的UI,每5s刷新一次进度。此时由于vold服务属于core,所以不会重启,之后vold设置加密映射,它创建了一个虚拟的加密块设备映射到实际的块设备,但加密是一块块写入,解密是一块块读取,vold会创建并写入crypto footer


crypto footer包含了加密类型的详细信息和用于系统解密的秘钥。主密钥通过读取 /dev/urandom创建的128bit随机值,对用户密码进行哈希加密,加密算法使用SSL库的PBKDF2函数。footer也同样包含随机数(也从/dev/urandom读取),为了增加熵而使用PBKDF2的hash算法得出,防止彩虹表破解密码。同时,CRYPT_ENCRYPTION_IN_PROGRESS标志设置到crypto footer中防止加密失败,cryptfs.h中有详细的crypto footer布局,crypto footer保存在分区的最后16Kbytes上,且/data文件系统不能使用到这部分。

如果是wipe模式的加密,vold会通过cryptfs_enable_wipe()方法调用命令"make_ext4fs"为加密块设备创建ext4格式的文件系统,注意分区上不会包含那最后的16Kbytes。

如果是inplace模式,vold调用cryptfs_enable_inplace()函数,开始循环读取每块数据然后写入加密块设备,这在30Gbyte分区的MotorolaXoom上要花费约1小时。这取决于硬件。加密进度每增加1%会更新vold.encrypt_progress。UI会以5s间隔检查加密进度的变化。

if (! rc) {        /* Success */         /* Clear the encryption in progres flag in the footer */        crypt_ftr.flags &= ~CRYPT_ENCRYPTION_IN_PROGRESS;        put_crypt_ftr_and_key(&crypt_ftr);         sleep(2); /* Give the UI a chance to show 100% progress */        cryptfs_reboot(0);    } else {


当各种加密全部成功时,vold会清除 footer上的ENCRYPTION_IN_PROGRESS标志,然后调用cryptfs_reboot(0)函数重启系统,

该函数的代码如下:

static void cryptfs_reboot(int recovery){    if (recovery) {        property_set(ANDROID_RB_PROPERTY, "reboot,recovery");    } else {        property_set(ANDROID_RB_PROPERTY, "reboot");    }    sleep(20);     /* Shouldn't get here, reboot should happen before sleep times out */    return;}ANDROID_R


B_PROPERTY的值如一下代码所示:

system/core/include/cutils/android_reboot.h

#define ANDROID_RB_PROPERTY "sys.powerctl"


可以看出只是根据传入参数recovery的值,对sys.powerctl进行设置,这里传入的是0,则进入if的false分支,写入”reboot”字符串。

system/core/rootdir/init.rc

on property:sys.powerctl=*    powerctl ${sys.powerctl}


触发init.rc的命令,执行powerctl reboot命令。

接着就执行到builtins.c的do_powerctl()函数,解析命令,然后调用system/core/libcutils/android_reboot.c的android_reboot()函数,通过系统调用,重启系统。

我们来看一下如果重启失败的处理:

  /* hrm, the encrypt step claims success, but the reboot failed.     * This should not happen.     * Set the property and return.  Hope the framework can deal with it.     */    property_set("vold.encrypt_progress", "error_reboot_failed");    release_wake_lock(lockid);    return rc;


如果因为某些原因reboot失败,vold会设置vold.encrypt_progress="error_reboot_failed",UI告知用户强行reboot,这不是预期会发生的情况。

接下来,我们来看一下机密过程失败的处理流程:

error_unencrypted:    free(vol_list);    property_set("vold.encrypt_progress", "error_not_encrypted");    if (lockid[0]) {        release_wake_lock(lockid);    }    return -1;


如果vold 在加密过程中遇到错误,没有数据遭到破坏并且framework在运行,vold会设置vold.encrypt_progress ="error_not_encrypted",UI提示用户reboot并告知加密没有进行。如果错误发生在framework停止之后,但是在UI进度条运行之前,vold会reboot系统,如果reboot失败,会设置vold.encrypt_progress="error_shutting_down" 并返回 -1,但不会有谁捕捉这个错误,这不是预期发生的。

} else {        char value[PROPERTY_VALUE_MAX];        property_get("ro.vold.wipe_on_crypt_fail", value, "0");        if (!strcmp(value, "1")) {            /* wipe data if encryption failed */            SLOGE("encryption failed - rebooting into recovery to wipe data\n");            mkdir("/cache/recovery", 0700);            int fd = open("/cache/recovery/command", O_RDWR|O_CREAT|O_TRUNC, 0600);            if (fd >= 0) {                write(fd, "--wipe_data", strlen("--wipe_data") + 1);                close(fd);            } else {                SLOGE("could not open /cache/recovery/command\n");            }            cryptfs_reboot(1);        } else {            /* set property to trigger dialog */            property_set("vold.encrypt_progress", "error_partially_encrypted");            release_wake_lock(lockid);        }        return -1;    }



如果vold在加密过程中遇到错误,设置vold.encrypt_progress= “ error_partially_encrypted ”,并返回-1 。然后,UI显示消息说明加密失败,并为用户提供恢复出厂设置的按钮。

改变密码

改变磁盘加密的密码时,UI发送命令"cryptfs changepw" 给vold,vold用新的密码重新加密生成主密钥。

更多相关文章

  1. Android设备如何保证数据同步写入磁盘
  2. Android设备adb授权的原理
  3. Android 编程下判断当前设备是手机还是平板
  4. android关闭或开启移动网络数据(关闭后,设备不可以上网,但可以打电
  5. Android——4.2.2 文件系统文件夹分析
  6. Android设备开发中的一些解决办法
  7. android驱动学习-led次设备号(2)
  8. android 命令(adb shell)进入指定模拟器或设备

随机推荐

  1. 在Android中查看和管理sqlite数据库
  2. 笔记77-listview属性介绍
  3. android批量插入数据效率对比
  4. Android(安卓)AIDL 实现两个APP之间的跨
  5. Android(安卓)登录界面调用输入法时让界
  6. Android系统开发小问题-启动过程中android
  7. Android实现底部弹出按钮菜单升级版
  8. Iphone,Imac,Win7,Android的日历同步
  9. Windows环境下 android-ndk-r10c 环境搭
  10. 转载:Android(安卓)service 实现过程