STM32H7S3: SAES fails after CRYP when using HAL_CRYP_Encrypt() — SAES_SR_CCF wait never completes
Hello,
I am working with an STM32H7S3L8 and I am currently testing the SAES and CRYP AES hardware accelerators using the STM32Cube HAL. I am using:
- MCU: STM32H7S3L8
- Board: NUCLEO-H7S3L8
- Firmware package: STM32Cube_FW_H7RS V1.3.0
- API: `HAL_CRYP_Init()` and `HAL_CRYP_Encrypt()`
- AES mode: AES-128-ECB
- SAES and CRYP are tested independently with the same NIST AES-128 test vector:
//CRYP TEST:
int tests_cryp_aes128_ecb(void)
{
/*
* NIST SP 800-38A
* F.1 ECB-AES128.Encrypt
*/
__ALIGN_BEGIN static const uint32_t key[4] __ALIGN_END = {0x2B7E1516, 0x28AED2A6, 0xABF71588, 0x09CF4F3C};
__ALIGN_BEGIN static const uint32_t plaintext[4] __ALIGN_END = {0x6BC1BEE2, 0x2E409F96, 0xE93D7E11, 0x7393172A};
__ALIGN_BEGIN static const uint32_t expected_ciphertext[4] __ALIGN_END = {0x3AD77BB4,
0x0D7A3660,
0xA89ECAF3,
0x2466EF97};
uint32_t ciphertext[4] = {0};
HAL_StatusTypeDef status;
/*
* Configure CRYP for AES-128 ECB encryption.
*/
CRYP_HandleTypeDef hcryp;
memset(&hcryp, 0, sizeof(hcryp));
hcryp.Instance = CRYP;
hcryp.Init.DataType = CRYP_DATATYPE_32B;
hcryp.Init.KeySize = CRYP_KEYSIZE_128B;
hcryp.Init.pKey = (uint32_t *)key;
hcryp.Init.Algorithm = CRYP_AES_ECB;
hcryp.Init.DataWidthUnit = CRYP_DATAWIDTHUNIT_WORD;
hcryp.Init.HeaderWidthUnit = CRYP_HEADERWIDTHUNIT_WORD;
hcryp.Init.KeyIVConfigSkip = CRYP_KEYIVCONFIG_ALWAYS;
hcryp.Init.KeyMode = CRYP_KEYMODE_NORMAL;
hcryp.Init.KeySelect = CRYP_KEYSEL_NORMAL;
/*
* Initialize CRYP peripheral.
*/
status = HAL_CRYP_Init(&hcryp);
if (status != HAL_OK)
{
printf("CRYP init FAILED\r\n");
return 1;
}
/*
* Encrypt plaintext.
*/
status = HAL_CRYP_Encrypt(&hcryp, (uint32_t *)plaintext, 4, (uint32_t *)ciphertext, HAL_MAX_DELAY);
if (status != HAL_OK)
{
printf("CRYP encryption FAILED\r\n");
return 1;
}
print_buffer("Plaintext ", plaintext, sizeof(plaintext) / sizeof(plaintext[0]));
print_buffer("Ciphertext", ciphertext, sizeof(ciphertext) / sizeof(ciphertext[0]));
print_buffer("Expected ", expected_ciphertext, sizeof(expected_ciphertext) / sizeof(expected_ciphertext[0]));
/*
* Compare with NIST expected result.
*/
if (memcmp(ciphertext, expected_ciphertext, CRYP_AES_BLOCK_SIZE) != 0)
{
printf("CRYP AES-128 ECB TEST FAILED\r\n");
return 1;
}
printf("CRYP AES-128 ECB TEST PASSED\r\n");
status = HAL_CRYP_DeInit(&hcryp);
if (status != HAL_OK)
{
printf("CRYP Deinit FAILED\r\n");
return 1;
}
return 0;
}
-------------------------------------------------------------------------------------------
//SAES TEST
int tests_saes_aes128_ecb(void)
{
__ALIGN_BEGIN static const uint32_t key[4] __ALIGN_END = {0x2B7E1516, 0x28AED2A6, 0xABF71588, 0x09CF4F3C};
__ALIGN_BEGIN static const uint32_t plaintext[4] __ALIGN_END = {0x6BC1BEE2, 0x2E409F96, 0xE93D7E11, 0x7393172A};
__ALIGN_BEGIN static const uint32_t expected_ciphertext[4] __ALIGN_END = {0x3AD77BB4,
0x0D7A3660,
0xA89ECAF3,
0x2466EF97};
uint32_t ciphertext[4] = {0};
HAL_StatusTypeDef status;
/*
* Configure SAES for AES-128 ECB encryption.
*/
CRYP_HandleTypeDef hsaes;
memset(&hsaes, 0, sizeof(hsaes));
hsaes.Instance = SAES;
hsaes.Init.DataType = CRYP_DATATYPE_32B;
hsaes.Init.KeySize = CRYP_KEYSIZE_128B;
hsaes.Init.pKey = (uint32_t *)key;
hsaes.Init.Algorithm = CRYP_AES_ECB;
hsaes.Init.DataWidthUnit = CRYP_DATAWIDTHUNIT_WORD;
hsaes.Init.HeaderWidthUnit = CRYP_HEADERWIDTHUNIT_WORD;
hsaes.Init.KeyIVConfigSkip = CRYP_KEYIVCONFIG_ALWAYS;
hsaes.Init.KeyMode = CRYP_KEYMODE_NORMAL;
hsaes.Init.KeySelect = CRYP_KEYSEL_NORMAL;
/*
* Initialize SAES peripheral.
*/
status = HAL_CRYP_Init(&hsaes);
if (status != HAL_OK)
{
printf("SAES init FAILED\r\n");
return 1;
}
/*
* Encrypt plaintext using saes.
*/
status = HAL_CRYP_Encrypt(&hsaes, (uint32_t *)plaintext, 4, (uint32_t *)ciphertext, 1000);
if (status != HAL_OK)
{
printf("SAES encryption FAILED\r\n");
return 1;
}
print_buffer("Plaintext ", plaintext, sizeof(plaintext) / sizeof(plaintext[0]));
print_buffer("Ciphertext", ciphertext, sizeof(ciphertext) / sizeof(ciphertext[0]));
print_buffer("Expected ", expected_ciphertext, sizeof(expected_ciphertext) / sizeof(expected_ciphertext[0]));
/*
* Compare with NIST expected result.
*/
if (memcmp(ciphertext, expected_ciphertext, CRYP_AES_BLOCK_SIZE) != 0)
{
printf("SAES AES-128 ECB TEST FAILED\r\n");
return 1;
}
printf("SAES AES-128 ECB TEST PASSED\r\n");
status = HAL_CRYP_DeInit(&hsaes);
if (status != HAL_OK)
{
printf("SAES Deinit FAILED\r\n");
return 1;
}
return 0;
}The basic SAES and CRYP tests work correctly when executed individually. However, I have found an issue that depends on the order in which the two peripherals are used.
Analysis of the problem
1. SAES → CRYP: works correctly
If I execute the tests in this order, both tests complete successfully. The SAES AES-128-ECB test produces the expected ciphertext, and the subsequent CRYP test also produces the expected result.
So there is no issue when CRYP is used after SAES.
2. SAES → CRYP → SAES: works correctly
If I execute SAES → CRYP → SAES, all three tests complete successfully.
The first SAES test works, the CRYP test works, and the second SAES test also works. When debugging the second SAES operation, just before writing the input data to `SAES->DINR`, I observe the following values of the registers:
- SAES->CR = 0x00000001
- SAES->SR = 0x00000080
The four 32-bit input words are then written to `SAES->DINR`. After the fourth word is written, `SAES->SR` changes from 0x80 to 0x81, and the HAL continues successfully. The expected ciphertext is obtained.
3. CRYP → SAES: fails
The problem occurs when I execute CRYP → SAES.
The CRYP test completes successfully. I then initialize SAES with a new local `CRYP_HandleTypeDef`:
CRYP_HandleTypeDef hsaes;
memset(&hsaes, 0, sizeof(hsaes));
hsaes.Instance = SAES;
hsaes.Init.DataType = CRYP_DATATYPE_32B;
hsaes.Init.KeySize = CRYP_KEYSIZE_128B;
hsaes.Init.pKey = (uint32_t *)key;
hsaes.Init.Algorithm = CRYP_AES_ECB;
hsaes.Init.DataWidthUnit = CRYP_DATAWIDTHUNIT_WORD;
hsaes.Init.HeaderWidthUnit = CRYP_HEADERWIDTHUNIT_WORD;
hsaes.Init.KeyIVConfigSkip = CRYP_KEYIVCONFIG_ALWAYS;
hsaes.Init.KeyMode = CRYP_KEYMODE_NORMAL;
hsaes.Init.KeySelect = CRYP_KEYSEL_NORMAL;
status = HAL_CRYP_Init(&hsaes);After that, I continue encrypting the plaintext and just before the first `DINR` write inside the CRYP_AES_ProcessData() function, the SAES registers have the following values:
- SAES->CR = 0x00000001
- SAES->SR = 0x00000080
The four input words are also written correctly to `SAES->DINR`. However, after the fourth input word, `SAES->SR` remains 0x80 instead of changing to 0x81. Because of this, the HAL then waits until the timeout expires and the ciphertext remains all zeros (the encryption process does not finish).
Therefore, the important difference I observe is:
| Before DINR | After 4th DINR | |||
| CR | SR | CR | SR | |
| SAES → CRYP → SAES | 0x01 | 0x80 | 0x01 | 0x81 (operation completes) |
| CRYP → SAES | 0x01 | 0x80 | 0x01 | 0x80 (operation does not complete) |
Where the HAL gets stuck
I traced the execution into `CRYP_AES_ProcessData()` in the SAES branch. The relevant code is:
((SAES_TypeDef *)(hcryp->Instance))->DINR = *(uint32_t *)(hcryp->pCrypInBuffPtr + hcryp->CrypInCount);
hcryp->CrypInCount++;
((SAES_TypeDef *)(hcryp->Instance))->DINR = *(uint32_t *)(hcryp->pCrypInBuffPtr + hcryp->CrypInCount);
hcryp->CrypInCount++;
((SAES_TypeDef *)(hcryp->Instance))->DINR = *(uint32_t *)(hcryp->pCrypInBuffPtr + hcryp->CrypInCount);
hcryp->CrypInCount++;
((SAES_TypeDef *)(hcryp->Instance))->DINR = *(uint32_t *)(hcryp->pCrypInBuffPtr + hcryp->CrypInCount);
hcryp->CrypInCount++;
if (CRYP_WaitOnCCFlag(hcryp, Timeout) != HAL_OK)
{
...
}The execution then enters:
static HAL_StatusTypeDef CRYP_WaitOnCCFlag( const CRYP_HandleTypeDef *hcryp, uint32_t Timeout)
{
uint32_t tickstart;
tickstart = HAL_GetTick();
while (HAL_IS_BIT_CLR(((SAES_TypeDef *)(hcryp->Instance))->SR, SAES_SR_CCF))
{
if (Timeout != HAL_MAX_DELAY)
{
if (((HAL_GetTick() - tickstart) > Timeout) || (Timeout == 0U))
{
return HAL_ERROR;
}
}
}
return HAL_OK;
}In the failing case (CRYP → SAES), this is where the HAL remains waiting until the timeout expires.
Possible discrepancy between HAL and RM0477
While investigating this, I noticed something that I would like to clarify. According to the STM32H7S3 RM0477, the `SAES_SR` register does not appear to contain a `CCF` flag that must be set when the encryption is completed.
The documented bits include:
Bit 7: KEYVALID
Bit 3: BUSY
Bit 2: WRERRF
Bit 1: RDERRF


