]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
fscrypt: use HMAC-SHA512 library for HKDF
authorEric Biggers <ebiggers@kernel.org>
Sat, 6 Sep 2025 03:59:13 +0000 (20:59 -0700)
committerEric Biggers <ebiggers@kernel.org>
Sat, 6 Sep 2025 04:01:51 +0000 (21:01 -0700)
For the HKDF-SHA512 key derivation needed by fscrypt, just use the
HMAC-SHA512 library functions directly.  These functions were introduced
in v6.17, and they provide simple and efficient direct support for
HMAC-SHA512.  This ends up being quite a bit simpler and more efficient
than using crypto/hkdf.c, as it avoids the generic crypto layer:

- The HMAC library can't fail, so callers don't need to handle errors
- No inefficient indirect calls
- No inefficient and error-prone dynamic allocations
- No inefficient and error-prone loading of algorithm by name
- Less stack usage

Benchmarks on x86_64 show that deriving a per-file key gets about 30%
faster, and FS_IOC_ADD_ENCRYPTION_KEY gets nearly twice as fast.

The only small downside is the HKDF-Expand logic gets duplicated again.
Then again, even considering that, the new fscrypt_hkdf_expand() is only
7 lines longer than the version that called hkdf_expand().  Later we
could add HKDF support to lib/crypto/, but for now let's just do this.

Link: https://lore.kernel.org/r/20250906035913.1141532-1-ebiggers@kernel.org
Signed-off-by: Eric Biggers <ebiggers@kernel.org>
fs/crypto/Kconfig
fs/crypto/fname.c
fs/crypto/fscrypt_private.h
fs/crypto/hkdf.c
fs/crypto/hooks.c
fs/crypto/keyring.c
fs/crypto/keysetup.c
fs/crypto/policy.c

index b5dfb0aa405ab04c65a230cb1ab7c63ef6f837fb..464b54610fd3468321cd35cf4cbf93473bfca9f8 100644 (file)
@@ -2,10 +2,9 @@
 config FS_ENCRYPTION
        bool "FS Encryption (Per-file encryption)"
        select CRYPTO
-       select CRYPTO_HASH
-       select CRYPTO_HKDF
        select CRYPTO_SKCIPHER
        select CRYPTO_LIB_SHA256
+       select CRYPTO_LIB_SHA512
        select KEYS
        help
          Enable encryption of files and directories.  This
@@ -32,8 +31,6 @@ config FS_ENCRYPTION_ALGS
        select CRYPTO_CBC
        select CRYPTO_CTS
        select CRYPTO_ECB
-       select CRYPTO_HMAC
-       select CRYPTO_SHA512
        select CRYPTO_XTS
 
 config FS_ENCRYPTION_INLINE_CRYPT
index f9f6713e144f7a0835c8305dfadd414e285c9184..5fa1eb58bb1d54bf00aa04880c1f8123afa9a740 100644 (file)
@@ -11,7 +11,6 @@
  * This has not yet undergone a rigorous security audit.
  */
 
-#include <crypto/hash.h>
 #include <crypto/sha2.h>
 #include <crypto/skcipher.h>
 #include <linux/export.h>
index d8b485b9881c50b452ce58688370e5f0ce4f828d..3a09f45887a47a7118297ac5ef4e61915cf2ffb2 100644 (file)
 #ifndef _FSCRYPT_PRIVATE_H
 #define _FSCRYPT_PRIVATE_H
 
+#include <crypto/sha2.h>
 #include <linux/fscrypt.h>
 #include <linux/minmax.h>
 #include <linux/siphash.h>
-#include <crypto/hash.h>
 #include <linux/blk-crypto.h>
 
 #define CONST_STRLEN(str)      (sizeof(str) - 1)
@@ -381,12 +381,8 @@ bool __fscrypt_fname_encrypted_size(const union fscrypt_policy *policy,
                                    u32 *encrypted_len_ret);
 
 /* hkdf.c */
