I am trying to do some RSA encryption using the mbedtls library on specifically the PK API on an esp32 using the Arduino framework and PlatformIO. I can succesfully encrypt 20 bytes at a time (that is inputArray[21]). But anymore than that, and I receive the 0x4080 error code RSA - Bad input parameters to function.
According to their documentation, I should be able to (given I am using a 2048 bit key) to encrypt at the very least 200 bytes at a time.
Command prompt when running the code:
___________________PROGRAM_START_________________________
Seeding the random number generator...
Initializing key context...
Generating the private key...
ABCDEFGHIJKLMNOPQRSTUV
Error encrypting
-16512
��������@�������������
���������������������
This is my RSACryptographer object:
//
// Created by DripTooHard on 15-04-2023.
//
#include "Cryptographer.h"
class Cryptographer{
protected:
mbedtls_ctr_drbg_context CTR_ctx;
public:
Cryptographer(){
}
virtual ~Cryptographer(){}
//TODO: Add PEMformatter
virtual int encrypt(unsigned char * inputArray, size_t inputLen, unsigned char * outputArray, size_t outSize, size_t * outLen) = 0;
virtual int decrypt(unsigned char * inputArray, size_t inputLen, unsigned char * outputArray,size_t outSize, size_t * outLen) = 0;
virtual int generate_key() = 0;
virtual int validate_key() = 0;
int generate_CTRX_context(){
mbedtls_ctr_drbg_free(&CTR_ctx);
mbedtls_entropy_context entropy; //Used to seed the drbg
mbedtls_entropy_init(&entropy);
mbedtls_ctr_drbg_init(&this->CTR_ctx);
Serial.println("Seeding the random number generator...");
int ret = (mbedtls_ctr_drbg_seed(&this->CTR_ctx, mbedtls_entropy_func, &entropy, NULL, 0)) != 0;
if (ret)
{
Serial.print("Error in seeding the random number generator\nmbedtls_ctr_drbg_seed returned: ");
Serial.print(ret);
return RSABooleanFalse;
//ESP_LOGE(TAG, "mbedtls_ctr_drbg_seed returned %d", ret);
}
return 0;
};
mbedtls_ctr_drbg_context get_CTRX_context(){
return CTR_ctx;
}
};
class RSACryptographer : public Cryptographer{
protected:
mbedtls_pk_context RSA_ctx;
public:
RSACryptographer()
{
}
~RSACryptographer(
){}
/**
*
* @param inputArray : The array which we would like to do our encryption/decryption on
* @param inputLen
* @param outputArray
* @param outLen : The length of the result (not the size of the outputArray)
* @param isEncryption : 0 if we're decrypting, 1 if we're encrypting
* @return 0 if succesfull, otherwise a specified error code
*/
int use_key(unsigned char * inputArray, size_t inputLen, unsigned char * outputArray,size_t outSize, size_t * outLen, int isEncryption){
int res;
//The input is larger than what our encryption algorithm can handle
if(inputLen>RSA_MAX_INPUT_LEN){
Serial.print("ERROR: INPUT: \n");
println_unsignedString(inputArray,inputLen,CHR);
Serial.print("EXCEEDS MAX ENCRYPTION INPUT LEN: ");
Serial.print(RSA_MAX_INPUT_LEN);
return RSA_ERR_INPUT_EXCEEDS_MAX_LEN;
}
if(isEncryption) {
res = mbedtls_pk_encrypt(&this->RSA_ctx, inputArray, inputLen, outputArray, outLen,
outSize, mbedtls_ctr_drbg_random, &this->CTR_ctx);
}
else{
res = mbedtls_pk_decrypt(&this->RSA_ctx, inputArray,inputLen, outputArray, outLen, outSize ,mbedtls_ctr_drbg_random,&this->CTR_ctx);
}
//If our output is larger than the array
if(*outLen>outSize){
Serial.println("ERROR: Output length exceeds size of the outputArray");
Serial.println(*outLen);
Serial.println(outSize);
return RSA_ERR_OUTPUT_EXCEEDS_OUTPUT_ARRAY_LEN;
}
return res;
}
int encrypt(unsigned char * inputArray, size_t inputLen, unsigned char * outputArray,size_t outSize, size_t * outLen){
return use_key(inputArray, inputLen, outputArray, outSize, outLen, 1);
}
int decrypt(unsigned char * inputArray, size_t inputLen, unsigned char * outputArray,size_t outSize, size_t * outLen) {
return use_key(inputArray, inputLen, outputArray, outSize, outLen, 0);
}
int generate_key(){
mbedtls_pk_init(&RSA_ctx);
int ret = mbedtls_pk_setup(&RSA_ctx, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA));
Serial.println("Initializing key context...");
ESP_LOGI(TAG_MAIN, "Initializing key context...");
if (ret != 0)
{
ESP_LOGE(TAG_WORDS, "mbedtls_pk_info_from_type returned %d", ret);
return RSABooleanFalse;
}
Serial.println("Generating the private key...");
ret = mbedtls_rsa_gen_key(mbedtls_pk_rsa(RSA_ctx), mbedtls_ctr_drbg_random, &CTR_ctx, pubKeyLen, RSAPubKeyEXPONENT);
if (ret != 0)
{
ESP_LOGE(TAG_WORDS, "mbedtls_rsa_gen_key returned %d", ret);
return RSABooleanFalse;
}
return 0;
}
int validate_key(){
return mbedtls_pk_check_pair(&this->RSA_ctx,&this->RSA_ctx);
}
mbedtls_pk_context get_RSA_context(){
return this->RSA_ctx;
}
};
And the code I am running/main:
#include <esp_spiffs.h>
#include "Arduino.h"
#include "Cryptography/CryptographicSettings.cpp"
#include "Cryptography/RSAEncryption.h"
#include "esp_dsp.h"
#include "SPIFFS.h"
#include "Flash/CustomSPIFFS.h"
#include "Flash/FileManager.cpp"
#include "Flash/FileManager.h"
#include "FS.h"
#include "sha/sha_parallel_engine.h"
#include "TinyGPSPlus-master/src/TinyGPS++.h"
#include "stddef.h"
#include "Utility.h"
#include "Cryptography/Cryptographer.h"
#include "Cryptography/Cryptographer.cpp"
#include "HardwareMacros.h"
static const char* TAG_main = "Main";
const char * RSAPubKeyPath = "/RSAPubKey";
const char * RSAPrivKeyPath = "RSAPrivKey";
unsigned char PEMKeyBuffer[256];
unsigned char pubKey[pubKeyLen];
unsigned char a[2] = "a";
unsigned char outputBuf[SHA256_OUTPUT_BUFFERLEN];
unsigned char ID[IDLen];
unsigned char outputBufDecrypt[SHA256_OUTPUT_BUFFERLEN];
/**
* Finds the size of an RSA key formatted in PEM format
*
* \arg PEMBUFFER: An unsigned char containing the PEM file in the format
* {PEM_EMPTY_PLACEHOLDER,...,PEM_EMPTY_PLACEHOLDER,PEMFILE....,\0}
* Where PEM_EMPTY_PLACEHOLDER indicates that the value is not a part of the actual PEM file.
*
* Notice, that there can be nothing inbetween the start of the PEMFILE and the end of the array.
*
* \return
* The size of the PEMFILE or PEM_ERR_NO_PEM_FILE if there is no PEM file
*/
int findStartingIndexPEMFile(unsigned char * PEMBuffer,size_t sizeOfBuffer){
for(int i = 0; i<sizeOfBuffer;i++){
unsigned char c = PEMBuffer[i];
//We've reached the end of the array without encountering anything but PEM_EMPTY_PLACEHOLDERS
if(c == '\0'){
return PEM_ERR_NO_PEM_FILE;
}
if(c != PEM_EMPTY_PLACEHOLDER){
return i;
}
}
return PEM_ERR_NO_PEM_FILE;
}
void setup(){
Serial.begin(9600);
int res = 0;
Serial.println("___________________PROGRAM_START_________________________"); //We get a lot of fluffer at the start of the program
//SPIFFS
FileManager * spiff = new SPIFFSFileManager();
auto * rsaCryptographer = new RSACryptographer();
if(rsaCryptographer->generate_CTRX_context() != 0){
Serial.println("Error generating CTR");
}
if(rsaCryptographer->generate_key() != 0){
Serial.println("Error");
}
if(rsaCryptographer->validate_key() != 0){
Serial.println("Error");
}
unsigned char inputArray[22];
unsigned char outputArray[4000];
size_t oLen;
fill_alphanumeric_unsignedString(inputArray,sizeof(inputArray));
println_unsignedString(inputArray,sizeof(inputArray),CHR);
res = rsaCryptographer->encrypt(inputArray,sizeof(inputArray),outputArray,sizeof(outputArray),&oLen);
if(res != 0){
Serial.println("Error encrypting");
Serial.println(res);
}
println_unsignedString(outputArray,sizeof(inputArray),CHR);
//fill_alphanumeric_unsignedString(outputArray,sizeof(outputArray));
rsaCryptographer->decrypt(outputArray,oLen,outputArray,sizeof(outputArray),&oLen);
//mbedtls_pk_encrypt(&key,(const unsigned char *)inputArray,sizeof(inputArray),outputArray,&oLen,sizeof(outputArray),mbedtls_ctr_drbg_random,&ctr_drbg);
println_unsignedString(outputArray,oLen,CHR);
};
void loop(){
};
CryptographicSettings file:
//
// Created by DripTooHard on 03-04-2023.
//
//Truth values for our RSA functions
//They're defined as such, because the mbedtls libraries define 0 as success
#define RSABooleanTrue 0
#define RSABooleanFalse 1
//RSA related
#define RSAPubKeyEXPONENT 65537
#define pubKeyLen 256 //2048 bits
#define RSA_MAX_INPUT_LEN 245 //245 bytes
//RSA related errors
#define RSA_ERR_INPUT_EXCEEDS_MAX_LEN -69
#define RSA_ERR_OUTPUT_EXCEEDS_OUTPUT_ARRAY_LEN -68
//Identification/Network related
#define IDLen 20 //How many of the chars we take from the hash
//If we iterate over a PEM file and it's "7"
#define PEM_EMPTY_PLACEHOLDER 7
#define PEM_ERR_NO_PEM_FILE -69
//Hashing related
#define SHA256_OUTPUT_BUFFERLEN 32 //256 bits
Ps. This is some messy code, because we're just building the objects one at a time. No need to help me with structuring the code or messy stuff like linking a .cpp file ;)
At first I thought this was a problem with the size of the output array, but after having set it to 4k bytes, I am fairly certain, this must not be the case. Unless I am using some very weird padding scheme.
You define a constant which is the size of signatures and ciphertexts made with the key, in bytes.
#define pubKeyLen 256 //2048 bits
But then you pass that constant to a function that expects the size of the key as a cryptographic (mathematical) objects. That's a number of bits.
ret = mbedtls_rsa_gen_key(mbedtls_pk_rsa(RSA_ctx), mbedtls_ctr_drbg_random, &CTR_ctx, pubKeyLen, RSAPubKeyEXPONENT);
So you're creating a 256-bit key. That's far too small for an RSA key: these days 2048 bits is the absolute minimum recommended, and even in the ancient days when RSA was just started and computers were less powerful, 321-bit RSA was known to be too short. But it's mathematically valid and unfortunately mbedtls lets you shoot yourself in the foot.
In general, the sizes of memory buffers are expressed in bytes, but the sizes of numbers and other mathematical objects are expressed in bits — and the sizes of arrays are expressed in elements. When dealing with cryptography, concepts like “key size” are usually expressed in bits, because they're the size of some underlying number. For RSA, that number is the public modulus (n), which is one of the two numbers that make up the public key.
As a general programming recommendation, make sure variable names clearly express the unit. For example, don't call this constant pubKeyLen
, call it pubKeyBytes
. Or better, since this is not actually the number of bytes of the public key in any sense (the public key encodes both the modulus and the public exponent, so it needs a few more than 256 bytes for a 2048-bit RSA key), call it by what it actually is, for you: ciphertextBytes
.
const size_t ciphertextBytes = 256;
const size_t pubKeyBits = 8 * ciphertextBytes;
On a low-end platform, I would recommend using elliptic curve cryptography rather than RSA to do hybrid encryption. ECC private key operations are faster than RSA, and the ciphertexts are smaller. The standard way to do hybrid encryption with ECC is ECIES. Mbed TLS doesn't have an ECIES API, but it can do ECIES: the asymmetric part is ECDH. From your private key and the public key of the other party, you take the ECDH shared secret (output of mbedtls_ecdh_calc_secret
), hash it with SHA-256 (keeping it simple because you only need one output), and use the first 16 bytes as an AES key.