2025-10-17 4:06 AM
Hi,
I am trying to send encrypted data between a C# application and STM32 in AES GCM mode.
Currently, I am only doing encryption of a known test vector.
Key and IV are both all zeros.
AAD length is 0.
My testvector is: 00 00 00 00 00 01 00 00 00 00 00 00 00 00 42 49
Using the code below, I get the same ciphertext in both applications:
03 88 DA CE 60 B7 A3 92 F3 28 C2 B9 71 B2 BC 31
But the tag never matches.
I suspect there is something wrong in the final stage, where the tag is calculated.
void AesTest()
{
uint8 iv[12];
uint8 plainText[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x49 };
uint8 header[0];
uint8 cipherText[sizeof(plainText)];
uint8 authTag[16];
memset(iv, 0, sizeof(iv));
uart_printf("ENCRYPT\r\n");
if (!AesEncryptGcm(iv, header, sizeof(header), plainText, sizeof(plainText), cipherText, authTag))
return;
}
bool AesEncryptGcm(const uint8 *iv, const uint8 *header, uint8 headerLen, const uint8 *plainText, uint16 len, uint8 *cipherText, uint8 *authTag)
{
if ((headerLen % 16) != 0 || (len % 16) != 0)
{
uart_printf("AES encryption error: header and src length must be a multiple of 16 bytes\r\n");
return false;
}
AesGcm(true, (const uint32*) iv, (const uint32*) header, headerLen / 4, (const uint32*) plainText, len / 4, (uint32*) cipherText, (uint32*) authTag);
return true;
}
static void AesGcm(bool encrypt, const uint32 *iv, const uint32 *header, uint16 headerLen, const uint32 *src, uint16 srcLen, uint32 *dst, uint32 *authTag)
{
// GCM INITIALIZE.
// Must disable AES peripheral before setting mode, key etc.
AES->CR &= ~BIT(0);
// Chaining mode: GCM.
AES->CR = (AES->CR & 0b11111111111111111111111110011111) | (0x3 << 5);
// Mode: encrypt or decrypt.
if (encrypt)
AES->CR = (AES->CR & 0b11111111111111111111111111100111) | (0x0 << 3); // Encryption.
else
AES->CR = (AES->CR & 0b11111111111111111111111111100111) | (0x2 << 3); // Decryption.
// Data type: ASCII stream = 8-bit data (byte swapping).
AES->CR = (AES->CR & 0b11111111111111111111111111111001) | (0x2 << 1);
// Key size: 128-bit.
AES->CR &= ~BIT(18);
// Key mode: key in AES core.
AES->CR = (AES->CR & 0b11111100111111111111111111111111) | (0x0 << 24);
// Select GCM initialization phase.
AES->CR = (AES->CR & 0b11111111111111111001111111111111) | (0x0 << 13);
// Write 96-bit IV + 32-bit counter start value.
AES->IVR3 = iv[0];
AES->IVR2 = iv[1];
AES->IVR1 = iv[2];
AES->IVR0 = 0x0002; // Counter starts from 2 for each new IV, automatically increased by the AES engine for each block.
// Write key. The key registers must always be written in ascending or descending order.
AES->KEYR3 = key[0]; // Key[127:96]
AES->KEYR2 = key[1]; // Key[95:64]
AES->KEYR1 = key[2]; // Key[63:32]
AES->KEYR0 = key[3]; // Key[31:0]
// Wait until key valid.
while ((AES->SR & BIT(7)) == 0)
;
// Start hash key calculation.
// Enable AES peripheral. Automatically cleared when the calculation is completed.
AES->CR |= BIT(0);
while ((AES->ISR & BIT(0)) == 0)
;
AES->ICR = BIT(0); // Clear CCF flag.
// GCM HEADER PHASE.
// Select GCM header phase.
AES->CR = (AES->CR & 0b11111111111111111001111111111111) | (0x1 << 13);
// Enable AES peripheral.
AES->CR |= BIT(0);
// Append header data.
// A 16-byte data block enters the AES peripheral with four successive 32-bit word writes.
// The most significant word (bits [127:96]) first, the least significant word (bits [31:0]) last.
// The four 32-bit words of a 16-byte data block must be stored in the memory consecutively
// and in big-endian order, that is, with the most significant word on the lowest address.
for (uint16 block = 0; block < headerLen; block += 4)
{
AES->ICR = BIT(0); // Clear CCF flag
for (uint8 i = 0; i < 4; i++)
AES->DINR = header[block + i];
// Wait for operation to complete.
while ((AES->ISR & BIT(0)) == 0)
;
// No data are read during header phase.
}
// GCM PAYLOAD PHASE.
// Select GCM payload phase.
AES->CR = (AES->CR & 0b11111111111111111001111111111111) | (0x2 << 13);
for (uint16 block = 0; block < srcLen; block += 4)
{
AES->ICR = BIT(0); // Clear CCF flag
for (uint8 i = 0; i < 4; i++)
AES->DINR = src[block + i];
// Wait for operation to complete.
while ((AES->ISR & BIT(0)) == 0)
;
// Save the header block.
// Data weights from the first to the fourth read operation are: [127:96], [95:64], [63:32], and [31:0].
for (uint8 i = 0; i < 4; i++)
dst[block + i] = AES->DOUTR;
}
// GCM FINALIZATION.
// Select GCM final phase.
AES->CR = (AES->CR & 0b11111111111111111001111111111111) | (0x3 << 13);
// In the final phase, data are written to AES_DINR normally (no swapping), while swapping is
// applied to tag data read from AES_DOUTR.
uart_printf("header len = %d\r\n", headerLen);
uart_printf("payload len = %d\r\n", srcLen);
// Write the concatenated AAD and payload byte lengths.
AES->ICR = BIT(0); // Clear CCF flag
AES->DINR = 0; // AAD length [63:32]
AES->DINR = headerLen * 4; // AAD length [31:0]
AES->DINR = 0; // Payload length [63:32]
AES->DINR = srcLen * 4; // Payload length [31:0]
// Wait for operation to complete.
while ((AES->ISR & BIT(0)) == 0)
;
// Get GCM authentication tag.
// Swapping is applied to tag data read from AES_DOUTR.
authTag[0] = __REV(AES->DOUTR);
authTag[1] = __REV(AES->DOUTR);
authTag[2] = __REV(AES->DOUTR);
authTag[3] = __REV(AES->DOUTR);
AES->ICR = BIT(0); // Clear CCF flag
// Disable AES peripheral.
AES->CR &= ~BIT(0);
}