I hadn't reviewed the encryption module of the Zero Gecko microcontroller yet.
The Zero Gecko processor family supports an accelerator for encryption and decryption. Simplicity Studio comes with application note AN0033 and example projects.
The examples don't target the Zero Gecko. But they have enough info to get you started.
I loaded the example 'EFM32G_aes_cbc_128', a 128 bit CBC (Cipher Block Chaining) example.
My first step was to understand what the example is doing.
Overview of the AN0033 example
There are two arrays with example data. One structure has the unencryped data (exampledata[]), the other has the same data encrypted by a known key and seed (expectedEncryptedData[]).
const uint8_t exampleData[] = { 0x6B, 0xC1, 0xBE, 0xE2, 0x2E, 0x40, 0x9F, 0x96, 0xE9, 0x3D, 0x7E, 0x11, 0x73, 0x93, 0x17, 0x2A, 0xAE, 0x2D, 0x8A, 0x57, 0x1E, 0x03, 0xAC, 0x9C, 0x9E, 0xB7, 0x6F, 0xAC, 0x45, 0xAF, 0x8E, 0x51, 0x30, 0xC8, 0x1C, 0x46, 0xA3, 0x5C, 0xE4, 0x11, 0xE5, 0xFB, 0xC1, 0x19, 0x1A, 0x0A, 0x52, 0xEF, 0xF6, 0x9F, 0x24, 0x45, 0xDF, 0x4F, 0x9B, 0x17, 0xAD, 0x2B, 0x41, 0x7B, 0xE6, 0x6C, 0x37, 0x10}; const uint8_t expectedEncryptedData[] = { 0x76, 0x49, 0xAB, 0xAC, 0x81, 0x19, 0xB2, 0x46, 0xCE, 0xE9, 0x8E, 0x9B, 0x12, 0xE9, 0x19, 0x7D, 0x50, 0x86, 0xCB, 0x9B, 0x50, 0x72, 0x19, 0xEE, 0x95, 0xDB, 0x11, 0x3A, 0x91, 0x76, 0x78, 0xB2, 0x73, 0xBE, 0xD6, 0xB8, 0xE3, 0xC1, 0x74, 0x3B, 0x71, 0x16, 0xE6, 0x9E, 0x22, 0x22, 0x95, 0x16, 0x3F, 0xF1, 0xCA, 0xA1, 0x68, 0x1F, 0xAC, 0x09, 0x12, 0x0E, 0xCA, 0x30, 0x75, 0x86, 0xE1, 0xA7};
The example first prepares the encryption and decryption keys.
const uint8_t exampleKey[] = { 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}; // ... const uint8_t initVector[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}; // ... /* Enable AES clock */ CMU_ClockEnable(cmuClock_AES, true); /* Calculate decryption key from the original key, needs to be done once for each key */ AES_DecryptKey128(decryptionKey, exampleKey);
Then it encrypts the plain data. And as a validation, it compares the result with what is expected (the predefined array of encrypted data expectedEncryptedData[]).
/* Encrypt data in AES_128 CBC */ AesCBC128(exampleKey, dataBuffer, dataBuffer, false, sizeof(dataBuffer) / (sizeof(dataBuffer[0]) * 16), initVector); /* Wait for AES to finish */ while(!AesFinished()); /* Check whether encrypted results are correct */ for (i = 0; i < (sizeof(dataBuffer) / sizeof(dataBuffer[0])); i++) { if (dataBuffer[i] != expectedEncryptedData[i]) { error = true; } }
The next step is to do the reverse. Encrypted data gets decrypted (this time using the decryption key generated at the start of the program in the AES_DecryptKey128() call).
That decrypted result is compared with the original plain data.
The example's implementation of AES128 CDC
The example talks directly to the AES registers. It also uses the AES interrupt to perform part of the encrypt/decrypt actions.
void AesCBC128(const uint8_t* key, const uint8_t* inputData, uint8_t* outputData, bool decrypt, const uint32_t blockNumber, const uint8_t* iv) { // ... AES->IEN = AES_IEN_DONE; NVIC_EnableIRQ(AES_IRQn); /* Write the KEY to AES module */ /* Writing only the KEYHA 4 times will transfer the key to all 4 * high key registers, used here, in 128 bit key mode, as buffers */ for (i = 3; i >= 0; i--) { AES->KEYLA = __REV(key32[i]); } /* Decryption */ if (decryptG) { /* Configure AES module */ AES->CTRL = AES_CTRL_DECRYPT | /* Set decryption */ // AES_CTRL_KEYBUFEN | /* Use key buffering */ AES_CTRL_DATASTART; /* Start decryption on data write */ /* Make a copy of the initial vector */ for (i = 0; i < 4; i++) { prev[i] = iv32[i]; } /* Load data and trigger a decryption */ for (i = 3; i >= 0; i--) { AES->DATA = __REV(inputDataG[i]); } } /* Encryption */ else { AES->CTRL = /*AES_CTRL_KEYBUFEN | */ /* Use key buffering */ AES_CTRL_XORSTART; /* Start encryption on data write */ /* Writing to the DATA register here does NOT trigger encryption */ for (i = 3; i >= 0; i--) { AES->DATA = __REV(iv32[i]); } /* Load data and trigger encryption using XORDATA*/ for (i = 3; i >= 0; i--) { AES->XORDATA = __REV(inputDataG[i]); } } }
and the supporting interrupt handler:
void AES_IRQHandler(void) { // ... /* Clear interrupt flag */ AES->IFC = AES_IFC_DONE; /* Decryption */ if (decryptG) { if (blockIndex < numberOfBlocks) { /* Writing to the XOR register here does NOT trigger decryption*/ for (i = 3; i >= 0; i--) { AES->XORDATA = __REV(prev[i]); prev[i] = inputDataG[i]; } inputDataG += 4; /* Store decrypted data */ for (i = 3; i >= 0; i--) { outputDataG[i] = __REV(AES->DATA); } outputDataG += 4; /* Load data and trigger a decryption */ for (i = 3; i >= 0; i--) { AES->DATA = __REV(inputDataG[i]); } } /* Indicate last block */ else { aesFinished = true; } /* Increase block index */ blockIndex++; } /* Encryption */ else { /* Store encrypted data */ for (i = 3; i >= 0; i--) { outputDataG[i] = __REV(AES->DATA); } outputDataG += 4; if (blockIndex < numberOfBlocks) { /* If not last block, write new data to be decrypted. */ /* AES is started automatically by the last write */ inputDataG += 4; for (i = 3; i >= 0; i--) { AES->XORDATA = __REV(inputDataG[i]); } } /* Indicate last block */ else { aesFinished = true; } /* Increase block index */ blockIndex++; } }
The validation of the work is fully completed:
bool AesFinished(void) { return aesFinished; }
I wanted to check the functionality of the AES Peripheral API in stead.
My simplified example
The only changes I had to make to the whole project are all in the source file aes_cbc_128.c :
I removed the interrupt handler AES_IRQHandler() completely.
The function AesCBC128() (with the low level implementation using the registers and interrupt) was replaced by a single function call to the AES API:
void AesCBC128(const uint8_t* key, const uint8_t* inputData, uint8_t* outputData, bool decrypt, const uint32_t blockNumber, const uint8_t* iv) { AES_CBC128(outputData, inputData, blockNumber * 16 , // AES_BLOCKSIZE key, iv, ! decrypt); }
And to avoid that I had to change the main example code, I changed the AesFinished() function to just return true. The main code uses this function to check that the work is finished.
The API implementation only returns when the encryption or decryption is complete, so we are ok to always return true.
bool AesFinished(void) { return true; }
The result is a less advanced implementation. On the other hand it's also simpler and easier to understand as first step into the AES module.
I've attached the zero gecko project for Simplicity Studio.