-struct fscrypt_hkdf {
-       struct crypto_shash *hmac_tfm;
-};
-
-int fscrypt_init_hkdf(struct fscrypt_hkdf *hkdf, const u8 *master_key,
-                     unsigned int master_key_size);
+void fscrypt_init_hkdf(struct hmac_sha512_key *hkdf, const u8 *master_key,
+                      unsigned int master_key_size);
 
 /*
  * The list of contexts in which fscrypt uses HKDF.  These values are used as
@@ -405,11 +401,9 @@ int fscrypt_init_hkdf(struct fscrypt_hkdf *hkdf, const u8 *master_key,
 #define HKDF_CONTEXT_KEY_IDENTIFIER_FOR_HW_WRAPPED_KEY \
                                        8 /* info=<empty>               */
 
-int fscrypt_hkdf_expand(const struct fscrypt_hkdf *hkdf, u8 context,
-                       const u8 *info, unsigned int infolen,
-                       u8 *okm, unsigned int okmlen);
-
-void fscrypt_destroy_hkdf(struct fscrypt_hkdf *hkdf);
+void fscrypt_hkdf_expand(const struct hmac_sha512_key *hkdf, u8 context,
+                        const u8 *info, unsigned int infolen,
+                        u8 *okm, unsigned int okmlen);
 
 /* inline_crypt.c */
 #ifdef CONFIG_FS_ENCRYPTION_INLINE_CRYPT
