]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
repart: add EncryptKDF= option for LUKS2 partitions
authorNandakumar Raghavan <naraghavan@microsoft.com>
Mon, 6 Apr 2026 10:42:51 +0000 (10:42 +0000)
committerLuca Boccassi <luca.boccassi@gmail.com>
Sat, 18 Apr 2026 10:03:52 +0000 (11:03 +0100)
systemd-repart currently creates LUKS2 encrypted partitions using
libcryptsetup's default KDF (Argon2id), which requires ~1GB of memory
during key derivation. This is too much for memory-constrained
environments such as kdump with limited crashkernel memory, where
luksOpen fails due to insufficient memory.

Add an EncryptKDF= option to repart.d partition definitions that allows
selecting the KDF type. Supported values are:

- "argon2id" — Argon2id with libcryptsetup-benchmarked parameters
- "pbkdf2"  — PBKDF2 with libcryptsetup-benchmarked parameters
- "minimal" — PBKDF2 with SHA-512, 1000 iterations, no benchmarking,
  matching the existing cryptsetup_set_minimal_pbkdf() behaviour used
  for TPM2-sealed keys

When not specified, the libcryptsetup default (argon2id) is used,
preserving existing behaviour.

The KDF type is applied via sym_crypt_set_pbkdf_type() after
sym_crypt_format() and before any keyslots are added.

man/repart.d.xml
src/repart/repart.c

index 217692d8135514697c2dd38b332b7a671d424503..028929ac9e842915bd219cb1575f853c5b47833e 100644 (file)
         <xi:include href="version-info.xml" xpointer="v261"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>EncryptKDF=</varname></term>
+
+        <listitem><para>Specifies the key derivation function (KDF) to use for LUKS2 encryption keyslots.
+        Takes one of <literal>argon2id</literal>, <literal>pbkdf2</literal>, or <literal>minimal</literal>.
+        If not specified, the default is determined by the cryptsetup library (typically
+        <literal>argon2id</literal>). This option has no effect if <varname>Encrypt=</varname> is
+        <literal>off</literal>.</para>
+
+        <para>When set to <literal>argon2id</literal> or <literal>pbkdf2</literal>, the specified KDF is
+        used with parameters benchmarked by the cryptsetup library. When set to <literal>minimal</literal>,
+        PBKDF2 is used with SHA-512, 1000 iterations, and no benchmarking — this is appropriate for
+        high-entropy keys (e.g. generated by a hardware key manager or sealed to a TPM) where the KDF only
+        needs to satisfy the LUKS2 format requirement, not strengthen a weak passphrase.</para>
+
+        <para>Note that Argon2-based KDFs may require significant memory (up to 1GB) during key derivation.
+        In memory-constrained environments such as kdump with limited crashkernel memory,
+        <literal>minimal</literal> or <literal>pbkdf2</literal> may be more appropriate. When
+        <varname>Encrypt=</varname> includes <literal>tpm2</literal>, the TPM2 keyslot always uses a minimal
+        PBKDF2 configuration regardless of this setting.</para>
+
+        <xi:include href="version-info.xml" xpointer="v261"/></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>Verity=</varname></term>
 
index b0b92ba2ae0753c59694f7a975efd0a3aecdf540..b8443fab1951ac9b3b9590f86028894214716e1f 100644 (file)
@@ -290,6 +290,14 @@ typedef enum IntegrityAlg {
         _INTEGRITY_ALG_INVALID = -EINVAL,
 } IntegrityAlg;
 
