The encryption ChaChaPoly is an authenticated encryption algorithm that combines the ChaCha20 stream cipher with the Poly1305 authentication scheme.
This algorithm is widely used in security applications on embedded devices because it is very secure, yet very efficient to run on resource-limited devices.
Advantages of ChaChaPoly
- Efficiency: It is faster than AES on devices without dedicated encryption hardware.
- Security: Offers a high level of security, comparable to AES.
- Simplicity: Easy to implement in software.
In the case of the ESP32, it has hardware acceleration for AES encryption. However, it is still very interesting to know the ChaChaPoly algorithm for secure communication with other devices.
So, let’s get to it. But first, let’s see what ChaChaPoly is 👇.
What is ChaChaPoly
ChaChaPoly is an authenticated encryption algorithm that combines two main components:
- ChaCha20: A stream cipher that generates a sequence of pseudo-random bytes from a key and a nonce (number used once). It is fast and efficient in both hardware and software.
- Poly1305: A message authentication code (MAC) scheme that ensures the integrity and authenticity of the data.
Together, ChaCha20 and Poly1305 form ChaChaPoly (how original, right? 😆), which provides confidentiality, integrity, and authenticity in communications.
Implementing ChaChaPoly on ESP32
The ESP32 does not have dedicated hardware for ChaChaPoly, but we can implement it in software using libraries like mbed TLS, which is already integrated.
#include <mbedtls/chachapoly.h>
The encryption and authentication process with ChaChaPoly involves the following steps:
- Initialization: Set up the ChaChaPoly context with a key and a nonce.
- Encryption: Encrypt the data using ChaCha20.
- Authentication: Generate a message authentication code (MAC) with Poly1305.
- Verification: Verify the authenticity and integrity of the received data.
Let’s see it all in an example that shows how to encrypt and authenticate a message using ChaChaPoly on the ESP32.
#include "../libs/Seeed_Arduino_mbedtls/src/Seeed_mbedtls.h"
#include "../libs/Seeed_Arduino_mbedtls/src/mbedtls/chachapoly.h"
#define CHA_CHA_POLY_KEY_SIZE 32
#define CHA_CHA_POLY_IV_SIZE 12
#define CHA_CHA_POLY_AUTH_SIZE 16
#define CHA_CHA_POLY_MESSAGE_SIZE 60
#define CHA_CHA_POLY_TAG_SIZE 16
void encryptChaChaPoly(const byte key[CHA_CHA_POLY_KEY_SIZE],
const byte iv[CHA_CHA_POLY_IV_SIZE],
const byte auth[CHA_CHA_POLY_AUTH_SIZE],
const byte plainText[CHA_CHA_POLY_MESSAGE_SIZE],
byte cipherText[CHA_CHA_POLY_MESSAGE_SIZE],
byte tag[CHA_CHA_POLY_TAG_SIZE])
{
unsigned char output[265];
unsigned char mac[16];
mbedtls_chachapoly_context ctx;
mbedtls_chachapoly_init(&ctx);
mbedtls_chachapoly_setkey(&ctx, key);
/*mbedtls_chachapoly_encrypt_and_tag(&ctx, 128, iv, nullptr, 0, plainText, output, mac);*/
}
bool decryptChaChaPoly(const byte key[CHA_CHA_POLY_KEY_SIZE],
const byte iv[CHA_CHA_POLY_IV_SIZE],
const byte auth[CHA_CHA_POLY_AUTH_SIZE],
const byte cipherText[CHA_CHA_POLY_MESSAGE_SIZE],
byte plainText[CHA_CHA_POLY_MESSAGE_SIZE],
const byte tag[CHA_CHA_POLY_TAG_SIZE])
{
unsigned char output[265];
mbedtls_chachapoly_context ctx;
mbedtls_chachapoly_init(&ctx);
mbedtls_chachapoly_setkey(&ctx, key);
mbedtls_chachapoly_auth_decrypt(&ctx, 128, iv, nullptr, 0, tag, cipherText, output);
}
uint8_t getrnd()
{
uint8_t really_random = *(volatile uint8_t*)0x3FF20E44;
return really_random;
}
void generateRandom(byte* bytes, size_t size)
{
for(size_t i = 0; i < size; i++)
{
bytes[i] = (byte)getrnd();
}
}
void generateIv(byte iv[CHA_CHA_POLY_IV_SIZE])
{
generateRandom(iv, CHA_CHA_POLY_IV_SIZE);
}
void setup()
{
Serial.begin(115200);
delay(2000);
byte key[CHA_CHA_POLY_KEY_SIZE] = {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38 };
byte auth[CHA_CHA_POLY_AUTH_SIZE] = {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18 };
byte iv[CHA_CHA_POLY_IV_SIZE];
// construct plain text message
byte plainText[CHA_CHA_POLY_MESSAGE_SIZE];
String plain = "{\"my secret message\"}";
plain.getBytes(plainText, CHA_CHA_POLY_MESSAGE_SIZE);
// encrypt plain text message from plainText to cipherText
byte cipherText[CHA_CHA_POLY_MESSAGE_SIZE];
byte tag[CHA_CHA_POLY_TAG_SIZE];
encryptChaChaPoly(key, iv, auth, plainText, cipherText, tag);
// decrypt message from cipherText to plainText
// output is valid only if result is true
//bool result = decryptChaChaPoly(key, iv, auth, cipherText, plainText, tag);
}
void loop()
{
delay(1000);
}
- Key and nonce: The key should be 256 bits (32 bytes) and the nonce should be 96 bits (12 bytes). Both must be secret and unique for each encryption operation.
- Plain text: The message we want to encrypt.
- Cipher text and MAC: The result of the encryption and authentication is stored in
ciphertext
andtag
, respectively. - Initialization and setup: We configure the ChaChaPoly context with the key and the nonce.
- Encryption and authentication: We use
mbedtls_chachapoly_encrypt_and_tag
to encrypt the message and generate the MAC. - Results: We display the ciphertext and MAC in hexadecimal format.