Android 5.0中的FDE功能实现

转载 2015年07月06日 16:14:10
标 题: 【分享】Android 5.0中的FDE功能实现
作 者: netsniffer
时 间: 2015-01-27,21:15:06
链 接: http://bbs.pediy.com/showthread.php?t=197289

Android 5.0中的FDE(full disk encryption-全磁盘加密)

在Android 5.0之前,vold中的磁盘加密功能FDE(full disk encryption),只是对用户密码及SALT采用了scrypt算法来生成加密密钥(4.4以前使用pkdf2,强度更低),这些只是增加了暴力破解的难度,但风险依然很高。比如可以获取分区镜像 前1K 及 后16K 的数据 (Android FDE的相关加密密钥默认存储在分区尾部的16 KBytes中,前4K是EXT4文件系统的Superblock,一般EXT4 FS SuperBlock前1K均为0),暴力枚举用户密码,并采用vold中相同的磁盘加密算法来生成解密密钥,尝试用其对前1K中的部分数据进行AES算法解密,解出来都为0则碰撞OK。默认Android原生的用户密码6位数字密码,即使很普通的PC 3、5个小时都能跑出来。

从5.0开始,增加了硬件TEE(Trusted Execution Environment)中的签名过程,结合多次scrypt算法来产生加密密钥,以此密钥来加密 磁盘加密主密钥,使得离线暴力破解几乎无可能,除非破解者获得了TEE中的RSA私钥,并清楚特定硬件平台HAL中的keymaster模块实现。

5.0中的加密 磁盘加密密钥 的实现逻辑:

1. 产生随机16 Bytes DEK(disk encryption key--磁盘加密用的密钥)及16 Bytes SALT;

2. 对(用户密码+SALT)使用scrypt算法产生32 Bytes HASH 作为IK1(intermediate key 1);

3. 将IK1填充到硬件产生的私钥规格大小(目前看到是RSA算法,256Bytes), 具体是: 
00 || IK1 || 00..00  ## one zero byte, 32 IK1 bytes, 223 zero bytes.

4. 使用硬件私钥 HBK 对 IK1 进行签名,生成256 Bytes签名数据作为IK2;

5. 对(IK2+SALT)使用scrypt算法(与第二步中的SALT相同)产生出32 Bytes HASH 作为IK3;

6. 使用IK3前16 Bytes作为KEK(用来加密主密钥DEK的KEY),后16 Bytes作为算法IV(初始化向量);

7. 使用AES_CBC算法,采用KEK作为密钥,IV作为初始化向量来加密用户的主密钥DEK,生成加密后的主密钥,存入分区尾部数据结构中;

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

> 4.4之前的分区尾部 磁盘加密相关的存储结构,默认采用pbkdf2来做密钥导出算法
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 spare1;  /* ignored */
  __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 */
};

> 4.4中的分区尾部 磁盘加密相关的存储结构,可选择使用scrypt及pbkdf2,默认为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 spare1;  /* ignored */
  __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) */
};

> 5.0中默认采用基于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];
};

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

几种密钥导出算法

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 */
    unsigned int keysize;
    char* master_key = (char*)convert_hex_ascii_to_key(passwd, &keysize);
    if (!master_key) return -1;
    // 2000轮,显然强度很低,所以4.4以后改成scrypt这种强度适中的算法
    PKCS5_PBKDF2_HMAC_SHA1(master_key, keysize, salt, SALT_LEN,
                           /*2000*/HASH_COUNT, /*16+16*/KEY_LEN_BYTES+IV_LEN_BYTES, ikey);

    memset(master_key, 0, keysize);
    free (master_key);
    return 0;
}

----------------------------------------------------

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; // 15
    int r = 1 << ftr->r_factor; // 3
    int p = 1 << ftr->p_factor; // 1

    /* Turn the password into a key and IV that can decrypt the master key */
    unsigned int keysize;
    unsigned char* master_key = convert_hex_ascii_to_key(passwd, &keysize);
    if (!master_key) return -1;
    crypto_scrypt(master_key, keysize, salt, SALT_LEN, N, r, p, ikey,
            KEY_LEN_BYTES + IV_LEN_BYTES);

    memset(master_key, 0, keysize);
    free (master_key);
    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;
    unsigned int key_size;
    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;

    unsigned char* master_key = convert_hex_ascii_to_key(passwd, &key_size);
    if (!master_key) {
        SLOGE("Failed to convert passwd from hex");
        return -1;
    }

    // 第一轮scrypt,生成IK1
    rc = crypto_scrypt(master_key, key_size, salt, SALT_LEN,
                       N, r, p, ikey, KEY_LEN_BYTES + IV_LEN_BYTES);
    memset(master_key, 0, key_size);
    free(master_key);

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

    // TEE中初始化RSA密钥对,最终在TEE中对IK1进行签名,并返回签名结果作为IK2
    if (keymaster_sign_object(ftr, ikey, KEY_LEN_BYTES + IV_LEN_BYTES,
                              &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, KEY_LEN_BYTES + IV_LEN_BYTES);
    free(signature);

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

    return 0;
}

