0

I am trying to implement AES-128 and AES-256 on an Arduino (Adafruit Feather M0, for any other people using SAMD21 processors!). The encryption and decryption is working, but I am unable to “save” the encrypted value. I believe the example passes a pointer for a char array to void encrypt(), but when using strcpy, strncpy or memcpy to copy over the value from the local char array to the one back in my loop(), the value never actually gets copied over.

Note that the hang only occurs in the void encrypt() method and I wonder if it is due to the encode_base64 line where the example code is casting the data as unsigned char*. I’ve been able to use strcpy, strncpy and memcpy successfully in void decrypt() so all I can think is that it's the unsigned char type.

Though according to this a char is ultimately treated like an unsigned char in standard libraries, which I assume that the strcpy function is part of the standard string library and not something special to Arduino.

I am very new to C/C++ and am nowhere near an expert. I am not quite sure how to get around this issue so I am hoping someone can point me in the right direction.

Code (I included links to the libraries at the top for each #include)

#include <Crypto.h>   // https://github.com/intrbiz/arduino-crypto
#include <base64.hpp> // https://github.com/Densaugeo/base64_arduino


#define BLOCK_SIZE 16

uint8_t key[BLOCK_SIZE] = { 0x1C,0x3E,0x4B,0xAF,0x13,0x4A,0x89,0xC3,0xF3,0x87,0x4F,0xBC,0xD7,0xF3, 0x31, 0x31 };
uint8_t iv[BLOCK_SIZE] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
char plain_text[] = "1234567890ABCDEF1234567890ABCDEF";

void bufferSize(char* text, int &length)
{
    int i = strlen(text);
    int buf = round(i / BLOCK_SIZE) * BLOCK_SIZE;
    length = (buf <= i) ? buf + BLOCK_SIZE : length = buf;
}

void encrypt(char* plain_text, char* output, int length)
{
    byte enciphered[length];
    // RNG::fill(iv, BLOCK_SIZE); // Using fixed test iv
    AES aesEncryptor(key, iv, AES::AES_MODE_128, AES::CIPHER_ENCRYPT);
    aesEncryptor.process((uint8_t*)plain_text, enciphered, length);
    int encrypted_size = sizeof(enciphered);

    char encoded[encrypted_size];
    encode_base64(enciphered, encrypted_size, (unsigned char*)encoded);

    Serial.print("void encrypt :: Encrypted: ");
    Serial.println(encoded);

    // strcpy(output, encoded); //- Hangs
    // strncpy(output, encoded, strlen((char*)encoded)); - Hangs
    // memcpy(output, encoded, strlen((char*)encoded)); - Hangs
}

void decrypt(char* enciphered, char* output, int length)
{
    length = length + 1; //re-adjust

    char decoded[length];
    decode_base64((unsigned char*)enciphered, (unsigned char*)decoded);
    bufferSize(enciphered, length);
    byte deciphered[length];
    AES aesDecryptor(key, iv, AES::AES_MODE_128, AES::CIPHER_DECRYPT);
    aesDecryptor.process((uint8_t*)decoded, deciphered, length);

    Serial.print("void decrypt :: Decrypted: ");
    Serial.println((char*)deciphered);

    strcpy(output, (char*)deciphered);
    // strncpy(output, (char*)deciphered, strlen((char*)deciphered));
    // memcpy(output, (char*)deciphered, strlen((char*)deciphered));
}

void setup() {
    Serial.begin(115200);
    while (!Serial) {
      ; //wait
    }

    Serial.println("AES128-CBC Test :: Starting...");
    Serial.print("Plaintext input "); Serial.println(plain_text);
}

void loop() {

  Serial.println(" = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = \n");

  // Encrypt
  int length = 0;
  bufferSize(plain_text, length);
  // Serial.print("Buffer length: ");
  // Serial.println(length);

  char encrypted[128];
  encrypt(plain_text, encrypted, length);

  // Serial.println("");
  Serial.print("RETURNED Encrypted Value: ");
  Serial.println(encrypted);


  // Decrypt
  length = 128; // WAS strlen(encrypted);
  char decrypted[length];
  char testEncryptedPayload[] = "pJUX0k/h/63Jywlyvn7vTMa9NdJF9Mz6JOB1T1gDMq+0NUkNycBR780kMvCYILGP"; // Added for testing purposes

  decrypt(testEncryptedPayload, decrypted, length);

  Serial.print("RETURNED Decrypted Value: ");
  Serial.println(decrypted);

  delay(5000);
}

/*
EXAMPLE FROM DOCUMENTATION => loop()

void loop() {
  char plain_text[] = "1234567890ABCDEF1234567890ABCDEF";

  // Encrypt
  int length = 0;
  bufferSize(plain_text, length);
  char encrypted[length];
  encrypt(plain_text, encrypted, length);

  Serial.println("");
  Serial.print("Encrypted: ");
  Serial.println(encrypted);

  // Decrypt
  length = strlen(encrypted);
  char decrypted[length];
  decrypt(encrypted, decrypted, length);

  Serial.print("Decrypted: ");
  Serial.println(decrypted);

  delay(5000);
}

*/

Sample Output:

AES128-CBC Test :: Starting...
Plaintext input 1234567890ABCDEF1234567890ABCDEF
 = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

void encrypt :: Encrypted: pJUX0k/h/63Jywlyvn7vTMa9NdJF9Mz6JOB1T1gDMq/eQVoPjf/UYv+9SuzV8LQa
RETURNED Encrypted Value: L
void decrypt :: Decrypted: 1234567890ABCDEF1234567890ABCDEF
RETURNED Decrypted Value: 1234567890ABCDEF1234567890ABCDEF
 = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
user5156141
  • 655
  • 9
  • 29
  • 4
    You seem to forget one very important thing about `char` strings: They are really called ***null-terminated** byte strings*. That *null-terminated* thing is what makes all string functions work, since they look for the null-terminator to know the end. – Some programmer dude Apr 28 '18 at 06:46
  • Fyi, `byte deciphered[length]` - that's a bad idea even in full-blown C++ implementations. It's not standard; use a vector. Likewise for `byte enciphered[length]` and `char encoded[encrypted_size];`. Also, one of those, `enciphered`, is possibly sized wrong anyway. A base64 encoding will have *at least* 4/3 the size of the input data. – WhozCraig Apr 28 '18 at 06:51
  • 1
    @WhozCraig Dynamic memory allocation on a micro controller? Not necessarily the best idea either... Admitted, VLA, being extension or not, can impose similar problems... – Aconcagua Apr 28 '18 at 06:53
  • `memcpy` definitely is not your problem - it accepts *anything* (`void*`) and copies as many bytes as you define, no matter what the content is. If you did copy too few or many bytes, it is due to bad number given... – Aconcagua Apr 28 '18 at 06:59
  • Thanks for the responses so far! @Someprogrammerdude I have already attempted the adding of '\0' - had no effect at pos 0 or encrypted_length-1. – user5156141 Apr 28 '18 at 07:13
  • @WhozCraig I will certainly research vectors as I have used them in other sketches. Though - I am still trying to get the example working before I can go and improve parts. I don't like dynamic memory allocation either, thankfully I have a fairly large MCU to try this on, but I will be calculating max lengths and hard coding them. The AES encryption is only going to be used for 16 char input strings – user5156141 Apr 28 '18 at 07:14
  • @Aconcagua When you say bad number, I am not manually calculating the max lengths, I am using sizeof() and strlen(), I have tried so many things my brain is a little fried. I will take a fresh look at the entire example later today – user5156141 Apr 28 '18 at 07:17
  • @user5156141 `strlen` is definitely wrong for encoded data that might contain intermediate zero bytes, as it would stop at the first such occurence and then discard the rest of the data. – Aconcagua Apr 28 '18 at 08:24
  • @Aconcagua So putting long array with random size on stack is OK in an embedded environment? Stack overflow is not likely to happen? I would rather investigate custom allocators for the special case of embedded systems. – Red.Wave Apr 28 '18 at 14:18
  • @Red.Wave I'd just use some static global memory with a maximum size. No problems with stack, no problems with dynamic allocation. Just need to handle the case that input possibly is too long... – Aconcagua Apr 28 '18 at 14:21
  • @Red.Wave Specifically for AES: As it is a block cipher, we should be able to send out the already encrypted block immediately and discard it right afterwards, so the memory gets free for subsequent input and we drop even this last limitation... – Aconcagua Apr 28 '18 at 14:24
  • @Aconcagua That would involve sink and source buffering, setting up the specific io registers and finally, manually crafting CBC out of ECB(ie. avoidind CBC api and using corresponding ECB counterparts). – Red.Wave Apr 28 '18 at 14:51
  • @Aconcagua my last phrase of course, depends on the crytpo lib used. – Red.Wave Apr 28 '18 at 15:01
  • @Red.Wave I have already tried moving the `encrypted` array with a fixed size of 128 to the top to make it global. Still have the exact same issue with the use of strcpy etc. – user5156141 Apr 28 '18 at 20:45
  • While the last line of `bufferSize()` is probably doing what you want, it's probably by accident. The rightmost expression in the ternary should just be `buf` not `length = buf`. – user1118321 Apr 28 '18 at 21:21
  • Could it be a resource problem? What is your RAM budget? An instance of class "AES" takes up more than 500 bytes and all the fixed strings in the program adds up to more than 400 bytes. Note: Even constant strings that are just passed to Serial.print() are copied to RAM at program startup. Keeping the constant strings in flash (program) memory requires explicit programming (see [source 1](https://stackoverflow.com/a/44500475/63550)). Today I shaved off 508 bytes of RAM by moving the longest constant strings into flash (for an Arduino Leonardo based macro keyboard). – Peter Mortensen Mar 16 '20 at 00:18

1 Answers1

0

I have not run the code, but in general it "hangs" with the M0 and has all been memory-related in my experience.

These two lines:

// strncpy(output, encoded, strlen((char*)encoded)); - Hangs
// memcpy(output, encoded, strlen((char*)encoded)); - Hangs

Perhaps what is actually failing is the strlen. Maybe encoded is not NULL terminated? Can you try with the actual length? (for example, memcpy(output, encoded, encoded_length);)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Eugenio Pace
  • 14,094
  • 1
  • 34
  • 43