ANDROID: ext4: add a non-reversible key derivation method
authorEric Biggers <ebiggers@google.com>
Wed, 11 Jan 2017 01:02:40 +0000 (17:02 -0800)
committerAmit Pundir <amit.pundir@linaro.org>
Mon, 10 Apr 2017 07:42:16 +0000 (13:12 +0530)
Add a new per-file key derivation method to ext4 encryption defined as:

    derived_key[0:127]   = AES-256-ENCRYPT(master_key[0:255], nonce)
    derived_key[128:255] = AES-256-ENCRYPT(master_key[0:255], nonce ^ 0x01)
    derived_key[256:383] = AES-256-ENCRYPT(master_key[256:511], nonce)
    derived_key[384:511] = AES-256-ENCRYPT(master_key[256:511], nonce ^ 0x01)

... where the derived key and master key are both 512 bits, the nonce is
128 bits, AES-256-ENCRYPT takes the arguments (key, plaintext), and
'nonce ^ 0x01' denotes flipping the low order bit of the last byte.

The existing key derivation method is
'derived_key = AES-128-ECB-ENCRYPT(key=nonce, plaintext=master_key)'.
We want to make this change because currently, given a derived key you
can easily compute the master key by computing
'AES-128-ECB-DECRYPT(key=nonce, ciphertext=derived_key)'.

This was formerly OK because the previous threat model assumed that the
master key and derived keys are equally hard to obtain by an attacker.
However, we are looking to move the master key into secure hardware in
some cases, so we want to make sure that an attacker with access to a
derived key cannot compute the master key.

We are doing this instead of increasing the nonce to 512 bits because
it's important that the per-file xattr fit in the inode itself.  By
default, inodes are 256 bytes, and on Android we're already pretty close
to that limit. If we increase the nonce size, we end up allocating a new
filesystem block for each and every encrypted file, which has a
substantial performance and disk utilization impact.

Another option considered was to use the HMAC-SHA512 of the nonce, keyed
by the master key.  However this would be a little less performant,
would be less extensible to other key sizes and MAC algorithms, and
would pull in a dependency (security-wise and code-wise) on SHA-512.

Due to the use of "aes" rather than "ecb(aes)" in the implementation,
the new key derivation method is actually about twice as fast as the old
one, though the old one could be optimized similarly as well.

This patch makes the new key derivation method be used whenever HEH is
used to encrypt filenames.  Although these two features are logically
independent, it was decided to bundle them together for now.  Note that
neither feature is upstream yet, and it cannot be guaranteed that the
on-disk format won't change if/when these features are upstreamed.  For
this reason, and as noted in the previous patch, the features are both
behind a special mode number for now.

Signed-off-by: Eric Biggers <ebiggers@google.com>
Change-Id: Iee4113f57e59dc8c0b7dc5238d7003c83defb986

fs/ext4/crypto_key.c
fs/ext4/ext4_crypto.h

index 20e7f1f502838ff43ea59110bb3db61498d72592..22096e31a720b0f4d7ab63ada8e48f1fd2cc2673 100644 (file)
@@ -29,16 +29,16 @@ static void derive_crypt_complete(struct crypto_async_request *req, int rc)
 }
 
 /**
- * ext4_derive_key_aes() - Derive a key using AES-128-ECB
+ * ext4_derive_key_v1() - Derive a key using AES-128-ECB
  * @deriving_key: Encryption key used for derivation.
  * @source_key:   Source key to which to apply derivation.
  * @derived_key:  Derived key.
  *
- * Return: Zero on success; non-zero otherwise.
+ * Return: 0 on success, -errno on failure
  */
-static int ext4_derive_key_aes(char deriving_key[EXT4_AES_128_ECB_KEY_SIZE],
-                              char source_key[EXT4_AES_256_XTS_KEY_SIZE],
-                              char derived_key[EXT4_AES_256_XTS_KEY_SIZE])
+static int ext4_derive_key_v1(const char deriving_key[EXT4_AES_128_ECB_KEY_SIZE],
+                             const char source_key[EXT4_AES_256_XTS_KEY_SIZE],
+                             char derived_key[EXT4_AES_256_XTS_KEY_SIZE])
 {
        int res = 0;
        struct ablkcipher_request *req = NULL;
@@ -83,6 +83,91 @@ out:
        return res;
 }
 