@@ -517,7 +511,7 @@ struct fscrypt_master_key_secret {
         * ->is_hw_wrapped=false, or by the "software secret" that hardware
         * derived from this master key if ->is_hw_wrapped=true.
         */
-       struct fscrypt_hkdf     hkdf;
+       struct hmac_sha512_key  hkdf;
 
        /*
         * True if this key is a hardware-wrapped key; false if this key is a
@@ -696,7 +690,7 @@ struct fscrypt_master_key *
 fscrypt_find_master_key(struct super_block *sb,
                        const struct fscrypt_key_specifier *mk_spec);
 
-int fscrypt_get_test_dummy_key_identifier(
+void fscrypt_get_test_dummy_key_identifier(
                          u8 key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE]);
 
 int fscrypt_add_test_dummy_key(struct super_block *sb,
@@ -732,8 +726,8 @@ void fscrypt_destroy_prepared_key(struct super_block *sb,
 int fscrypt_set_per_file_enc_key(struct fscrypt_inode_info *ci,
                                 const u8 *raw_key);
 
-int fscrypt_derive_dirhash_key(struct fscrypt_inode_info *ci,
-                              const struct fscrypt_master_key *mk);
+void fscrypt_derive_dirhash_key(struct fscrypt_inode_info *ci,
+                               const struct fscrypt_master_key *mk);
 
 void fscrypt_hash_inode_number(struct fscrypt_inode_info *ci,
                               const struct fscrypt_master_key *mk);
index b1ef506cd341dedbc694c111fd3eb98708849a3c..706f56d0076eed33fb046df2baae5500184218a3 100644 (file)
@@ -1,5 +1,9 @@
 // SPDX-License-Identifier: GPL-2.0
 /*
+ * Implementation of HKDF ("HMAC-based Extract-and-Expand Key Derivation
+ * Function"), aka RFC 5869.  See also the original paper (Krawczyk 2010):
+ * "Cryptographic Extraction and Key Derivation: The HKDF Scheme".
+ *
  * This is used to derive keys from the fscrypt master keys (or from the
  * "software secrets" which hardware derives from the fscrypt master keys, in
  * the case that the fscrypt master keys are hardware-wrapped keys).
@@ -7,10 +11,6 @@
  * Copyright 2019 Google LLC
  */
 
-#include <crypto/hash.h>
-#include <crypto/hkdf.h>
-#include <crypto/sha2.h>
-
 #include "fscrypt_private.h"
 
 /*
@@ -24,7 +24,6 @@
  * HKDF-SHA512 being much faster than HKDF-SHA256, as the longer digest size of
  * SHA-512 causes HKDF-Expand to only need to do one iteration rather than two.
  */
-#define HKDF_HMAC_ALG          "hmac(sha512)"
 #define HKDF_HASHLEN           SHA512_DIGEST_SIZE
 
 /*
  */
 
 /*
- * Compute HKDF-Extract using the given master key as the input keying material,
- * and prepare an HMAC transform object keyed by the resulting pseudorandom key.
- *
- * Afterwards, the keyed HMAC transform object can be used for HKDF-Expand many
- * times without having to recompute HKDF-Extract each time.
+ * Compute HKDF-Extract using 'master_key' as the input keying material, and
+ * prepare the resulting HMAC key in 'hkdf'.  Afterwards, 'hkdf' can be used for
+ * HKDF-Expand many times without having to recompute HKDF-Extract each time.
  */
-int fscrypt_init_hkdf(struct fscrypt_hkdf *hkdf, const u8 *master_key,
-                     unsigned int master_key_size)
+void fscrypt_init_hkdf(struct hmac_sha512_key *hkdf, const u8 *master_key,
+                      unsigned int master_key_size)
 {
-       struct crypto_shash *hmac_tfm;
        static const u8 default_salt[HKDF_HASHLEN];
        u8 prk[HKDF_HASHLEN];
-       int err;
-
-       hmac_tfm = crypto_alloc_shash(HKDF_HMAC_ALG, 0, FSCRYPT_CRYPTOAPI_MASK);
-       if (IS_ERR(hmac_tfm)) {
-               fscrypt_err(NULL, "Error allocating " HKDF_HMAC_ALG ": %ld",
-                           PTR_ERR(hmac_tfm));
-               return PTR_ERR(hmac_tfm);
-       }
-
-       if (WARN_ON_ONCE(crypto_shash_digestsize(hmac_tfm) != sizeof(prk))) {
-               err = -EINVAL;
-               goto err_free_tfm;
-       }
-
-       err = hkdf_extract(hmac_tfm, master_key, master_key_size,
-                          default_salt, HKDF_HASHLEN, prk);
-       if (err)
-               goto err_free_tfm;
-
-       err = crypto_shash_setkey(hmac_tfm, prk, sizeof(prk));
-       if (err)
-               goto err_free_tfm;
 
-       hkdf->hmac_tfm = hmac_tfm;
-       goto out;
-
-err_free_tfm:
-       crypto_free_shash(hmac_tfm);
-out:
+       hmac_sha512_usingrawkey(default_salt, sizeof(default_salt),
+                               master_key, master_key_size, prk);
+       hmac_sha512_preparekey(hkdf, prk, sizeof(prk));
        memzero_explicit(prk, sizeof(prk));
-       return err;
 }
 
 /*
- * HKDF-Expand (RFC 5869 section 2.3).  This expands the pseudorandom key, which
- * was already keyed into 'hkdf->hmac_tfm' by fscrypt_init_hkdf(), into 'okmlen'
+ * HKDF-Expand (RFC 5869 section 2.3).  Expand the HMAC key 'hkdf' into 'okmlen'
  * bytes of output keying material parameterized by the application-specific
  * 'info' of length 'infolen' bytes, prefixed by "fscrypt\0" and the 'context'
  * byte.  This is thread-safe and may be called by multiple threads in parallel.
@@ -100,30 +69,32 @@ out:
  * adds to its application-specific info strings to guarantee that it doesn't
  * accidentally repeat an info string when using HKDF for different purposes.)
  */
-int fscrypt_hkdf_expand(const struct fscrypt_hkdf *hkdf, u8 context,
-                       const u8 *info, unsigned int infolen,
-                       u8 *okm, unsigned int okmlen)
-{
-       SHASH_DESC_ON_STACK(desc, hkdf->hmac_tfm);
-       u8 *full_info;
-       int err;
-
-       full_info = kzalloc(infolen + 9, GFP_KERNEL);
-       if (!full_info)
-               return -ENOMEM;
-       desc->tfm = hkdf->hmac_tfm;
-
-       memcpy(full_info, "fscrypt\0", 8);
-       full_info[8] = context;
-       memcpy(full_info + 9, info, infolen);
-
-       err = hkdf_expand(hkdf->hmac_tfm, full_info, infolen + 9,
-                         okm, okmlen);
-       kfree_sensitive(full_info);
-       return err;
-}
-
-void fscrypt_destroy_hkdf(struct fscrypt_hkdf *hkdf)
+void fscrypt_hkdf_expand(const struct hmac_sha512_key *hkdf, u8 context,
+                        const u8 *info, unsigned int infolen,
+                        u8 *okm, unsigned int okmlen)
 {
-       crypto_free_shash(hkdf->hmac_tfm);
+       struct hmac_sha512_ctx ctx;
+       u8 counter = 1;
+       u8 tmp[HKDF_HASHLEN];
+
+       WARN_ON_ONCE(okmlen > 255 * HKDF_HASHLEN);
+
+       for (unsigned int i = 0; i < okmlen; i += HKDF_HASHLEN) {
+               hmac_sha512_init(&ctx, hkdf);
+               if (i != 0)
+                       hmac_sha512_update(&ctx, &okm[i - HKDF_HASHLEN],
+                                          HKDF_HASHLEN);
+               hmac_sha512_update(&ctx, "fscrypt\0", 8);
+               hmac_sha512_update(&ctx, &context, 1);
+               hmac_sha512_update(&ctx, info, infolen);
+               hmac_sha512_update(&ctx, &counter, 1);
+               if (okmlen - i < HKDF_HASHLEN) {
+                       hmac_sha512_final(&ctx, tmp);
+                       memcpy(&okm[i], tmp, okmlen - i);
+                       memzero_explicit(tmp, sizeof(tmp));
+               } else {
+                       hmac_sha512_final(&ctx, &okm[i]);
+               }
+               counter++;
+       }
 }
index e0b32ac841f765e8389f8b808da56a838b011c8d..0fb3496551b9419d7b92f5031aa45883f86d09de 100644 (file)
@@ -205,7 +205,7 @@ int fscrypt_prepare_setflags(struct inode *inode,
                mk = ci->ci_master_key;
                down_read(&mk->mk_sem);
                if (mk->mk_present)
-                       err = fscrypt_derive_dirhash_key(ci, mk);
+                       fscrypt_derive_dirhash_key(ci, mk);
                else
                        err = -ENOKEY;
                up_read(&mk->mk_sem);
index 7557f6a88b8f329dfc7e57eb381933e3ba89c4e5..3adbd7167055a9ca171c535bc8359f8706cb6da4 100644 (file)
@@ -42,7 +42,6 @@ struct fscrypt_keyring {
 
 static void wipe_master_key_secret(struct fscrypt_master_key_secret *secret)
 {
-       fscrypt_destroy_hkdf(&secret->hkdf);
        memzero_explicit(secret, sizeof(*secret));
 }
 
@@ -587,21 +586,17 @@ static int add_master_key(struct super_block *sb,
                        keyid_kdf_ctx =
                                HKDF_CONTEXT_KEY_IDENTIFIER_FOR_HW_WRAPPED_KEY;
                }
-               err = fscrypt_init_hkdf(&secret->hkdf, kdf_key, kdf_key_size);
+               fscrypt_init_hkdf(&secret->hkdf, kdf_key, kdf_key_size);
                /*
                 * Now that the KDF context is initialized, the raw KDF key is
                 * no longer needed.
                 */
                memzero_explicit(kdf_key, kdf_key_size);
-               if (err)
-                       return err;
 
                /* Calculate the key identifier */
-               err = fscrypt_hkdf_expand(&secret->hkdf, keyid_kdf_ctx, NULL, 0,
-                                         key_spec->u.identifier,
-                                         FSCRYPT_KEY_IDENTIFIER_SIZE);
-               if (err)
-                       return err;
+               fscrypt_hkdf_expand(&secret->hkdf, keyid_kdf_ctx, NULL, 0,
+                                   key_spec->u.identifier,
+                                   FSCRYPT_KEY_IDENTIFIER_SIZE);
        }
        return do_add_master_key(sb, secret, key_spec);
 }
@@ -835,24 +830,17 @@ fscrypt_get_test_dummy_secret(struct fscrypt_master_key_secret *secret)
        memcpy(secret->bytes, test_key, sizeof(test_key));
 }
 
