Skip to main content
Visitor II
September 29, 2026
Question

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.