Android(安卓)FDE 加密过程
16lz
2021-12-04
- Android Full Disk Encryption
- Settings中加密入口
- 调用关系
- 加密实现
- 参考
- Vold properties
- init properties
- init actions
Android Full Disk Encryption
FDE是android设备全盘加密的简称;主要用于对Android设备userdata分区数据的加密,以实现数据保护的目的
- FDE是什么
- FDE是Full Disk Encrypt的缩写
- 保护/data 分区数据
- 参考google的官方介绍(需要访问google网络) encryption
- 一种保护user data的机制。例如:联系人,图片,视频等等
- 截止当前,Android支持加密userdata分区的数据以及不可移除sdcard的数据
- 不支持其他分区的加密
- FDE如何工作
- 基于android kernel的dm-crypt feature 实现
- 128 Advanced Encryption Standard(AES) with cipher-block chaining(CBD)and ESSIV:SHA256
- Android5.0之后,首次开机会加密
- 加密之后,将/data挂在到dm的虚拟节点上
- FDE是Full Disk Encrypt的缩写
- 首次启动
- fstab.qcom 标志
- encryptable manually encrypt
- foeceencrypted encrypt in fist boot
- 使用默认的密码获得master key
- 默认密码是”default_password”
- Keymaster(HW/SW) general key blob,存储在footer中
- default_password_salt(random)–>scrypt–>derived key
- Derived+keypair–>sign–>intermedia key(ikey)
- salt,keybob,derived key stored in footer
- set ikey to TZ
- Hardware Crypto
- TZ generates an encrypt key derived from new password,set to crypto enginee
- Software Crypto
- Call APU to set key with ikey(security_hw.c)
- fstab.qcom 标志
- 非首次开机
- 如果用户设置了pin/或者密码
- 弹出提示用于输入密码
- 使用默认的密码获得master key
- 从footer中读取salt,keyblob,derived key
- 从输入或者salt中获得derivedkey,并进行比较
- Derived+keypair–>sign–>intermedia key(ikey)
- salt,keybob,derived key stored in footer
- set ikey to TZ
- 升级相关
- 如果使用了软件加密,可以直接软件升级
- 如果使用了硬件加密,不可以直接软件升级
- 如果用户设置了pin/或者密码
- init.rc
- on property:vold.decrypt=trigger_default_encryption
- start defaultcrypto
- service defaultcrypto /system/bin/vdc –wait cryptfs mountdefaultencrypted
- disable
- oneshot
- #vold will set vold.decrypt to trigger_restart_framework(default encryption) or trigger_restart_min_framework(other encryption)
- on property:vold.decrypt=trigger_default_encryption
- FDE 启动总结
- init 进程读取fstab中,并依据fstab中的配置进行挂载
- 在挂载过程中发现该分区标记了encrypted,使用property触发一个属性
- vdc服务传递命令给cryptd socket
- 监听器接收到该socket,然后触发encrypt/decrypt 动作
- 重要的函数
- CryptCommandListener::CryptfsCmd::runCommand
- get_keymaster_hw_fde_password
- set_hw_device_encryption_key
- create_crypto_blk_dev
- update_hw_device_encryption_key
Settings中加密入口
//package/apps/SettingsCryptKeeper.javaCryptKeeperSettings.javaCryptKeeperConfirm.java
调试加密过程显示的UI
//正常显示进度条adb shell pm enable com.android.settings/.CryptKeeperadb shell am start -e "com.android.settings.CryptKeeper.DEBUG_FORCE_VIEW" "progress" -n com.android.settings/.CryptKeeper//提示输入密码adb shell am start -e "com.android.settings.CryptKeeper.DEBUG_FORCE_VIEW" "password" -n com.android.settings/.CryptKeeper//出现错误adb shell am start -e "com.android.settings.CryptKeeper.DEBUG_FORCE_VIEW" "error" -n com.android.settings/.CryptKeeper
调用关系
MountService 启动后,会创建两个Thread 一个用于处理同底层vold的交互的、一个用于处理加密 。启动NativeDaemonConnector Thread 之后,对socket进行监听
调用流程:
NativeDaemonConnector.run() public int encryptStorage(int type, String password) -->mCryptConnector.execute("cryptfs", "enablecrypto", "inplace", …); --> CryptCommandListener::CryptfsCmd::runCommand(SocketClient*, argc, **argv) -->cryptfs_enable_default(char *howarg, int allow_reboot) -->cryptfs_enable_internal(char*,int,char*,int)
Created with Raphaël 2.1.0 encryptStorage encryptStorage mCryptConnector.execute mCryptConnector.execute CryptCommandListener_CryptfsCmd_runCommand CryptCommandListener_CryptfsCmd_runCommand cryptfs cryptfs 加密 socket:CryptCommandListener::CryptfsCmd::runCommand 主要代码解释int CryptCommandListener::CryptfsCmd::runCommand(SocketClient *cli, int argc, char **argv) {} else if (!strcmp(argv[1], "cryptocomplete")) { int type = getType(argv[3]); if (type == -1) { } else if (type == CRYPT_TYPE_DEFAULT) { rc = cryptfs_enable_default(argv[2],/*allow_reboot*/false); } else { rc = cryptfs_enable(argv[2], type, argv[4], /*allow_reboot*/false); } } else if (!strcmp(argv[1], "changepw")) {}
//比较文件系统的和分区的大小int cryptfs_enable_internal(char *howarg, int crypt_type, char *passwd, int allow_reboot) /* Get the size of the real block device */ int fd = open(real_blkdev, O_RDONLY|O_CLOEXEC); get_blkdev_size(fd, &nr_sec); fs_size_sec = get_fs_size(real_blkdev); if (fs_size_sec > max_fs_size_sec) { SLOGE("Orig filesystem overlaps crypto footer region. Cannot encrypt in place.");
/*获得一个随机的密钥对和盐,作为加密的密码。将密钥对和盐存储在data 分区的footer中*/static int create_encrypted_random_key(char *passwd, unsigned char *master_key, unsigned char *salt,crypt_mnt_ftr *crypt_ftr) { /* Get some random bits for a key */ fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC); read(fd, key_buf, sizeof(key_buf)); read(fd, salt, SALT_LEN); close(fd); /* Now encrypt it with the password */ return encrypt_master_key(passwd, salt, key_buf, master_key, crypt_ftr);}
/* Create mapped block device /dev/dm-0 with key_index which retrieved from set_hw_device_encryption_key*/decrypt_master_key(passwd, decrypted_master_key, &crypt_ftr, 0, 0);create_crypto_blk_dev(&crypt_ftr, decrypted_master_key, real_blkdev, crypto_blkdev, "userdata");rc = cryptfs_enable_all_volumes(&crypt_ftr, how, crypto_blkdev, real_blkdev, previously_encrypted_upto);cryptfs_enable_all_volumes--->cryptfs_enable_inplace ------> cryptfs_enable_inplace_ext4
//加密进程从真实的设备节点读取数据并将其写入到mapper的设备节点中static int cryptfs_enable_inplace_ext4(char *crypto_blkdev, char *real_blkdev, off64_t size, off64_t *size_already_done, off64_t tot_size, off64_t previously_encrypted_upto){ encrypt_groups(&data);}
加密实现
//判定当前设备是否正在被加密或者曾经加密过程被中断了。if (how == CRYPTO_ENABLE_INPLACE && get_crypt_ftr_and_key(&crypt_ftr) == 0 && (crypt_ftr.flags & CRYPT_ENCRYPTION_IN_PROGRESS)) { previously_encrypted_upto = crypt_ftr.encrypted_upto; crypt_ftr.encrypted_upto = 0; crypt_ftr.flags &= ~CRYPT_ENCRYPTION_IN_PROGRESS; //加密的状态是一直不确定的。直到加密完成,然后重启。所以,这里首先将flag设置为CRYPT_INCONSISTENT_STATE。 //待加密完成之后,将CRYPT_INCONSISTENT_STATE标志移除 crypt_ftr.flags |= CRYPT_INCONSISTENT_STATE; put_crypt_ftr_and_key(&crypt_ftr);}//判断当前加密状态,通过ro.crypto.state属性来感知//计算real block device的大小//申请一个wakelock,这里可以根据需求申请partial或者full wake locksnprintf(lockid, sizeof(lockid), "enablecrypto%d", (int) getpid());acquire_wake_lock(PARTIAL_WAKE_LOCK, lockid);/* The init files are setup to stop the class main and late start when * vold sets trigger_shutdown_framework. *///设置vold.decrypt属性的值为trigger_shutdown_framework,触发init进程shutdown class mainproperty_set("vold.decrypt", "trigger_shutdown_framework");SLOGD("Just asked init to shut down class main\n");/* Ask vold to unmount all devices that it manages *///unmount 被管理的所有设备if (vold_unmountAll()) { SLOGE("Failed to unmount all vold managed devices");}/* Now unmount the /data partition. *///挂在data分区if (wait_and_unmount(DATA_MNT_POINT, false)) { if (allow_reboot) { goto error_shutting_down; } else { goto error_unencrypted; }}//为了提升体验,这里所做的一些额外工作/* Do extra work for a better UX when doing the long inplace encryption */if (how == CRYPTO_ENABLE_INPLACE) { ///data分区已经被卸载了,这里需要将tmpfs挂在到/data并且设置一个系统属性告诉系统,我们要开始加密啦。。 if (fs_mgr_do_tmpfs_mount(DATA_MNT_POINT)) { goto error_shutting_down; }else { SLOGD("Successful: mount tmpfs for '%s'\n", DATA_MNT_POINT); //.. } //告诉framework,我们要开始加密啦。framework property_set("vold.encrypt_progress", "0");//在/data上座一些必要的准备。//设置vold.decryp=trigger_post_fs_data来触发系列动作。 //动作完成之后,会将vold.post_fs_data_done设置为1,这里等待50s /* *property_set("vold.post_fs_data_done", "0"); *property_set("vold.decrypt", "trigger_post_fs_data"); *SLOGD("Just triggered post_fs_data\n"); */ if (prep_data_fs()) { goto error_shutting_down; } //等待2s,shutting down framework并不是同步进行。//故,需要等待一段时间。否则在某些设备上,图形相关的服务有有问题。 sleep(2);}/* Start the actual work of making an encrypted filesystem *//* Initialize a crypt_mnt_ftr for the partition *///开始真正的加密前的初始化。首先初始化得到crypt_mnt_ftr.if (previously_encrypted_upto == 0) { if (cryptfs_init_crypt_mnt_ftr(&crypt_ftr)) { goto error_shutting_down; } if (!strcmp(key_loc, KEY_IN_FOOTER)) { crypt_ftr.fs_size = nr_sec - (CRYPT_FOOTER_OFFSET / CRYPT_SECTOR_SIZE); } else { crypt_ftr.fs_size = nr_sec; } //加密的状态是一直不确定的。直到加密完成,然后重启。所以,这里首先将flag设置为CRYPT_INCONSISTENT_STATE。 //待加密完成之后,将CRYPT_INCONSISTENT_STATE标志移除 crypt_ftr.flags |= CRYPT_INCONSISTENT_STATE; crypt_ftr.crypt_type = crypt_type; //选择不同的加密方式 "aes-xts"(使用软件加密) "aes-cbc-essiv:sha256"(使用硬件加密) //设置秘钥,将秘钥存储在分区的尾部。#ifndef CONFIG_HW_DISK_ENCRYPTION strlcpy((char *)crypt_ftr.crypto_type_name, "aes-cbc-essiv:sha256", MAX_CRYPTO_TYPE_NAME_LEN);#else strlcpy((char *)crypt_ftr.crypto_type_name, "aes-xts", MAX_CRYPTO_TYPE_NAME_LEN); rc = clear_hw_device_encryption_key(); rc = set_hw_device_encryption_key(passwd,#endif /* Make an encrypted master key */ if (create_encrypted_random_key(passwd, crypt_ftr.master_key, crypt_ftr.salt, &crypt_ftr)) { SLOGE("Cannot create encrypted master key\n"); goto error_shutting_down; } //将key写入到分区结尾 put_crypt_ftr_and_key(&crypt_ftr);}if (how == CRYPTO_ENABLE_INPLACE) { /* startup service classes main and late_start */ //启动services class main以及late_start property_set("vold.decrypt", "trigger_restart_min_framework"); SLOGD("Just triggered restart_min_framework\n"); //framework正在重启。稍等就可以看到进度条的显示了。 //进度条显示的含义是:正在加密的进度或者加密映射的文件进度}//创建master key//将‘userdata’映射到加密节点上decrypt_master_key(passwd, decrypted_master_key, &crypt_ftr, 0, 0);create_crypto_blk_dev(&crypt_ftr, decrypted_master_key, real_blkdev, crypto_blkdev, "userdata");/* If we are continuing, check checksums match *///读取的sha256值,同计算得到的sha256值进行比较rc = 0;if (previously_encrypted_upto) { __le8 hash_first_block[SHA256_DIGEST_LENGTH]; rc = cryptfs_SHA256_fileblock(crypto_blkdev, hash_first_block); if (!rc && memcmp(hash_first_block, crypt_ftr.hash_first_block, sizeof(hash_first_block)) != 0) { SLOGE("Checksums do not match - trigger wipe"); rc = -1; }}if (!rc) { rc = cryptfs_enable_all_volumes(&crypt_ftr, how, crypto_blkdev, real_blkdev, previously_encrypted_upto);}/* Calculate checksum if we are not finished */if (!rc && how == CRYPTO_ENABLE_INPLACE && crypt_ftr.encrypted_upto != crypt_ftr.fs_size) { rc = cryptfs_SHA256_fileblock(crypto_blkdev, crypt_ftr.hash_first_block); if (rc) { SLOGE("Error calculating checksum for continuing encryption"); rc = -1; }}/* Undo the dm-crypt mapping whether we succeed or not */delete_crypto_blk_dev("userdata");if (! rc) {//条件判断ok /* Success */ crypt_ftr.flags &= ~CRYPT_INCONSISTENT_STATE; if (how == CRYPTO_ENABLE_INPLACE && crypt_ftr.encrypted_upto != crypt_ftr.fs_size) { SLOGD("Encrypted up to sector %lld - will continue after reboot", crypt_ftr.encrypted_upto); crypt_ftr.flags |= CRYPT_ENCRYPTION_IN_PROGRESS; } put_crypt_ftr_and_key(&crypt_ftr);#ifdef MTK_EMMC_SUPPORT { struct phone_encrypt_state ps; ps.state = PHONE_ENCRYPTED; if (misc_set_phone_encrypt_state(&ps, fstab) < 0) { SLOGE("Failed to set encrypted status to 0x%x in MISC\n", ps.state); } else { SLOGD("Success: Set encrypted status to 0x%x in MISC\n", ps.state); } }#endif //判断ro.crypted.state的值是否为encrypted(代表加密成功) if (how == CRYPTO_ENABLE_WIPE || crypt_ftr.encrypted_upto == crypt_ftr.fs_size) { char value[PROPERTY_VALUE_MAX]; property_get("ro.crypto.state", value, ""); if (!strcmp(value, "")) { /* default encryption - continue first boot sequence */ property_set("ro.crypto.state", "encrypted"); release_wake_lock(lockid); cryptfs_check_passwd(DEFAULT_PASSWORD); property_set("vold.encrypt_progress", ""); cryptfs_restart_internal(1); return 0; } else { sleep(2); /* Give the UI a chance to show 100% progress */ cryptfs_reboot(reboot); } } else { sleep(2); /* Partially encrypted, ensure writes flushed to ssd */ cryptfs_reboot(shutdown); }} 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|O_CLOEXEC, 0600); if (fd >= 0) { write(fd, "--wipe_data\n", strlen("--wipe_data\n") + 1); write(fd, "--reason=cryptfs_enable_internal\n", strlen("--reason=cryptfs_enable_internal\n") + 1); close(fd); } else { SLOGE("could not open /cache/recovery/command\n"); } cryptfs_reboot(recovery); } else { /* set property to trigger dialog */ property_set("vold.encrypt_progress", "error_partially_encrypted"); release_wake_lock(lockid); } return -1;}
参考
Vold properties
Property | Description |
---|---|
vold.decrypt=trigger_encryption | Encrypt the drive with no password. |
vold.decrypt=trigger_default_encryption | Check the drive to see if it is encrypted with no password. If it is,decrypt and mount it, else setvold.decrypt to trigger_restart_min_framework. |
vold.decrypt=trigger_reset_main | Set by vold to shutdown the UI asking for the disk password. |
vold.decrypt=trigger_post_fs_data | Set by vold to prep /data with necessary directories, et al. |
vold.decrypt=trigger_restart_framework | Set by vold to start the real framework and all services. |
vold.decrypt=trigger_shutdown_framework | Set by vold to shutdown the full framework to start encryption. |
vold.decrypt=trigger_restart_min_framework | Set by vold to start the progress bar UI for encryption or prompt for password, depending on the value of ro.crypto.state. |
vold.encrypt_progress | When the framework starts up, if this property is set, enter the progress bar UI mode. |
vold.encrypt_progress | 0 to 100 The progress bar UI should display the percentage value set. |
vold.encrypt_progress=error_partially_encrypted | The progress bar UI should display a message that the encryption failed, and give the user an option to factory reset the device. |
vold.encrypt_progress=error_reboot_failed | The progress bar UI should display a message saying encryption completed, and give the user a button to reboot the device. This error is not expected to happen. |
vold.encrypt_progress=error_not_encrypted | The progress bar UI should display a message saying an error occurred, no data was encrypted or lost, and give the user a button to reboot the system. |
vold.encrypt_progress=error_shutting_down | The progress bar UI is not running, so it is unclear who will respond to this error. And it should never happen anyway. |
vold.post_fs_data_done | 0 Set by vold just before setting vold.decrypt to trigger_post_fs_data. |
vold.post_fs_data_done | 1 Set by init.rc or init.rc just after finishing the task post-fs-data. |
init properties
Property | Description |
---|---|
ro.crypto.fs_crypto_blkdev | Set by the vold command checkpw for later use by the vold command restart. |
ro.crypto.state unencrypted | Set by init to say this system is running with an unencrypted /data ro.crypto.state encrypted. Set by init to say this system is running with an encrypted /data. |
ro.crypto.fs_type | |
ro.crypto.fs_real_blkdev | |
ro.crypto.fs_mnt_point | |
ro.crypto.fs_options | |
ro.crypto.fs_flags | These five properties are set by init when it tries to mount /data with parameters passed in from init.rc. vold uses these to setup the crypto mapping. |
ro.crypto.tmpfs_options | Set by init.rc with the options init should use when mounting the tmpfs /data filesystem |
init actions
on post-fs-data
on nonencrypted
on property:vold.decrypt=trigger_reset_main
on property:vold.decrypt=trigger_post_fs_data
on property:vold.decrypt=trigger_restart_min_framework
on property:vold.decrypt=trigger_restart_framework
on property:vold.decrypt=trigger_shutdown_framework
on property:vold.decrypt=trigger_encryption
on property:vold.decrypt=trigger_default_encryption
更多相关文章
- Java/android下哈希sha1和MD5的实现
- webview开发-适配多分辨率的Android设备
- Android(安卓)实现蓝牙客户端与服务器端通信
- android内核字符驱动设备实战之----------设备驱动程序篇
- Google 发布 Android(安卓)@ Home,让你用 Android(安卓)设备控制
- android事件处理
- Android(安卓)手机bluetooth 名字的设置
- js判断Android和Ios
- Android加密算法:AES、Base64加密算法