-int fscrypt_get_test_dummy_key_identifier(
+void fscrypt_get_test_dummy_key_identifier(
                                u8 key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE])
 {
        struct fscrypt_master_key_secret secret;
-       int err;
 
        fscrypt_get_test_dummy_secret(&secret);
-
-       err = fscrypt_init_hkdf(&secret.hkdf, secret.bytes, secret.size);
-       if (err)
-               goto out;
-       err = fscrypt_hkdf_expand(&secret.hkdf,
-                                 HKDF_CONTEXT_KEY_IDENTIFIER_FOR_RAW_KEY,
-                                 NULL, 0, key_identifier,
-                                 FSCRYPT_KEY_IDENTIFIER_SIZE);
-out:
+       fscrypt_init_hkdf(&secret.hkdf, secret.bytes, secret.size);
+       fscrypt_hkdf_expand(&secret.hkdf,
+                           HKDF_CONTEXT_KEY_IDENTIFIER_FOR_RAW_KEY, NULL, 0,
+                           key_identifier, FSCRYPT_KEY_IDENTIFIER_SIZE);
        wipe_master_key_secret(&secret);
-       return err;
 }
 
 /**
index 4f3b9ecbfe4e66415c8ee58128262ccf80238143..a3ed96eaebc8572fac40a224763eeaf459311ceb 100644 (file)
@@ -253,11 +253,8 @@ static int setup_per_mode_enc_key(struct fscrypt_inode_info *ci,
                       sizeof(sb->s_uuid));
                hkdf_infolen += sizeof(sb->s_uuid);
        }
-       err = fscrypt_hkdf_expand(&mk->mk_secret.hkdf,
-                                 hkdf_context, hkdf_info, hkdf_infolen,
-                                 mode_key, mode->keysize);
-       if (err)
-               goto out_unlock;
+       fscrypt_hkdf_expand(&mk->mk_secret.hkdf, hkdf_context, hkdf_info,
+                           hkdf_infolen, mode_key, mode->keysize);
        err = fscrypt_prepare_key(prep_key, mode_key, ci);
        memzero_explicit(mode_key, mode->keysize);
        if (err)
@@ -278,36 +275,25 @@ out_unlock:
  * as a pair of 64-bit words.  Therefore, on big endian CPUs we have to do an
  * endianness swap in order to get the same results as on little endian CPUs.
  */
