Android 分区加密那些事 (1)

操作系统是给用户使用的,用户的隐私是系统安全需要保障的关键,而加密则是系统安全性保障的一大模块。

在 Android 里,系统会对设备上的用户文件分区进行加密处理,设备加密后,用户的数据在写入磁盘前都会进行自动加密,如果系统应用在经过授权后需要读取,数据将会在返回给进程前自动解密。未经授权时,访问的数据将无法被读取,具体可表现为空 / 乱码(但包含文件的基本信息如大小、修改时间等)。且在恢复出厂时,如分区未解密,/data 分区将会被强制清空(仅针对于部分厂商和第三方的 Recovery 设定)。

加密分 FDE 全盘加密(Android 4.x 开始引入)和 FBE 文件级加密(Android 7.0 开始引入)。在 Android 9.0 中,Google 引入了元数据加密(Metadata FBE)的支持,基于 FBE 的延伸,Android 10 后,FDE 不再起作用,默认使用 FBE 加密。

在 Android 6 之前,Google 没有对厂商做出硬性要求,在此之后只要手机默认搭载该版本及更高的系统,手机必须默认开启加密功能(第一次开机进行加密)。

全盘加密

全盘加密,全称为 Full Disk Encryption,它使用密钥(密钥本身也经过加密)对 用户数据分区(/data、/userdata)进行加密。

FDE 基于 android kernel 的 dm-crypt 特性实现,它采用 128 AES 的算法,搭配 Cipher-Block Chaining (CBC) 和 ESSIV:SHA256 进行加密。其标准流程如下(以 Android 5.0 为例):

  1. 创建一个随机生成的 128 位的 master key 和 SALT。
  2. 对默认(用户)密码和 SALT 使用 scrypt 算法产生一个 HASH,作为初级密钥 IK1(默认密码是“default_password”)。通过 TEE 为生成的 HASH 签名,同时使用相应签名的 HASH 来加密 master key。
  3. 通过 TEE 初始化 RSA 密钥对,最终在 TEE 中对 IK1 进行签名,对初级密钥进行签名,生成二级密钥 IK2。
  4. 跟第二步相同的方法,对二级密钥和 SALT 使用 scrypt 算法产生三级密钥 ikey。
  5. 使用 ikey 的前 16 Bytes 作为 KEK(用来加密 master key 的 Key),后 16 Bytes 作为算法。
  6. 使用 AES_CBC 算法,采用 KEK 作为密钥,算法作为初始化向量来加密用户的 master key,生成加密后的 master key,存入分区尾部数据结构中。

在 5.0 之前,全盘加密有局限性,在 5.0 发布后,全盘加密带来了更大的灵活性,例如:快速加密(仅对 EXT4、F2FS 文件系统有效)、fstab.qcom 标志(以便第一次开机进行强制加密)、无密码加密的支持(第一次开机用户无法设置密码)、加密密钥可存储在专门的硬件存储空间内。同时,5.0 的 FDE 方式相比以前也有很多改变,4.x 时期只是对用户密码及 SALT 采用了 scrypt 算法来生成加密密钥(且用的是 pkdf2 算法,强度更低),可以直接通过枚举的方式暴力破解,在 5.0 后,增加了基于 TEE 的签名过程,结合多次 scrypt 算法产生加密密钥,然后以此密钥来加密 master key。

若要加密、解密或清除 /data,/data 不得处于装载状态。但是如果要显示任何系统界面,框架都必须启动,而框架需要 /data 才能运行。为了解决这一冲突,/data 上会装载一个临时文件系统。通过该文件系统,Android 可以提示输入密码、显示进度或根据需要建议清除数据。呈现为开机前输入密码的黑屏界面。

因为 Google 要求厂商对用户分区进行强制加密是在 6.0 之后,因此在这里分两种情况:1. 第一次开机自动加密(无密码设置,适用于 5.0 及以上版本)。2. 用户手动通过设置>安全>加密与凭据加密(由用户设置密码,适用于 4.x)。

第一次开机的新设备加密(针对于开启了强制加密的 5.0 系统)

  1. 检查 fstab.qcom,因 /data 被设置为 forceencrypted,因此需要加密,如没有则不加密(可以此办法阻止第一次启动系统的手机进行强制加密)。
  2. 开始加密 /data,进行无密码加密。
  3. 装载一个 tmp 分区,将 /data 挂载到该分区内。
  4. 启动框架以显示进度,对于此方式的加密,进度条不会被显示(没有数据需要加密)。如果是 5.0 之前的设备,则会再启动一次开机动画,因为加解密需要 restart 一次服务。
  5. 加密完成后关闭框架。vold 会将 vold.decrypt 设置为 trigger_default_encryption 以检查加密类型,同时启动 defaultcrypto 服务,以便解密并装载 /data。
  6. 启动框架,进入系统界面。