+typedef enum EncryptKDF {
+        ENCRYPT_KDF_ARGON2ID,
+        ENCRYPT_KDF_PBKDF2,
+        ENCRYPT_KDF_MINIMAL,
+        _ENCRYPT_KDF_MAX,
+        _ENCRYPT_KDF_INVALID = -EINVAL,
+} EncryptKDF;
+
 typedef enum VerityMode {
         VERITY_OFF,
         VERITY_DATA,
@@ -472,6 +480,7 @@ typedef struct Partition {
         size_t tpm2_n_hash_pcr_values;
         IntegrityMode integrity;
         IntegrityAlg integrity_alg;
+        EncryptKDF encrypt_kdf;
         VerityMode verity;
         char *verity_match_key;
         MinimizeMode minimize;
@@ -598,6 +607,12 @@ static const char *integrity_alg_table[_INTEGRITY_ALG_MAX] = {
         [INTEGRITY_ALG_HMAC_SHA512] = "hmac-sha512",
 };
 
+static const char *encrypt_kdf_table[_ENCRYPT_KDF_MAX] = {
+        [ENCRYPT_KDF_ARGON2ID] = "argon2id",
+        [ENCRYPT_KDF_PBKDF2]   = "pbkdf2",
+        [ENCRYPT_KDF_MINIMAL]  = "minimal",
+};
+
 static const char *verity_mode_table[_VERITY_MODE_MAX] = {
         [VERITY_OFF]  = "off",
         [VERITY_DATA] = "data",
@@ -636,6 +651,7 @@ DEFINE_PRIVATE_STRING_TABLE_LOOKUP(append_mode, AppendMode);
 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(encrypt_mode, EncryptMode, ENCRYPT_KEY_FILE);
 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(integrity_mode, IntegrityMode, INTEGRITY_INLINE);
 DEFINE_PRIVATE_STRING_TABLE_LOOKUP(integrity_alg, IntegrityAlg);
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(encrypt_kdf, EncryptKDF);
 DEFINE_PRIVATE_STRING_TABLE_LOOKUP(verity_mode, VerityMode);
 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(minimize_mode, MinimizeMode, MINIMIZE_BEST);
 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(progress_phase, ProgressPhase);
@@ -738,6 +754,7 @@ static Partition *partition_new(Context *c) {
                 .progress_ratelimit = { 100 * USEC_PER_MSEC, 1 },
                 .fs_sector_size = UINT64_MAX,
                 .discard = -1,
+                .encrypt_kdf = _ENCRYPT_KDF_INVALID,
         };
 
         return p;
@@ -2735,6 +2752,7 @@ static int config_parse_key_file(
 
 static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_integrity, integrity_mode, IntegrityMode, INTEGRITY_OFF);
 static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_integrity_alg, integrity_alg, IntegrityAlg, INTEGRITY_ALG_HMAC_SHA256);
+static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_encrypt_kdf, encrypt_kdf, EncryptKDF, _ENCRYPT_KDF_INVALID);
 
 static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_verity, verity_mode, VerityMode, VERITY_OFF);
 static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_minimize, minimize_mode, MinimizeMode, MINIMIZE_OFF);
@@ -2892,6 +2910,7 @@ static int partition_read_definition(
                 { "Partition", "KeyFile",                  config_parse_key_file,          0,                                  p                           },
                 { "Partition", "Integrity",                config_parse_integrity,         0,                                  &p->integrity               },
                 { "Partition", "IntegrityAlgorithm",       config_parse_integrity_alg,     0,                                  &p->integrity_alg           },
+                { "Partition", "EncryptKDF",               config_parse_encrypt_kdf,       0,                                  &p->encrypt_kdf             },
                 { "Partition", "Compression",              config_parse_string,            CONFIG_PARSE_STRING_SAFE_AND_ASCII, &p->compression             },
                 { "Partition", "CompressionLevel",         config_parse_string,            CONFIG_PARSE_STRING_SAFE_AND_ASCII, &p->compression_level       },
                 { "Partition", "SupplementFor",            config_parse_string,            0,                                  &p->supplement_for_name     },
@@ -3066,6 +3085,10 @@ static int partition_read_definition(
                 log_syntax(NULL, LOG_WARNING, path, 1, 0,
                            "Discard=yes has no effect with Encrypt=off.");
 
+        if (p->encrypt == ENCRYPT_OFF && p->encrypt_kdf >= 0)
+                log_syntax(NULL, LOG_WARNING, path, 1, 0,
+                           "EncryptKDF= has no effect with Encrypt=off.");
+
         if (p->encrypt != ENCRYPT_OFF && p->integrity == INTEGRITY_INLINE && p->discard > 0)
                 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
                                   "Integrity=inline is incompatible with Discard=yes.");
@@ -5266,6 +5289,30 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta
         if (r < 0)
                 return log_error_errno(r, "Failed to LUKS2 format future partition: %m");
 
+        /* If an explicit KDF is configured, apply it before adding any keyslots. */
+        if (p->encrypt_kdf >= 0) {
+                if (p->encrypt_kdf == ENCRYPT_KDF_MINIMAL) {
+                        /* Minimal PBKDF2 with sha512, 1000 iterations, no benchmarking — appropriate
+                         * for high-entropy keys where the KDF only satisfies the LUKS2 format requirement
+                         * (e.g. kdump with crashkernel=512MB). */
+                        r = cryptsetup_set_minimal_pbkdf(cd);
+                } else {
+                        /* For argon2id or pbkdf2, set the type and let libcryptsetup benchmark
+                         * and determine the parameters. */
+                        static const struct crypt_pbkdf_type argon2id_pbkdf = {
+                                .type = "argon2id",
+                        };
+                        static const struct crypt_pbkdf_type pbkdf2_pbkdf = {
+                                .type = "pbkdf2",
+                        };
+
+                        r = sym_crypt_set_pbkdf_type(cd, p->encrypt_kdf == ENCRYPT_KDF_ARGON2ID ?
+                                                     &argon2id_pbkdf : &pbkdf2_pbkdf);
+                }
+                if (r < 0)
+                        return log_error_errno(r, "Failed to set KDF type: %m");
+        }
+
         bool allow_discards = p->integrity != INTEGRITY_INLINE && (arg_discard ? p->discard != 0 : p->discard > 0);
         if (allow_discards) {
                 uint32_t flags;