This chapter shows how to use the HMAC-SHA-2 IP by showing some snippets such as initialization, initiating SHA-2 or HMAC process and processing the interrupts.
This code is not compilable but serves to demonstrate the IO required.
More detailed and complete code can be found in the software under sw/
, ROM code and HMAC DIF.
This section of the code describes initializing the HMAC with SHA-2 256 digest using a 256-bit key: setting up the interrupts, endianness, and HMAC/SHA-2 mode.
CFG.endian_swap
reverses the byte-order of input words when software writes into the message FIFO.
CFG.digest_swap
reverses the byte-order in the final HMAC or SHA hash.
void hmac_init(unsigned int endianess, unsigned int digest_endian) {
HMAC_CFG(0) = HMAC_CFG_SHA_EN
| HMAC_CFG_HMAC_EN
| (endianess << HMAC_CFG_ENDIAN_SWAP_LSB)
| (digest_endian << HMAC_CFG_DIGEST_SWAP_LSB)
| (HMAC_CFG_DIGEST_SIZE_FIELD << HMAC_CFG_DIGEST_SIZE_VALUE_SHA2_256)
| (HMAC_CFG_KEY_LENGTH_FIELD << HMAC_CFG_KEY_LENGTH_FIELD);
// Enable interrupts if needed.
// If secret key is static, you can put the key here
HMAC_KEY_0 = SECRET_KEY_0;
HMAC_KEY_1 = SECRET_KEY_1;
HMAC_KEY_2 = SECRET_KEY_2;
HMAC_KEY_3 = SECRET_KEY_3;
HMAC_KEY_4 = SECRET_KEY_4;
HMAC_KEY_5 = SECRET_KEY_5;
HMAC_KEY_6 = SECRET_KEY_6;
HMAC_KEY_7 = SECRET_KEY_7;
}
The following code shows how to send a message to the HMAC, the procedure is the same whether a full HMAC or just a SHA-2 computation is required (choose between them using CFG.hmac_en
).
In both cases the SHA-2 engine must be enabled using CFG.sha_en
(once all other configuration has been properly set).
If the message is larger than 512-bit, the software must wait until the FIFO is not full before writing further bits.
For SHA-2 256, only DIGEST_0
..7
should be read out; the redundant digests are irrelevant and would hold irrelevant values.
For SHA-2 384, only DIGEST_0
..11
should be read out, the rest should be truncated out by not being read via SW.
For SHA-2 512, all DIGEST_0
..15
should be read out.
void run_hmac(uint32_t *msg, uint32_t msg_len, uint32_t *hash) {
// Initiate hash: hash_start
REG32(HMAC_CMD(0)) = (1 << HMAC_CMD_HASH_START);
// write the message: below example assumes word-aligned access
for (uint32_t written = 0 ; written < (msg_len >> 3) ; written += 4) {
while((REG32(HMAC_STATUS(0)) >> HMAC_STATUS_FIFO_FULL) & 0x1) ;
// Any write data from HMAC_MSG_FIFO_OFFSET to HMAC_MSG_FIFO_SIZE
// is written to the message FIFO
REG32(HMAC_MSG_FIFO(0)) = *(msg+(written/4));
}
// Completes hash: hash_process
REG32(HMAC_CMD(0)) = (1 << HMAC_CMD_HASH_PROCESS);
while(0 == (REG32(HMAC_INTR_STATE(0)) >> HMAC_INTR_STATE_HMAC_DONE) & 0x1);
REG32(HMAC_INTR_STATE(0)) = 1 << HMAC_INTR_STATE_HMAC_DONE;
// Read the digest
for (int i = 0 ; i < 8 ; i++) {
*(hash + i) = REG32(HMAC_DIGEST_0(0) + (i << 2));
}
}
The HMAC IP prevents CFG
and KEY
registers from getting updating while the engine is processing messages.
Such attempts are discarded.
The KEY
register ignores any attempt to access the secret key in the middle of the process.
If the software tries to update the KEY, the IP reports an error through the Error FIFO.
The error code is SwUpdateSecretKeyInProcess
, 0x0003
.
Software can let the HMAC IP process multiple message streams in a time-interleaved fashion by saving and restoring the context (i.e., parts of the hardware-internal state).
Such context switches are possible only at the boundary of complete message blocks (512-bit for SHA-2 256 or 1024-bit for SHA-2 384/512). When SW doesn't know each instant at which a full message block is available, it can buffer data in memory until a block is full and only write HMAC's FIFOs once the buffer in memory contains a full message block.
The context that needs to be saved and restored is in the following registers: CFG
, DIGEST_*
, and MSG_LENGTH_*
.
Each message stream needs to be started once by setting the CMD.hash_start
bit and finalized once by setting the CMD.hash_process
bit.
To switch from one message stream to another, set the CMD.hash_stop
bit, wait for the hmac_done
interrupt (or status bit), save one context and restore the other, and then set the CMD.hash_continue
bit.
Here is an example usage pattern of this feature:
- Start processing message stream A by configuring HMAC and then setting the
CMD.hash_start
bit. - Write an arbitrary number of message blocks to HMAC's
MSG_FIFO
. - Stop HMAC by setting the
CMD.hash_stop
bit and wait for thehmac_done
interrupt (or poll the interrupt status register). - Save the context by reading the
DIGEST_0
..15
andMSG_LENGTH_
{LOWER
,UPPER
} registers. If the operation is keyed HMAC, the values ofKEY_0
..X
registers also need to be maintained as part of the context, whereX
is the last register used for the given key length (e.g. for HMAC-256,X=7
). However, key registers cannot be read from SW, therefore SW must maintain key values as part of its own context during initialization. Similarly, the value of theCFG
register must also be preserved, and SW should keep its value separately, instead of reading it fromCFG
register. - Disable
sha_en
by updatingCFG
register, in order to clear the digest from stream A. This is necessary to also prevent leakage of intermediate context of one SW thread to another. - Repeat steps 1-5 for message stream B.
- Before restoring context, make sure that
sha_en
inCFG
remains disabled. - Restore the context of message stream A by writing the
CFG
,DIGEST_0
..15
, andMSG_LENGTH_
{LOWER
,UPPER
} registers. In the case of keyed HMAC,KEY_0
..X
registers also need to be restored. - Enable
sha_en
ofCFG
, which enables HMAC again and locks down writing to theDIGEST_0
..15
registers from SW. - Continue processing message stream A by setting the
CMD.hash_continue
bit. - Write an arbitrary number of message blocks to HMAC's
MSG_FIFO
. - Continue this with as many message blocks and parallel message streams as needed.
The final hash for any message stream can be obtained at any time (no need for complete blocks) by setting
CMD.hash_process
and waiting for thehmac_done
interrupt / status bit, finally reading the digest from theDIGEST
registers.
When HMAC sees errors, the IP reports the error via INTR_STATE.hmac_err
.
The details of the error type is stored in ERR_CODE
.
Error | Value | Description |
---|---|---|
SwPushMsgWhenShaDisabled |
0x1 |
The error is reported when SW writes data into MSG_FIFO when SHA is disabled. It may be due to SW routine error, or FI attacks. |
SwHashStartWhenShaDisabled |
0x2 |
When HMAC detects the CMD.start when SHA is disabled, it reports this error code. |
SwUpdateSecretKeyInProcess |
0x3 |
Secret Key CSRs should not be modified during the hashing. This error is reported when those CSRs are revised in active. |
SwHashStartWhenActive |
0x4 |
The error is reported when CMD.start is received while HMAC is running. |
SwPushMsgWhenDisallowed |
0x5 |
After CMD.process is received, the MSG_FIFO should not by updated by SW. This error is reported in that case. |
SwInvalidConfig |
0x6 |
SW has configured HMAC incorrectly. |
If the SW is slow and the SHA2 engine pops the data fast enough, the Message FIFO's depth may remain 0.
The Message FIFO's fifo_empty
status bit, however, is lowered for a cycle.
However, if the SHA-2 engine is currently busy, the Message FIFO may actually run full (indicated by the fifo_full
status bit).
Resolving this conditions may take several dozens of cycles.
After the SHA-2 engine starts popping the data again, the Message FIFO will eventually run empty again and the fifo_empty
status interrupt will fire.
Note that the fifo_empty
status interrupt will not fire if i) the Message FIFO is not writable by software, or ii) after software has written either the Process
or Stop
command.
The recommended approach for software to write messages is:
- Check the FIFO depth
STATUS.fifo_depth
. This represents the number of entry slots currently occupied in the FIFO. - Calculate the remaining size as
<max number of fifo entries> - <STATUS.fifo_depth>)
. - Write data to fill the remaining size.
- Repeat until all data is written.
In case the FIFO runs full (check
STATUS.fifo_full
), software can optionally wait for thefifo_empty
status interrupt before continuing.
If the FIFO runs full, software should not continue to write data into MSG_FIFO
and first wait the status flag STATUS.fifo_full
to lower.
Whilst the FIFO is full, the HMAC will block writes until the FIFO has space which will cause back-pressure on the interconnect.