However, in the HAL documentation, the flag `CCF` is implemented as the Bit 0 in the `SAES_SR` register. Therefore, based on that, the result I obtain are:
SAES->SR = 0x80
KEYVALID = 1
CFF = 0
SAES->SR = 0x81
KEYVALID = 1
CRR = 1
This makes me wonder whether there is a mismatch between the HAL implementation and the SAES register definition for the STM32H7S3/H7S family.
Additional observations
I have also checked the SAES and CRYP peripheral clocks during the failing case. Immediately before the failing SAES encryption:
CRYP clock = 0
SAES clock = 1
Therefore, the failure does not appear to be caused simply by the CRYP clock remaining enabled. I also tested deinitializing CRYP before initializing SAES, but the issue still occurs.
The problem therefore seems to be related either to the state/configuration of the SAES hardware after CRYP has been used, or to the way the HAL handles the SAES peripheral.
Questions
Could someone from ST clarify the following?
1. Is `SAES_SR_CCF` actually a valid status flag for the SAES peripheral on STM32H7S3?
2. If not, why does the H7RS HAL use `CRYP_WaitOnCCFlag()` and wait for `SAES_SR_CCF`?
3. Is there any required reset, state clearing, or additional configuration that must be performed when switching from CRYP → SAES?
4. Is the expected behavior that `SAES->SR.CCF` should become set after the four `DINR` writes?
5. Could this be a HAL issue or an incorrect register definition for the STM32H7S3?
6. Is there a known difference between the SAES implementation described in the RM0477 and the one expected by the current `stm32h7rsxx_hal_cryp.c` implementation?
I would also appreciate any guidance on what registers or internal SAES state I should inspect to determine why the same SAES operation succeeds after:
SAES → CRYP → SAES
but fails after:
CRYP → SAES
while the initial `SAES->CR` and `SAES->SR` values are identical in both cases.
Thank you.