由用户对现有设备进行加密(进入设置手动加密)

使用这种方式,/data 里通常会有一些数据,因此加密的速度取决于数据的大小和多少。同时,加密前需要给手机充电(并达到一定数值要求),如果设备在完成加密之前耗尽电量并关机,文件数据将会处于部分加密状态,出现这种情况,必须恢复出厂设置,而这将导致所有数据都丢失。

  1. 加密前,要求用户输入密码,该密码将会是用户密码,用来加密。
  2. 检查密码,用来获取用户输入的密码。
  3. 用 vold 检查是否存在错误,如果不能加密,则会返回 -1 并记录在日志中,如果能加密,将会关闭框架,停止部分服务。
  4. 创建加密页脚和路径文件,开始重新启动。
  5. 开机后,检测路径文件,开始加密 /data。
  6. 装载一个 tmp 分区,将 /data 挂载到该分区内。
  7. 启动框架以显示进度,该界面每 5 秒查询一次该属性并更新进度条。加密循环会在已加密的分区比例每增加百分之一时更新一次 vold.encrypt_progress。
  8. /data 加密后,更新加密页脚。

以上是加密流程,对于这两种加密情况,启动流程也是不一样的。

首先,系统会检测设备是否加密,如 /data 无法装载,那么设备已经被加密,此时将会检测是设置了 encryptable 或 forceencrypt 标记之一(无密码加密),还是设置了 ro.crypto.state = “encrypted” 标记(设定了密码的加密)。如果是前者,会启动 defaultcrypto 服务,以了解 /data 加密是否使用了密码;如果是后者,vold 会将 vold.decrypt 设置为 trigger_restart_min_framework,告诉框架自己需要获取用户密码(呈现为开机前输入密码的黑屏界面)。

加密实现

下面我们来讨论一下加密实现,该实现方法可以在 cryptfs.cpp 中找到。

//采用基于 TEE 签名,使用多次 scrypt 算法产生加密密钥
struct crypt_mnt_ftr {
    __le32 magic; /* See above */
    __le16 major_version;
    __le16 minor_version;
    __le32 ftr_size;             /* in bytes, not including key following */
    __le32 flags;                /* See above */
    __le32 keysize;              /* in bytes */
    __le32 crypt_type;           /* how master_key is encrypted. Must be a
                                  * CRYPT_TYPE_XXX value */
    __le64 fs_size;              /* Size of the encrypted fs, in 512 byte sectors */
    __le32 failed_decrypt_count; /* count of # of failed attempts to decrypt and
                                    mount, set to 0 on successful mount */
    unsigned char crypto_type_name[MAX_CRYPTO_TYPE_NAME_LEN]; /* The type of encryption
                                                                 needed to decrypt this
                                                                 partition, null terminated */
    __le32 spare2;                                            /* ignored */
    unsigned char master_key[MAX_KEY_LEN]; /* The encrypted key for decrypting the filesystem */
    unsigned char salt[SALT_LEN];          /* The salt used for this encryption */
    __le64 persist_data_offset[2];         /* Absolute offset to both copies of crypt_persist_data
                                            * on device with that info, either the footer of the
                                            * real_blkdevice or the metadata partition. */
    __le32 persist_data_size; /* The number of bytes allocated to each copy of the
                               * persistent data table*/
    __le8 kdf_type; /* The key derivation function used. */
    /* scrypt parameters. See www.tarsnap.com/scrypt/scrypt.pdf */
    __le8 N_factor;        /* (1 << N) */
    __le8 r_factor;        /* (1 << r) */
    __le8 p_factor;        /* (1 << p) */
    __le64 encrypted_upto; /* If we are in state CRYPT_ENCRYPTION_IN_PROGRESS and
                              we have to stop (e.g. power low) this is the last
                              encrypted 512 byte sector.*/
    __le8 hash_first_block[SHA256_DIGEST_LENGTH]; /* When CRYPT_ENCRYPTION_IN_PROGRESS
                                                     set, hash of first block, used
                                                     to validate before continuing*/
    /* key_master key, used to sign the derived key which is then used to generate
     * the intermediate key
     * This key should be used for no other purposes! We use this key to sign unpadded
     * data, which is acceptable but only if the key is not reused elsewhere. */
    __le8 keymaster_blob[KEYMASTER_BLOB_SIZE];
    __le32 keymaster_blob_size;
    /* Store scrypt of salted intermediate key. When decryption fails, we can
       check if this matches, and if it does, we know that the problem is with the
       drive, and there is no point in asking the user for more passwords.
       Note that if any part of this structure is corrupt, this will not match and
       we will continue to believe the user entered the wrong password. In that
       case the only solution is for the user to enter a password enough times to
       force a wipe.
       Note also that there is no need to worry about migration. If this data is
       wrong, we simply won't recognise a right password, and will continue to
       prompt. On the first password change, this value will be populated and
       then we will be OK.
     */
    unsigned char scrypted_intermediate_key[SCRYPT_LEN];
    /* sha of this structure with this element set to zero
       Used when encrypting on reboot to validate structure before doing something
       fatal
     */
    unsigned char sha256[SHA256_DIGEST_LENGTH];
};
#define HASH_COUNT 2000
#define SALT_LEN 16