+/**
+ * ext4_derive_key_v2() - Derive a key non-reversibly
+ * @nonce: the nonce associated with the file
+ * @master_key: the master key referenced by the file
+ * @derived_key: (output) the resulting derived key
+ *
+ * This function computes the following:
+ *      derived_key[0:127]   = AES-256-ENCRYPT(master_key[0:255], nonce)
+ *      derived_key[128:255] = AES-256-ENCRYPT(master_key[0:255], nonce ^ 0x01)
+ *      derived_key[256:383] = AES-256-ENCRYPT(master_key[256:511], nonce)
+ *      derived_key[384:511] = AES-256-ENCRYPT(master_key[256:511], nonce ^ 0x01)
+ *
+ * 'nonce ^ 0x01' denotes flipping the low order bit of the last byte.
+ *
+ * Unlike the v1 algorithm, the v2 algorithm is "non-reversible", meaning that
+ * compromising a derived key does not also compromise the master key.
+ *
+ * Return: 0 on success, -errno on failure
+ */
+static int ext4_derive_key_v2(const char nonce[EXT4_KEY_DERIVATION_NONCE_SIZE],
+                             const char master_key[EXT4_MAX_KEY_SIZE],
+                             char derived_key[EXT4_MAX_KEY_SIZE])
+{
+       const int noncelen = EXT4_KEY_DERIVATION_NONCE_SIZE;
+       struct crypto_cipher *tfm;
+       int err;
+       int i;
+
+       /*
+        * Since we only use each transform for a small number of encryptions,
+        * requesting just "aes" turns out to be significantly faster than
+        * "ecb(aes)", by about a factor of two.
+        */
+       tfm = crypto_alloc_cipher("aes", 0, 0);
+       if (IS_ERR(tfm))
+               return PTR_ERR(tfm);
+
+       BUILD_BUG_ON(4 * EXT4_KEY_DERIVATION_NONCE_SIZE != EXT4_MAX_KEY_SIZE);
+       BUILD_BUG_ON(2 * EXT4_AES_256_ECB_KEY_SIZE != EXT4_MAX_KEY_SIZE);
+       for (i = 0; i < 2; i++) {
+               memcpy(derived_key, nonce, noncelen);
+               memcpy(derived_key + noncelen, nonce, noncelen);
+               derived_key[2 * noncelen - 1] ^= 0x01;
+               err = crypto_cipher_setkey(tfm, master_key,
+                                          EXT4_AES_256_ECB_KEY_SIZE);
+               if (err)
+                       break;
+               crypto_cipher_encrypt_one(tfm, derived_key, derived_key);
+               crypto_cipher_encrypt_one(tfm, derived_key + noncelen,
+                                         derived_key + noncelen);
+               master_key += EXT4_AES_256_ECB_KEY_SIZE;
+               derived_key += 2 * noncelen;
+       }
+       crypto_free_cipher(tfm);
+       return err;
+}
+
+/**
+ * ext4_derive_key() - Derive a per-file key from a nonce and master key
+ * @ctx: the encryption context associated with the file
+ * @master_key: the master key referenced by the file
+ * @derived_key: (output) the resulting derived key
+ *
+ * Return: 0 on success, -errno on failure
+ */
+static int ext4_derive_key(const struct ext4_encryption_context *ctx,
+                          const char master_key[EXT4_MAX_KEY_SIZE],
+                          char derived_key[EXT4_MAX_KEY_SIZE])
+{
+       BUILD_BUG_ON(EXT4_AES_128_ECB_KEY_SIZE != EXT4_KEY_DERIVATION_NONCE_SIZE);
+       BUILD_BUG_ON(EXT4_AES_256_XTS_KEY_SIZE != EXT4_MAX_KEY_SIZE);
+
+       /*
+        * Although the key derivation algorithm is logically independent of the
+        * choice of encryption modes, in this kernel it is bundled with HEH
+        * encryption of filenames, which is another crypto improvement that
+        * requires an on-disk format change and requires userspace to specify
+        * different encryption policies.
+        */
+       if (ctx->filenames_encryption_mode == EXT4_ENCRYPTION_MODE_AES_256_HEH)
+               return ext4_derive_key_v2(ctx->nonce, master_key, derived_key);
+       else
+               return ext4_derive_key_v1(ctx->nonce, master_key, derived_key);
+}
+
 void ext4_free_crypt_info(struct ext4_crypt_info *ci)
 {
        if (!ci)
@@ -223,8 +308,7 @@ int ext4_get_encryption_info(struct inode *inode)
                up_read(&keyring_key->sem);
                goto out;
        }
-       res = ext4_derive_key_aes(ctx.nonce, master_key->raw,
-                                 raw_key);
+       res = ext4_derive_key(&ctx, master_key->raw, raw_key);
        up_read(&keyring_key->sem);
        if (res)
                goto out;
index acdeb3e032d3a19e1273e62be34c4bda606db1ec..e52637d969db2a36154654770454bf4814b91d81 100644 (file)
@@ -58,6 +58,7 @@ struct ext4_encryption_context {
 #define EXT4_XTS_TWEAK_SIZE 16
 #define EXT4_AES_128_ECB_KEY_SIZE 16
 #define EXT4_AES_256_GCM_KEY_SIZE 32
+#define EXT4_AES_256_ECB_KEY_SIZE 32
 #define EXT4_AES_256_CBC_KEY_SIZE 32
 #define EXT4_AES_256_CTS_KEY_SIZE 32
 #define EXT4_AES_256_HEH_KEY_SIZE 32