-static int fscrypt_derive_siphash_key(const struct fscrypt_master_key *mk,
-                                     u8 context, const u8 *info,
-                                     unsigned int infolen, siphash_key_t *key)
+static void fscrypt_derive_siphash_key(const struct fscrypt_master_key *mk,
+                                      u8 context, const u8 *info,
+                                      unsigned int infolen, siphash_key_t *key)
 {
-       int err;
-
-       err = fscrypt_hkdf_expand(&mk->mk_secret.hkdf, context, info, infolen,
-                                 (u8 *)key, sizeof(*key));
-       if (err)
-               return err;
-
+       fscrypt_hkdf_expand(&mk->mk_secret.hkdf, context, info, infolen,
+                           (u8 *)key, sizeof(*key));
        BUILD_BUG_ON(sizeof(*key) != 16);
        BUILD_BUG_ON(ARRAY_SIZE(key->key) != 2);
        le64_to_cpus(&key->key[0]);
        le64_to_cpus(&key->key[1]);
-       return 0;
 }
 
-int fscrypt_derive_dirhash_key(struct fscrypt_inode_info *ci,
-                              const struct fscrypt_master_key *mk)
+void fscrypt_derive_dirhash_key(struct fscrypt_inode_info *ci,
+                               const struct fscrypt_master_key *mk)
 {
-       int err;
-
-       err = fscrypt_derive_siphash_key(mk, HKDF_CONTEXT_DIRHASH_KEY,
-                                        ci->ci_nonce, FSCRYPT_FILE_NONCE_SIZE,
-                                        &ci->ci_dirhash_key);
-       if (err)
-               return err;
+       fscrypt_derive_siphash_key(mk, HKDF_CONTEXT_DIRHASH_KEY,
+                                  ci->ci_nonce, FSCRYPT_FILE_NONCE_SIZE,
+                                  &ci->ci_dirhash_key);
        ci->ci_dirhash_key_initialized = true;
-       return 0;
 }
 
 void fscrypt_hash_inode_number(struct fscrypt_inode_info *ci,
@@ -338,17 +324,12 @@ static int fscrypt_setup_iv_ino_lblk_32_key(struct fscrypt_inode_info *ci,
                if (mk->mk_ino_hash_key_initialized)
                        goto unlock;
 
-               err = fscrypt_derive_siphash_key(mk,
-                                                HKDF_CONTEXT_INODE_HASH_KEY,
-                                                NULL, 0, &mk->mk_ino_hash_key);
-               if (err)
-                       goto unlock;
+               fscrypt_derive_siphash_key(mk, HKDF_CONTEXT_INODE_HASH_KEY,
+                                          NULL, 0, &mk->mk_ino_hash_key);
                /* pairs with smp_load_acquire() above */
                smp_store_release(&mk->mk_ino_hash_key_initialized, true);
 unlock:
                mutex_unlock(&fscrypt_mode_key_setup_mutex);
-               if (err)
-                       return err;
        }
 
        /*
@@ -402,13 +383,10 @@ static int fscrypt_setup_v2_file_key(struct fscrypt_inode_info *ci,
        } else {
                u8 derived_key[FSCRYPT_MAX_RAW_KEY_SIZE];
 
-               err = fscrypt_hkdf_expand(&mk->mk_secret.hkdf,
-                                         HKDF_CONTEXT_PER_FILE_ENC_KEY,
-                                         ci->ci_nonce, FSCRYPT_FILE_NONCE_SIZE,
-                                         derived_key, ci->ci_mode->keysize);
-               if (err)
-                       return err;
-
+               fscrypt_hkdf_expand(&mk->mk_secret.hkdf,
+                                   HKDF_CONTEXT_PER_FILE_ENC_KEY,
+                                   ci->ci_nonce, FSCRYPT_FILE_NONCE_SIZE,
+                                   derived_key, ci->ci_mode->keysize);
                err = fscrypt_set_per_file_enc_key(ci, derived_key);
                memzero_explicit(derived_key, ci->ci_mode->keysize);
        }
@@ -416,11 +394,8 @@ static int fscrypt_setup_v2_file_key(struct fscrypt_inode_info *ci,
                return err;
 
        /* Derive a secret dirhash key for directories that need it. */
-       if (need_dirhash_key) {
-               err = fscrypt_derive_dirhash_key(ci, mk);
-               if (err)
-                       return err;
-       }
+       if (need_dirhash_key)
+               fscrypt_derive_dirhash_key(ci, mk);
 
        return 0;
 }
index 6ad30ae07c065cfa05afe9933f908fa577d46fdc..cf38b36d7bdfdaa32747fd49bd32098f905489d0 100644 (file)
@@ -826,10 +826,8 @@ int fscrypt_parse_test_dummy_encryption(const struct fs_parameter *param,
                policy->version = FSCRYPT_POLICY_V2;
                policy->v2.contents_encryption_mode = FSCRYPT_MODE_AES_256_XTS;
                policy->v2.filenames_encryption_mode = FSCRYPT_MODE_AES_256_CTS;
-               err = fscrypt_get_test_dummy_key_identifier(
+               fscrypt_get_test_dummy_key_identifier(
                                policy->v2.master_key_identifier);
-               if (err)
-                       goto out;
        } else {
                err = -EINVAL;
                goto out;