//这是 4.x 时期采用 pbkdf2 的算法,强度低,很容易被暴力破解
static int pbkdf2(const char *passwd, const unsigned char *salt,
                  unsigned char *ikey, void *params UNUSED)
{
    SLOGI("Using pbkdf2 for cryptfs KDF");

    /* Turn the password into a key and IV that can decrypt the master key */
    return PKCS5_PBKDF2_HMAC_SHA1(passwd, strlen(passwd), salt, SALT_LEN,
                                  HASH_COUNT, INTERMEDIATE_BUF_SIZE,
                                  ikey) != 1;
}
//后续采用 scrypt 算法,强度高,难被破解
static int scrypt(const char *passwd, const unsigned char *salt,
                  unsigned char *ikey, void *params)
{
    SLOGI("Using scrypt for cryptfs KDF");

    struct crypt_mnt_ftr *ftr = (struct crypt_mnt_ftr *) params;

    int N = 1 << ftr->N_factor;
    int r = 1 << ftr->r_factor;
    int p = 1 << ftr->p_factor;

    /* Turn the password into a key and IV that can decrypt the master key */
    crypto_scrypt((const uint8_t*)passwd, strlen(passwd),
                  salt, SALT_LEN, N, r, p, ikey,
                  INTERMEDIATE_BUF_SIZE);

   return 0;
}
//开始加密

static int scrypt_keymaster(const char *passwd, const unsigned char *salt,
                            unsigned char *ikey, void *params)
{
    SLOGI("Using scrypt with keymaster for cryptfs KDF");

    int rc;
    size_t signature_size;
    unsigned char* signature;
    struct crypt_mnt_ftr *ftr = (struct crypt_mnt_ftr *) params;

    int N = 1 << ftr->N_factor;
    int r = 1 << ftr->r_factor;
    int p = 1 << ftr->p_factor;

   //第一轮 scrypt,生成初级密钥(IK1)
    rc = crypto_scrypt((const uint8_t*)passwd, strlen(passwd),
                       salt, SALT_LEN, N, r, p, ikey,
                       INTERMEDIATE_BUF_SIZE);

    if (rc) {
        SLOGE("scrypt failed");
        return -1;
    }

   //通过 TEE 初始化 RSA 密钥对,最终在 TEE 中对 IK1 进行签名,对初级密钥进行签名,生成二级密钥 IK2
    if (keymaster_sign_object(ftr, ikey, INTERMEDIATE_BUF_SIZE,
                              &signature, &signature_size)) {
        SLOGE("Signing failed");
        return -1;
    }

   //对 IK2 结合 SALT 再通过 scrypt 产生最终的使用 AES_CBC 的三级密钥 ikey (16B KEK + 16B 算法 IV)
    rc = crypto_scrypt(signature, signature_size, salt, SALT_LEN,
                       N, r, p, ikey, INTERMEDIATE_BUF_SIZE);
    free(signature);

    if (rc) {
        SLOGE("scrypt failed");
        return -1;
    }

    return 0;
}
//调用加密模块,产生 RSA 密钥对
/* Create a new keymaster key and store it in this footer */
static int keymaster_create_key(struct crypt_mnt_ftr *ftr)
{
    if (ftr->keymaster_blob_size) {
        SLOGI("Already have key");
        return 0;
    }

    int rc = keymaster_create_key_for_cryptfs_scrypt(RSA_KEY_SIZE, RSA_EXPONENT,
            KEYMASTER_CRYPTFS_RATE_LIMIT, ftr->keymaster_blob, KEYMASTER_BLOB_SIZE,
            &ftr->keymaster_blob_size);
    if (rc) {
        if (ftr->keymaster_blob_size > KEYMASTER_BLOB_SIZE) {
            SLOGE("Keymaster key blob too large");
            ftr->keymaster_blob_size = 0;
        }
        SLOGE("Failed to generate keypair");
        return -1;
    }
    return 0;
}
static int encrypt_master_key(const char *passwd, const unsigned char *salt,
                              const unsigned char *decrypted_master_key,
                              unsigned char *encrypted_master_key,
                              struct crypt_mnt_ftr *crypt_ftr)
{
    unsigned char ikey[INTERMEDIATE_BUF_SIZE] = { 0 };
    EVP_CIPHER_CTX e_ctx;
    int encrypted_len, final_len;
    int rc = 0;

