NVS partition encryption keys in HMAC mode
Posted: Thu Dec 28, 2023 11:18 am
I don't really completely understand how it works on ESP32-C3. Things that clear to me:
1. HMAC are used as a KDF to securely derive AES encryption key, which are used to encrypt/decrypt data on NVS partition.
2. The key for HMAC are stored in eFuse, read protected.
Ok, but HMAC also takes 'message' as input, besides the key. Both message and key contribute to output. So what are the 'message' in the NVS/HMAC scenario? EDIT: see below, found out that already
Second thing I don't understand is how the resulting HMAC output aka encryption key (and the data on corresponding encrypted partition) are protected from being read by flashing other APP which can just read NVS the same way as original firmware. Yes, I know that enabling secure boot or flash encryption can prevent this. But that's just another layer of protection, not mentionned on the docs. I mean I understand that key in efuse are read protected but from that I've seen on the API reference and code HMAC output are not and is just stored in RAM structure. Am I missing something or it's intended to protect data in NVS only by external dump and extra layer (like secure boot,flash encryption, etc) are required to secure it from hacked firmware on CPU?
Answer to first question might be a key for my understanding second. If there are some kind of application supplied 'message' for HMAC KDF it is basically used as 'password' to derive encryption key. But I can't find anything related on API reference.
Really would like to learn an answers on these, to get theory behind that feature. But in case someone might wonder what I'm trying to achieve - I've made a device to generate TOTP (RFC 6238) on LCD with keypad, which are used to enter PIN code. Key for the TOTP needs to be secured. Unfortunately most TOTP services use HMAC-SHA1 without options, so I can't just store it in the eFuse and use HMAC module to generate keys. So I'm stuck with looking for other options. Currently I've followed the most straighforward way (for me at least) - encrypt it with AES and store on flash. Key for the AES encryption are derived from PIN and some other data with PBKDF2. Considering adding some steps with HMAC module upstream mode for KDF as well, having it's key protected in eFuse. Besides I'm forced to keep AES encryption key and/or decrypted TOTP key in RAM for some time when the device are "unlocked" (correct PIN entered) to generate TOTP responses. But at least even without secure boot and flash encryption (I'm considering enabling them in 'release' version but as I've already said - that's just another layer) if this device gets in wrong hands the only option I see it to brute-force PIN codes which should be covered by HMAC with eFuse key step plus high enough count of iterations to slow it down. Enabling secure boot & flash encryption as well will probably provide me just enough security for the purpose - it's just a hobby project anyways, not trying to make it unbreakable by intelligence services guys
EDIT: I don't know how it works like this, but once I've posted that message I was able to find a piece of code in ESP-IDF which derives NVS encryption keys with HMAC. So 'message' are basically fixed 'seeds' of 0xAEBE5A5A and 0xCEDEA5A5 unfortunately:
1. HMAC are used as a KDF to securely derive AES encryption key, which are used to encrypt/decrypt data on NVS partition.
2. The key for HMAC are stored in eFuse, read protected.
Ok, but HMAC also takes 'message' as input, besides the key. Both message and key contribute to output. So what are the 'message' in the NVS/HMAC scenario? EDIT: see below, found out that already
Second thing I don't understand is how the resulting HMAC output aka encryption key (and the data on corresponding encrypted partition) are protected from being read by flashing other APP which can just read NVS the same way as original firmware. Yes, I know that enabling secure boot or flash encryption can prevent this. But that's just another layer of protection, not mentionned on the docs. I mean I understand that key in efuse are read protected but from that I've seen on the API reference and code HMAC output are not and is just stored in RAM structure. Am I missing something or it's intended to protect data in NVS only by external dump and extra layer (like secure boot,flash encryption, etc) are required to secure it from hacked firmware on CPU?
Answer to first question might be a key for my understanding second. If there are some kind of application supplied 'message' for HMAC KDF it is basically used as 'password' to derive encryption key. But I can't find anything related on API reference.
Really would like to learn an answers on these, to get theory behind that feature. But in case someone might wonder what I'm trying to achieve - I've made a device to generate TOTP (RFC 6238) on LCD with keypad, which are used to enter PIN code. Key for the TOTP needs to be secured. Unfortunately most TOTP services use HMAC-SHA1 without options, so I can't just store it in the eFuse and use HMAC module to generate keys. So I'm stuck with looking for other options. Currently I've followed the most straighforward way (for me at least) - encrypt it with AES and store on flash. Key for the AES encryption are derived from PIN and some other data with PBKDF2. Considering adding some steps with HMAC module upstream mode for KDF as well, having it's key protected in eFuse. Besides I'm forced to keep AES encryption key and/or decrypted TOTP key in RAM for some time when the device are "unlocked" (correct PIN entered) to generate TOTP responses. But at least even without secure boot and flash encryption (I'm considering enabling them in 'release' version but as I've already said - that's just another layer) if this device gets in wrong hands the only option I see it to brute-force PIN codes which should be covered by HMAC with eFuse key step plus high enough count of iterations to slow it down. Enabling secure boot & flash encryption as well will probably provide me just enough security for the purpose - it's just a hobby project anyways, not trying to make it unbreakable by intelligence services guys
EDIT: I don't know how it works like this, but once I've posted that message I was able to find a piece of code in ESP-IDF which derives NVS encryption keys with HMAC. So 'message' are basically fixed 'seeds' of 0xAEBE5A5A and 0xCEDEA5A5 unfortunately:
- static esp_err_t compute_nvs_keys_with_hmac(hmac_key_id_t hmac_key_id, nvs_sec_cfg_t* cfg)
- {
- uint32_t ekey_seed[8] = {[0 ... 7] = 0xAEBE5A5A};
- uint32_t tkey_seed[8] = {[0 ... 7] = 0xCEDEA5A5};
- esp_err_t err = esp_hmac_calculate(hmac_key_id, ekey_seed, sizeof(ekey_seed), (uint8_t *)cfg->eky);
- if (err != ESP_OK) {
- ESP_LOGE(TAG, "Failed to calculate seed HMAC: [0x%02X] (%s)", err, esp_err_to_name(err));
- return err;
- }
- ESP_FAULT_ASSERT(err == ESP_OK);
- err = esp_hmac_calculate(hmac_key_id, tkey_seed, sizeof(tkey_seed), (uint8_t *)cfg->tky);
- if (err != ESP_OK) {
- ESP_LOGE(TAG, "Failed to calculate seed HMAC: [0x%02X] (%s)", err, esp_err_to_name(err));
- return err;
- }
- ESP_FAULT_ASSERT(err == ESP_OK);
- /* NOTE: If the XTS E-key and T-key are the same, we have a hash collision */
- ESP_FAULT_ASSERT(memcmp(cfg->eky, cfg->tky, NVS_KEY_SIZE) != 0);
- return ESP_OK;
- }