说明

参考英文文章: 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(安卓)C++层打印调用栈
  2. Android(安卓)Wifi获取组播
  3. Android(安卓)原生 MediaPlayer 和 MediaCodec 的区别和联系(二)
  4. Android(安卓)StageFrightMediaScanner源代码解析
  5. Android(安卓)Intent调用大全
  6. Android(安卓)之 四大组件、六大布局、五大存储
  7. Android(安卓)app中调用启动其他应用(系统应用和第三方应用)2016.1
  8. Android中文API(135) —— SQLiteCursorDriver
  9. android 完全退出程序

随机推荐

  1. golang中.a文件是什么
  2. golang不开发gui吗
  3. golang gmssl编译不过
  4. golang是什么语言
  5. golang map为啥不并发
  6. golang中sort包如何实现
  7. emacs支持golang吗
  8. golang用户登录怎么做
  9. dart和golang区别
  10. go语言中包导入的一些问题