    /* Turn the password into an intermediate key and IV that can decrypt the master key */
    get_device_scrypt_params(crypt_ftr);

    switch (crypt_ftr->kdf_type) {
    case KDF_SCRYPT_KEYMASTER:
        // 加载并初始化 keymaster,通过 TEE 得到 RSA 的公私钥对 keymaster_blob
        if (keymaster_create_key(crypt_ftr)) {
            SLOGE("keymaster_create_key failed");
            return -1;
        }

        // 产生加密密钥 KEK
        if (scrypt_keymaster(passwd, salt, ikey, crypt_ftr)) {
            SLOGE("scrypt failed");
            return -1;
        }
        break;

    case KDF_SCRYPT:
        if (scrypt(passwd, salt, ikey, crypt_ftr)) {
            SLOGE("scrypt failed");
            return -1;
        }
        break;

    default:
        SLOGE("Invalid kdf_type");
        return -1;
    }

    /* Initialize the decryption engine */
    EVP_CIPHER_CTX_init(&e_ctx);
    if (! EVP_EncryptInit_ex(&e_ctx, EVP_aes_128_cbc(), NULL, ikey,
                             ikey+INTERMEDIATE_KEY_LEN_BYTES)) {
        SLOGE("EVP_EncryptInit failed\n");
        return -1;
    }
    EVP_CIPHER_CTX_set_padding(&e_ctx, 0); /* Turn off padding as our data is block aligned */

    //使用 KEK 及 IV,采用 AES_CBC 算法对 Master key 加密
    /* Encrypt the master key */
    if (! EVP_EncryptUpdate(&e_ctx, encrypted_master_key, &encrypted_len,
                            decrypted_master_key, crypt_ftr->keysize)) {
        SLOGE("EVP_EncryptUpdate failed\n");
        return -1;
    }
    if (! EVP_EncryptFinal_ex(&e_ctx, encrypted_master_key + encrypted_len, &final_len)) {
        SLOGE("EVP_EncryptFinal failed\n");
        return -1;
    }

    if (encrypted_len + final_len != static_cast<int>(crypt_ftr->keysize)) {
        SLOGE("EVP_Encryption length check failed with %d, %d bytes\n", encrypted_len, final_len);
        return -1;
    }

    /* Store the scrypt of the intermediate key, so we can validate if it's a
       password error or mount error when things go wrong.
       Note there's no need to check for errors, since if this is incorrect, we
       simply won't wipe userdata, which is the correct default behavior
    */
    int N = 1 << crypt_ftr->N_factor;
    int r = 1 << crypt_ftr->r_factor;
    int p = 1 << crypt_ftr->p_factor;

    //将 ikey 存入分区尾部数据结构中。生成加密密钥并做一次摘要,将值与存入的比较来检验是否一致。
    rc = crypto_scrypt(ikey, INTERMEDIATE_KEY_LEN_BYTES,
                       crypt_ftr->salt, sizeof(crypt_ftr->salt), N, r, p,
                       crypt_ftr->scrypted_intermediate_key,
                       sizeof(crypt_ftr->scrypted_intermediate_key));

    if (rc) {
      SLOGE("encrypt_master_key: crypto_scrypt failed");
    }

    EVP_CIPHER_CTX_cleanup(&e_ctx);

    return 0;
}
暂无评论

发送评论 编辑评论

分享你的想法!评论时请如实在信息框填写个人信息
文明上网理性发言,请遵守相关法律法规,发表评论将会在站长审核通过后出现在评论列表中

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