/* Create a new keymaster key and store it in this footer */
static int keymaster_create_key(struct crypt_mnt_ftr *ftr)
{
    uint8_t* key = 0;
    keymaster_device_t *keymaster_dev = 0;

    if (keymaster_init(&keymaster_dev)) {
        SLOGE("Failed to init keymaster");
        return -1;
    }

    int rc = 0;

    keymaster_rsa_keygen_params_t params;
    memset(&params, '\0', sizeof(params));
    params.public_exponent = RSA_EXPONENT;
    params.modulus_size = RSA_KEY_SIZE;

    size_t key_size;
    // 调用HAL中的加密模块,产生RSA密钥对
    // 其中的私钥一般是加密返回的,具体keymaster_blob数据结构和算法不同硬件平台厂家实现不一样

    if (keymaster_dev->generate_keypair(keymaster_dev, TYPE_RSA, &params,
                                        &key, &key_size)) {
        SLOGE("Failed to generate keypair");
        rc = -1;
        goto out;
    }

    if (key_size > KEYMASTER_BLOB_SIZE) {
        SLOGE("Keymaster key too large for crypto footer");
        rc = -1;
        goto out;
    }

    memcpy(ftr->keymaster_blob, key, key_size);
    ftr->keymaster_blob_size = key_size;

out:
    keymaster_close(keymaster_dev);
    free(key);
    return rc;
}

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[32+32] = { 0 }; /* Big enough to hold a 256 bit key and 256 bit IV */
    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_UNPADDED:
    case KDF_SCRYPT_KEYMASTER_BADLY_PADDED:
    case KDF_SCRYPT_KEYMASTER:
        // 加载并初始化HAL 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;
    }

    // 使用前边得到的KEK及IV,采用AES_CBC算法对磁盘加密密钥master_key进行加密
    

    /* Initialize the decryption engine */
    if (! EVP_EncryptInit(&e_ctx, EVP_aes_128_cbc(), ikey, ikey+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 */

    /* Encrypt the master key */
    if (! EVP_EncryptUpdate(&e_ctx, encrypted_master_key, &encrypted_len,
                              decrypted_master_key, KEY_LEN_BYTES)) {
        SLOGE("EVP_EncryptUpdate failed\n");
        return -1;
    }
    if (! EVP_EncryptFinal(&e_ctx, encrypted_master_key + encrypted_len, &final_len)) {
        SLOGE("EVP_EncryptFinal failed\n");
        return -1;
    }

    if (encrypted_len + final_len != KEY_LEN_BYTES) {
        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(KEK+IV)进行一次摘要存入分区尾部的crypt_ftr数据结构中,
    // 后续验证可对传入的用户密码+分区尾部存储的SALT 采用同样的密钥导出算法,
    // 生成加密密钥,并做一次scrypt摘要,将摘要值与之前crypt_ftr存储的进行比较来检验是否一致

    rc = crypto_scrypt(ikey, 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");
    }

    return 0;
}
举报

相关文章推荐

android 5.0+录屏功能实现

Android Lollipop (5.0) 屏幕录制实现 基本原理在 Android 5.0,Google 终于开放了视频录制的接口,其实严格来说,是屏幕采集的接口,也就是 MediaProjec...

金额从数字转换成中文汉字大写的实现

以下简单封装的实现方法,详细见注释class money2chinese { private $amount; // 金额 private $num2chinese; // 大写数字 ...

精选:深入理解 Docker 内部原理及网络配置

网络绝对是任何系统的核心,对于容器而言也是如此。Docker 作为目前最火的轻量级容器技术,有很多令人称道的功能,如 Docker 的镜像管理。然而,Docker的网络一直以来都比较薄弱,所以我们有必要深入了解Docker的网络知识,以满足更高的网络需求。

VC之CString,wchar_t,int,string,char*之间的转换

1. CString 转 wchar_t CString path = "asdf"; wchar_t wstr[256] = path.AllocSysString(); 或者: ...

Android:Navigationdrawer导航抽屉功能的实现(源代码分享)

http://blog.csdn.net/gerogelin/article/details/25409549 导航抽屉(navigationdrawer)是一个从屏幕左边滑入的...

用C#实现实现简单的 Ping 的功能,用于测试网络是否已经联通

用C#实现实现简单的 Ping 的功能,用于测试网络是否已经联通 1. 根据IP地址获得主机名称 /// /// 根据IP地址获得主机名称 /// ...

vuejs2.0使用Sortable.js实现的拖拽功能

简介 http://www.cnblogs.com/moqiutao/p/6423754.html在使用vue1.x之前的版本的时候,页面中的拖拽功能,我在项目中是直接用的jQuery ui中的so...

PHP实现下载功能的代码

PHP实现下载功能的代码,并实现安全下载,隐藏文件真实地址等,需要的朋友可以参考下 php escapeshellcmd多字节编码漏洞  ·详细讲解PHP中缓存技术的应用  ·利用PHP V...

地图功能的简易实现

地图功能的简易实现 效果图 1️⃣.基于位置的服务介绍 基于位置的服务简称LBS,随着移动互联网的兴起,此技术近几年异常的火爆,主要的工作原理就是利用无线电通讯网络或GPS等定位...

js实现图片预览的功能

效果图: function yulan() { var fileext=document.form1.UpFile.value.substr...

java SSM框架批量上传实现

SSM对于数组对象的批量删除的实现
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)