Strange behavior with variable declaration using AES library

Hello everyone. I was working over some AES for sending RF messages. I declared my message as a byte array, for example: byte data[] = “This string was decoded. Cantbe bigger than 47c”;.
//The current string can’t be bigger than 47 chars because of the library (Radio Head) i’m using.

The thing is, after the AES library does its work I get a cipher array and I send it by RF, but, depending on where my data string is declared, that cipher string will get a different result under strlen() evaluation and then crash. The two places where I’m declaring are at the beginning of file and in the upper line before calling the function.

So, my question is: Will declaring a variable outside function or inside function change its visibility from a sibling or child function? In next code I give an example of what I see is happening in my code, if I switch the position of variable “something”, the strlen will be different, no matter if the content of “something” is the same:

//byte something[] = "Algo";

void mainfunction(){
  byte something[] = "Algo";

  function2(something);
  }

void function2(data){
  byte result[] = do.something.with.data;
  strlen(result); <--- Here I see the difference
  }

Of course, here is the real code I’m using. It requires RadioHead (http://www.airspayce.com/mikem/arduino/RadioHead/RadioHead-1.89.zip) library and AES (https://github.com/spaniakos/AES) library and it uses two arduinos connected this way: Arduino1 D12 <-> Arduino2 D11, Arduino1 D11 <-> Arduino2 D12, Arduino1 GND <-> Arduino2 GND. To test you don’t need to use the second arduino, just the master one and change where to declare data variable.

master.ino

#include <RH_ASK.h>
#include <AES.h>

//radiohead has a limit to send of 60 characters

AES aes;
byte *key = (unsigned char*)"0123456789010123"; //this key must be unique and secretly shared
unsigned long long int my_iv = 36753562;//this initialization vector can be const, but the same between devices

byte data[] = "This string was decoded. Cantbe bigger than 47c";//the string we are going to send

int plainLength = 0;  // don't count the trailing /0 of the string !
int padedLength = 0;

RH_ASK driver; //initializing an instance of RF driver, both RX and TX being RX on pin 11 and TX on pin 12

void setup()
{
  Serial.begin(9600);   // Debugging only
  if (!driver.init())
    Serial.println("init failed");
}

void loop()
{ 
  //byte data[] = "This string was decoded. Cantbe bigger than 48a";//the string we are going to send
  
  byte data_l = strlen(data);
  send_encrypted(data,128,data_l);
  
  delay(2000); 
}

void send_encrypted(char* data,byte bits, byte length){
  plainLength = length-1;  // don't count the trailing /0 of the string !
  Serial.println(plainLength);
  padedLength = plainLength + N_BLOCK - plainLength % N_BLOCK;
  aes.iv_inc();
  byte iv [N_BLOCK] ;
  byte plain_p[padedLength];
  byte cipher [padedLength];
  unsigned long ms = micros ();
  aes.set_IV(my_iv);
  aes.get_IV(iv);
  aes.do_aes_encrypt(data,plainLength,cipher,key,bits,iv);
  
  Serial.print("Ciphered here:");
  char result[1];
  #define printable cipher
  for (byte i = 0; i < sizeof(printable); ++i){
    sprintf(result,"%c",printable[i]);
    Serial.print(result);
    }
  Serial.println("===endof");
  Serial.println(strlen(cipher));
  driver.send((uint8_t *)cipher,strlen(cipher));
  driver.waitPacketSent();  
}

slave.ino

#include <RH_ASK.h>
#include <SPI.h> // Not actualy used but needed to compile
#include <AES.h>

AES aes;
byte *key = (unsigned char*)"0123456789010123"; //this key must be unique and secretly shared
unsigned long long int my_iv = 36753562; //this initialization vector can be const, but the same between devices


RH_ASK driver; //instance for the RF driver object

void setup()
{
    Serial.begin(9600); // Debugging only
    if (!driver.init())
         Serial.println("init failed");
    delay(500);
}

void loop()
{
    uint8_t buf[128]=""; //it is equal to null for cleaning the spaced used for variable
    uint8_t buflen = sizeof(buf); //just saving its size
    if (driver.recv(buf, &buflen)) // Non-blocking
    {
      
      // Message with a good checksum received, dump it.
      //byte result [128] = ""; //variable not being used
      aes_decrypt(buf,128,buf); //here im rewriting the entry string, but not sure if it is a good idea. Actually works fine

      Serial.print(F("Message decoded:")); //just some printing
      char buffer1[1]; //this buffer is for reading the char string
      #define printable buf //for fast changing
      for (byte i = 0; i < sizeof(printable); ++i){
        sprintf(buffer1,"%c",printable[i]); //convert bytes to char
        Serial.print(buffer1); //print char, could be saved too
        }
      Serial.println(F("===endofmessage"));//just some printing
    }

    
}

/*
 * Here data is the encrypted data received, bits is just the expected size of AES, result is a pointer to the
 * string where the decrypted data should be saved
 */
void aes_decrypt(char* data, byte bits, byte* result) //fucntion for decryption
{
  int plainLength = strlen(data)-16; //for some reason, sended data is equal to string size minus block size+1. need to check library for undestand
  int padedLength = plainLength + N_BLOCK - plainLength % N_BLOCK; //the normal padded calculus
  aes.iv_inc(); //start of aes?
  byte iv [N_BLOCK] = ""; //clean array to avoid old variables contamination (sometimes, the reasigned pointer points to the same 
  //place is stack, so is like recylcing variables, thats why it is declared and initialized with empty spaces)

  aes.set_IV(my_iv); //setting initial vector
  aes.get_IV(iv); //getting it
  aes.do_aes_decrypt(data,padedLength,result,key,bits,iv);//decrypting
}

Being sincere, I know when I’m using a variable declared out of scope, but this array’s pointer is being passed as a parameter. I can’t get what is going on.

plainLength = length-1;  // don't count the trailing /0 of the string !

Hum, did not read in details. But strlen() does not count the trailing zero. sizeof() would

(and make up your mind for using char or byte for cohérence - given you use c-string, char makes more sense)

This is a very small bufferchar buffer1[1];in which you won’t be able to fit the trailing null of that cstring sprintf(buffer1,"%c",printable[i]); //convert bytes to char (buffer overflow, all bets are off).

As a side note, why do you need sprintf at all for a char as you have it already in printable[i]

J-M-L:
Hum, did not read in details. But strlen() does not count the trailing zero. sizeof() would

Yes! I was just about to write it. My main error was to do an strlen() over a byte array, which didn't had a null terminator. So it was measuring the variable and all the stack in memory until finding something null. It worked when the variable was in the upper place because it had another variables after it giving the chance of finding a null terminator. (or i think so)

My last question: If I do strlen() over a byte array like byte array[] = "1234" will it give me a result of 4 or unpredictable. I mean, that last byte is decoded by strlen and readed as a null o just a number.

J-M-L:
(and make up your mind for using char or byte for cohérence - given you use c-string, char makes more sense)

Actually it is because the function on AES library is strictly specting a byte array and I can't modify the library, so just give it that way.

J-M-L:
This is a very small bufferchar buffer1[1];in which you won’t be able to fit the trailing null of that cstring sprintf(buffer1,"%c",printable[i]); //convert bytes to char (buffer overflow, all bets are off).

As a side note, why do you need sprintf at all for a char as you have it already in printable[i]

You're right, it had to be just a normal char and just copy it, my mistake.

anxietyscroll:
. My main error was to do an strlen() over a byte array, which didn’t had a null terminator.

No - your main error is actually treating ciphertext generated by AES as characters. It is not. It is binary and can have a zero as a legitimate part of the ciphertext at any point. For example, valid ciphertext might be

byte ciphertext = {1, 20, 3, 0, 33, 6, 0, 91, 21, 2, 0, 3,3,3,0,3};

In this all the zeros are part of the ciphertext. AES ciphertext will be a multiple of 16 bytes and strlen() gives you the position of the first zero which can be any value at all ! For the above example it would return 3 even though the ciphertext length is actually 16 !

anxietyscroll:
My last question: If I do strlen() over a byte array like byte array[] = "1234" will it give me a result of 4 or unpredictable. I mean, that last byte is decoded by strlen and readed as a null o just a number.

This:

byte array[] = "1234";

Is not a byte array. It's a cstring (assigned to the wrong variable type). It has 4 bytes representing ASCII characters and a terminating null. strlen(array) will always return 4. But, the compiler will (should) complain that you've passed strlen the wrong type (should be 'const char *', not 'byte *').

This:

byte array[] = {1, 2, 3, 4};

Is an array of bytes. It contains 4 bytes with the binary values 1, 2, 3, 4. The next value is indeterminate. strlen(array) will return unpredictable values. AND the compiler will complain that you've passed strlen the wrong type.

gfvalvo:
Is not a byte array. It's a cstring (assigned to the wrong variable type).

Does this means the compiler is translating the cstring to bytes or changing the byte type to the correct char type?

anxietyscroll:
Does this means the compiler is translating the cstring to bytes or changing the byte type to the correct char type?

defining a type and the syntax (use of double quotes for example) helps the compiler do the right thing with your data but everything ends up in bits and bytes...

when you write [color=red]"[/color]Hello World[color=red]"[/color] you have defined a character string aka cString.

In C or C++ programming, the collection of characters defined this way is stored in the form of arrays and are arrays of type char terminated with null character, that is, '\0' (ASCII value of null character is 0).

so [color=red]"[/color]Hello World[color=red]"[/color] is represented for the compiler as a char array with each byte filled in with the ASCII code of consecutive letter and with an added trailing NULL char.

if you write byte message[] = [color=red]"[/color]Hello World[color=red]"[/color]; the compiler starts by creating the char array with the trailing NULL character, and then cast this array into an array of unsigned char or uint8_t (the byte type).

so your message variable is of type array of byte, and initialized with the array containing all the bytes from the cString including the trailing NULL char.

the function strlen() applies to cStrings, and will count the number of bytes all the way to - but not including - the trailing NULL char.

the operator sizeof() will give you the amount of space reserved by the compiler to represent the data.

So if you ask forstrlen(message);you'll get 11 because that's the number of bytes used to represent the cString, but if you ask sizeof(message); you'll get 12 because the compiler allocated the trailing NULL char and so the size of that array does take into account the extra byte you don't see.

Note that because you changed the type of your cString to an array of bytes, the compiler will complain with a

warning: invalid conversion from 'byte* {aka unsigned char*}' to 'const char*'

message.

in a nutshell: use strlen() only for cStrings. use sizeof() to know the number of bytes used to represent the parameter (that the compiler knows about).

here is a small code to test:

byte message[] = "Hello World";
void setup() {
  Serial.begin(115200);
  Serial.print("STRLEN -> "); Serial.println(strlen(message));
  Serial.print("SIZEOF -> "); Serial.println(sizeof(message));
}

void loop() {}

Not to be confused Strings - with a capital S - are objects (instance of a class) that represent sequences of characters.

Bottom Line - assign stuff to the correct variable type and the compiler won't complain.

gfvalvo:
Bottom Line - assign stuff to the correct variable type and the compiler won't complain.

Bottom line is that using strlen() on AES ciphertext is just plain wrong since a zero byte can occur in any byte of the ciphertext as part of the ciphertext and it does not indicate the end of the ciphertext !

Thanks J-M-L, incredible explanation.

stowite:
Bottom line is that using strlen() on AES ciphertext is just plain wrong since a zero byte can occur in any byte of the ciphertext as part of the ciphertext and it does not indicate the end of the ciphertext !

Yes, stowie, since one can never know what kind of data will be the encryption result, doing strlen is like throwing stones up and expecting never being hit. Thanks!