Skip to content

HMAC/HKDF-SHA512 support basics #2299

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions boot/bootutil/include/bootutil/enc_key_public.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,19 @@ extern "C" {
# define BOOT_ENC_KEY_SIZE 16
#endif

#ifdef MCUBOOT_HMAC_SHA512
# define BOOT_HMAC_SIZE 64
#else
# define BOOT_HMAC_SIZE 32
#endif

#if defined(MCUBOOT_ENCRYPT_RSA)
# define BOOT_ENC_TLV_SIZE (256)
# define BOOT_ENC_TLV IMAGE_TLV_ENC_RSA2048
#elif defined(MCUBOOT_ENCRYPT_EC256)
# if defined(MCUBOOT_HMAC_SHA512)
# error "ECIES-P256 does not support HMAC-SHA512"
# endif
# define EC_PUBK_LEN (65)
# define EC_PRIVK_LEN (32)
# define EC_SHARED_LEN (32)
Expand All @@ -82,7 +91,11 @@ extern "C" {
# define EC_PUBK_LEN (32)
# define EC_PRIVK_LEN (32)
# define EC_SHARED_LEN (32)
# define BOOT_ENC_TLV IMAGE_TLV_ENC_X25519
# if !defined(MCUBOOT_HMAC_SHA512)
# define BOOT_ENC_TLV IMAGE_TLV_ENC_X25519
# else
# define BOOT_ENC_TLV IMAGE_TLV_ENC_X25519_SHA512
# endif
#elif defined(MCUBOOT_ENCRYPT_KW)
# define BOOT_ENC_TLV_SIZE (BOOT_ENC_KEY_SIZE + 8)
# define BOOT_ENC_TLV IMAGE_TLV_ENC_KW
Expand All @@ -91,7 +104,7 @@ extern "C" {
/* Common ECIES definitions */
#if defined(EC_PUBK_LEN)
# define EC_PUBK_INDEX (0)
# define EC_TAG_LEN (32)
# define EC_TAG_LEN (BOOT_HMAC_SIZE)
# define EC_TAG_INDEX (EC_PUBK_INDEX + EC_PUBK_LEN)
# define EC_CIPHERKEY_INDEX (EC_TAG_INDEX + EC_TAG_LEN)
# define EC_CIPHERKEY_LEN BOOT_ENC_KEY_SIZE
Expand Down
3 changes: 3 additions & 0 deletions boot/bootutil/include/bootutil/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ extern "C" {
#define IMAGE_TLV_ENC_KW 0x31 /* Key encrypted with AES-KW 128 or 256*/
#define IMAGE_TLV_ENC_EC256 0x32 /* Key encrypted with ECIES-EC256 */
#define IMAGE_TLV_ENC_X25519 0x33 /* Key encrypted with ECIES-X25519 */
#define IMAGE_TLV_ENC_X25519_SHA512 0x34 /* Key exchange using ECIES-X25519 and SHA512 for MAC
* tag and HKDF in key derivation process
*/
#define IMAGE_TLV_DEPENDENCY 0x40 /* Image depends on other image */
#define IMAGE_TLV_SEC_CNT 0x50 /* security counter */
#define IMAGE_TLV_BOOT_RECORD 0x60 /* measured boot record */
Expand Down
12 changes: 9 additions & 3 deletions boot/bootutil/src/encrypted_psa.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@

BOOT_LOG_MODULE_DECLARE(mcuboot_psa_enc);

#if defined(MCUBOOT_HMAC_SHA512)
#define PSA_HMAC_HKDF_SHA PSA_ALG_SHA_512
#else
#define PSA_HMAC_HKDF_SHA PSA_ALG_SHA_256
#endif

#define X25519_OID "\x6e"
static const uint8_t ec_pubkey_oid[] = MBEDTLS_OID_ISO_IDENTIFIED_ORG \
MBEDTLS_OID_ORG_GOV X25519_OID;
Expand Down Expand Up @@ -162,7 +168,7 @@ boot_decrypt_key(const uint8_t *buf, uint8_t *enckey)
return -1;
}

key_do_alg = PSA_ALG_KEY_AGREEMENT(PSA_ALG_ECDH, PSA_ALG_HKDF(PSA_ALG_SHA_256));
key_do_alg = PSA_ALG_KEY_AGREEMENT(PSA_ALG_ECDH, PSA_ALG_HKDF(PSA_HMAC_HKDF_SHA));

psa_ret = psa_key_derivation_setup(&key_do, key_do_alg);
if (psa_ret != PSA_SUCCESS) {
Expand Down Expand Up @@ -225,7 +231,7 @@ boot_decrypt_key(const uint8_t *buf, uint8_t *enckey)
*/
psa_set_key_type(&kattr, PSA_KEY_TYPE_HMAC);
psa_set_key_usage_flags(&kattr, PSA_KEY_USAGE_VERIFY_MESSAGE);
psa_set_key_algorithm(&kattr, PSA_ALG_HMAC(PSA_ALG_SHA_256));
psa_set_key_algorithm(&kattr, PSA_ALG_HMAC(PSA_HMAC_HKDF_SHA));

/* Import the MAC tag key part of derived key */
psa_ret = psa_import_key(&kattr,
Expand All @@ -239,7 +245,7 @@ boot_decrypt_key(const uint8_t *buf, uint8_t *enckey)
}

/* Verify the MAC tag of the random encryption key */
psa_ret = psa_mac_verify(kid, PSA_ALG_HMAC(PSA_ALG_SHA_256),
psa_ret = psa_mac_verify(kid, PSA_ALG_HMAC(PSA_HMAC_HKDF_SHA),
&buf[EC_CIPHERKEY_INDEX], EC_CIPHERKEY_LEN,
&buf[EC_TAG_INDEX],
EC_TAG_LEN);
Expand Down
4 changes: 4 additions & 0 deletions boot/bootutil/src/image_validate.c
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,11 @@ static const uint16_t allowed_unprot_tlvs[] = {
IMAGE_TLV_ENC_RSA2048,
IMAGE_TLV_ENC_KW,
IMAGE_TLV_ENC_EC256,
#if !defined(MCUBOOT_HMAC_SHA512)
IMAGE_TLV_ENC_X25519,
#else
IMAGE_TLV_ENC_X25519_SHA512,
#endif
/* Mark end with ANY. */
IMAGE_TLV_ANY,
};
Expand Down
9 changes: 9 additions & 0 deletions boot/zephyr/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,15 @@ config BOOT_ENCRYPT_X25519
help
Hidden option selecting x25519 encryption.

config BOOT_HMAC_SHA512
bool "Use SHA512 for HMAC/HKDF"
depends on BOOT_ENCRYPT_X25519
depends on BOOT_USE_PSA_CRYPTO
help
By default SHA256 is used for HKDF/HMAC in key exchange expansion
and verification. This options switches to SHA512. The option is
mainly useful to reduce numer of compiled in SHA algorithms.

config BOOT_ENCRYPTION_KEY_FILE
string "Encryption key file"
depends on BOOT_ENCRYPT_IMAGE
Expand Down
7 changes: 7 additions & 0 deletions boot/zephyr/include/mcuboot_config/mcuboot_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@
#define MCUBOOT_ENCRYPT_X25519
#endif

/* Support for HMAC/HKDF using SHA512; this is used in key exchange where
* HKDF is used for key expansion and HMAC is used for key verification.
*/
#ifdef CONFIG_BOOT_HMAC_SHA512
#define MCUBOOT_HMAC_SHA512
#endif

#ifdef CONFIG_BOOT_DECOMPRESSION
#define MCUBOOT_DECOMPRESS_IMAGES
#endif
Expand Down
21 changes: 15 additions & 6 deletions docs/encrypted_images.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,24 +92,33 @@ libraries. The whole key encryption can be summarized as:
keypair. Those keys will be our ephemeral keys.
* Generate a new secret (DH) using the ephemeral private key and the public key
that corresponds to the private key embedded in the HW.
* Derive the new keys from the secret using HKDF (built on HMAC-SHA256). We
are not using a `salt` and using an `info` of `MCUBoot_ECIES_v1`, generating
48 bytes of key material.
* Derive the new keys from the secret using HKDF. We are not using a `salt`
and using an `info` of `MCUBoot_ECIES_v1`, generating 48 bytes of key material.
* A new random encryption key is generated (for AES). This is
the AES key used to encrypt the images.
* The key is encrypted with AES-128-CTR or AES-256-CTR and a `nonce` of 0 using
the first 16 bytes of key material generated previously by the HKDF.
* The encrypted key now goes through a HMAC-SHA256 using the remaining 32
* The encrypted key now goes through a HMAC using the remaining 32
bytes of key material from the HKDF.

There are different TLVs for ECIES-P256, ECIES-X25519 with SHA256 HKDF/HMAC
and ECIES-X25519 with SHA512 HKDF/HMAC.
The final TLV is built from the 65 bytes for ECIES-P256 or 32 bytes for
ECIES-X25519, which correspond to the ephemeral public key, followed by the
32 bytes of MAC tag and the 16 or 32 bytes of the encrypted key, resulting in
a TLV of 113 or 129 bytes for ECIES-P256 and 80 or 96 bytes for ECIES-X25519.
MAC tag and the 16 or 32 bytes of the encrypted key, resulting in final TLV
length:
* ECIES-P256 has TLV length 113 to 129 bytes, depending on AES key length.
* ECIES-X25519 on SHA256 TLV length is 80 or 96 bytes, depending on AES key
length.
* ECIES-X25519 on SHA512 TLV length is 112 or 128, depending on AES key
length.

The implemenation of ECIES-P256 is named ENC_EC256 in the source code and
artifacts while ECIES-X25519 is named ENC_X25519.

Note that MCUboot is built to support only one ECIES and HMAC SHA at once,
and truncated HMAC is not supported at this time

## [Upgrade process](#upgrade-process)

When starting a new upgrade process, `MCUboot` checks that the image in the
Expand Down
37 changes: 27 additions & 10 deletions scripts/imgtool/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
'ENCKW': 0x31,
'ENCEC256': 0x32,
'ENCX25519': 0x33,
'ENCX25519_SHA512': 0x34,
'DEPENDENCY': 0x40,
'SEC_CNT': 0x50,
'BOOT_RECORD': 0x60,
Expand Down Expand Up @@ -436,21 +437,21 @@ def check_trailer(self):
len(self.payload), tsize, self.slot_size)
raise click.UsageError(msg)

def ecies_hkdf(self, enckey, plainkey):
def ecies_hkdf(self, enckey, plainkey, hmac_sha_alg):
if isinstance(enckey, ecdsa.ECDSA256P1Public):
newpk = ec.generate_private_key(ec.SECP256R1(), default_backend())
shared = newpk.exchange(ec.ECDH(), enckey._get_public())
else:
newpk = X25519PrivateKey.generate()
shared = newpk.exchange(enckey._get_public())
derived_key = HKDF(
algorithm=hashes.SHA256(), length=48, salt=None,
algorithm=hmac_sha_alg, length=48, salt=None,
info=b'MCUBoot_ECIES_v1', backend=default_backend()).derive(shared)
encryptor = Cipher(algorithms.AES(derived_key[:16]),
modes.CTR(bytes([0] * 16)),
backend=default_backend()).encryptor()
cipherkey = encryptor.update(plainkey) + encryptor.finalize()
mac = hmac.HMAC(derived_key[16:], hashes.SHA256(),
mac = hmac.HMAC(derived_key[16:], hmac_sha_alg,
backend=default_backend())
mac.update(cipherkey)
ciphermac = mac.finalize()
Expand All @@ -468,7 +469,8 @@ def create(self, key, public_key_format, enckey, dependencies=None,
sw_type=None, custom_tlvs=None, compression_tlvs=None,
compression_type=None, encrypt_keylen=128, clear=False,
fixed_sig=None, pub_key=None, vector_to_sign=None,
user_sha='auto', is_pure=False, keep_comp_size=False, dont_encrypt=False):
user_sha='auto', hmac_sha='auto', is_pure=False, keep_comp_size=False,
dont_encrypt=False):
self.enckey = enckey

# key decides on sha, then pub_key; of both are none default is used
Expand Down Expand Up @@ -675,6 +677,17 @@ def create(self, key, public_key_format, enckey, dependencies=None,
else:
plainkey = os.urandom(16)

if not isinstance(enckey, rsa.RSAPublic):
if hmac_sha == 'auto' or hmac_sha == '256':
hmac_sha = '256'
hmac_sha_alg = hashes.SHA256()
elif hmac_sha == '512':
if not isinstance(enckey, x25519.X25519Public):
raise click.UsageError("Currently only ECIES-X25519 supports HMAC-SHA512")
hmac_sha_alg = hashes.SHA512()
else:
raise click.UsageError("Unsupported HMAC-SHA")

if isinstance(enckey, rsa.RSAPublic):
cipherkey = enckey._get_public().encrypt(
plainkey, padding.OAEP(
Expand All @@ -683,15 +696,19 @@ def create(self, key, public_key_format, enckey, dependencies=None,
label=None))
self.enctlv_len = len(cipherkey)
tlv.add('ENCRSA2048', cipherkey)
elif isinstance(enckey, (ecdsa.ECDSA256P1Public,
x25519.X25519Public)):
cipherkey, mac, pubk = self.ecies_hkdf(enckey, plainkey)
elif isinstance(enckey, ecdsa.ECDSA256P1Public):
cipherkey, mac, pubk = self.ecies_hkdf(enckey, plainkey, hmac_sha_alg)
enctlv = pubk + mac + cipherkey
self.enctlv_len = len(enctlv)
if isinstance(enckey, ecdsa.ECDSA256P1Public):
tlv.add('ENCEC256', enctlv)
else:
tlv.add('ENCEC256', enctlv)
elif isinstance(enckey, x25519.X25519Public):
cipherkey, mac, pubk = self.ecies_hkdf(enckey, plainkey, hmac_sha_alg)
enctlv = pubk + mac + cipherkey
self.enctlv_len = len(enctlv)
if (hmac_sha == '256'):
tlv.add('ENCX25519', enctlv)
else:
tlv.add('ENCX25519_SHA512', enctlv)

if not clear:
nonce = bytes([0] * 16)
Expand Down
11 changes: 7 additions & 4 deletions scripts/imgtool/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ def gen_x25519(keyfile, passwd):
}
valid_formats = ['openssl', 'pkcs8']
valid_sha = [ 'auto', '256', '384', '512' ]
valid_hmac_sha = [ 'auto', '256', '512' ]


def load_signature(sigfile):
Expand Down Expand Up @@ -437,6 +438,8 @@ def convert(self, value, param, ctx):
@click.option('--sha', 'user_sha', type=click.Choice(valid_sha), default='auto',
help='selected sha algorithm to use; defaults to "auto" which is 256 if '
'no cryptographic signature is used, or default for signature type')
@click.option('--hmac-sha', 'hmac_sha', type=click.Choice(valid_hmac_sha), default='auto',
help='sha algorithm used in HKDF/HMAC in ECIES key exchange TLV')
@click.option('--vector-to-sign', type=click.Choice(['payload', 'digest']),
help='send to OUTFILE the payload or payload''s digest instead '
'of complied image. These data can be used for external image '
Expand All @@ -449,7 +452,7 @@ def sign(key, public_key_format, align, version, pad_sig, header_size,
endian, encrypt_keylen, encrypt, compression, infile, outfile,
dependencies, load_addr, hex_addr, erased_val, save_enctlv,
security_counter, boot_record, custom_tlv, rom_fixed, max_align,
clear, fix_sig, fix_sig_pubkey, sig_out, user_sha, is_pure,
clear, fix_sig, fix_sig_pubkey, sig_out, user_sha, hmac_sha, is_pure,
vector_to_sign, non_bootable):

if confirm:
Expand Down Expand Up @@ -526,7 +529,7 @@ def sign(key, public_key_format, align, version, pad_sig, header_size,
img.create(key, public_key_format, enckey, dependencies, boot_record,
custom_tlvs, compression_tlvs, None, int(encrypt_keylen), clear,
baked_signature, pub_key, vector_to_sign, user_sha=user_sha,
is_pure=is_pure, keep_comp_size=False, dont_encrypt=True)
hmac_sha=hmac_sha, is_pure=is_pure, keep_comp_size=False, dont_encrypt=True)
compressed_img = image.Image(version=decode_version(version),
header_size=header_size, pad_header=pad_header,
pad=pad, confirm=confirm, align=int(align),
Expand Down Expand Up @@ -570,14 +573,14 @@ def sign(key, public_key_format, align, version, pad_sig, header_size,
compressed_img.create(key, public_key_format, enckey,
dependencies, boot_record, custom_tlvs, compression_tlvs,
compression, int(encrypt_keylen), clear, baked_signature,
pub_key, vector_to_sign, user_sha=user_sha,
pub_key, vector_to_sign, user_sha=user_sha, hmac_sha=hmac_sha,
is_pure=is_pure, keep_comp_size=keep_comp_size)
img = compressed_img
else:
img.create(key, public_key_format, enckey, dependencies, boot_record,
custom_tlvs, compression_tlvs, None, int(encrypt_keylen), clear,
baked_signature, pub_key, vector_to_sign, user_sha=user_sha,
is_pure=is_pure)
hmac_sha=hmac_sha, is_pure=is_pure)
img.save(outfile, hex_addr)
if sig_out is not None:
new_signature = img.get_signature()
Expand Down
Loading