This article will discuss the implementation of an AES-encrypted HTTP GET request carried between an Arduino (ESP32) device and C# ASP.NET.
Image: The ESP32 module
Both Arduino and C# have AES encryption libraries available for use. Therefore, messages can be encrypted during data transmission between them.
I had experienced with AES encryption in the past, I wrote an article about the AES 256 bit implementation in C#. Below is the link to that article:
However, this is the first time that I use AES in Arduino project. If my understanding is correct, the basic foundation of the AES algorithm is implemented the same way in both programming languages (C/C++ and C#). So, there should be no problem to encrypt it in C/C++ and decrypt it in C#, and vice versa encrypt in C# and decrypt in C/C++.
Okay, Let's Start
There are five elements (parameters) that will affect how AES works. Let's identify them:
- Cipher Mode
- Block/Key size (128 bits, 192 bits, 256 bits)
- Key bytes
- IV bytes (Initial Vector)
- Padding Mode
By setting correctly on both sides (C# and C/C++), they should produce the same result.
First, Cipher Mode
Decompiling the C# assemblies, the following is obtained:
namespace System.Security.Cryptography
{
[Serializable]
[ComVisible(true)]
public enum CipherMode
{
CBC = 1,
ECB,
OFB,
CFB,
CTS
}
}
CBC is the default Cipher Mode used in C# AES.
Okay, let's explore the AES Cipher Mode in Arduino.
After some searching, I found and installed a library called AESLib
provided by Matej Sychra.
Arduino Reference Page: https://www.arduino.cc/reference/en/libraries/aeslib/
Author's Github: https://github.com/suculent/thinx-aes-lib
The Key/Block Size and Cipher Mode
According to description, it uses key size of 128 bits and CBC Cipher Mode.
The KEY and IV
Since AESLib
uses a 128-bit key size, this means the Key
and IV
will consist of 16 bytes. 16 bytes equals 128 bits. Each byte can represent any number ranging from 0 to 255.
Random 16 bytes, Okay, this is easy. Example:
C# (ASP.NET)
byte[] aesKey = { 48, 85, 92, 36, 73, 111, 127, 18, 64, 38, 54, 89, 72, 105, 78, 15 };
byte[] aesIv = { 145, 33, 51, 67, 24, 173, 52, 209, 63, 42, 13, 115, 220, 3, 8, 10 };
C/C++ (Arduino)
byte aesKey[] = { 48, 85, 92, 36, 73, 111, 127, 18, 64, 38, 54, 89, 72, 105, 78, 15 };
byte aesIv[] = { 145, 33, 51, 67, 24, 173, 52, 209, 63, 42, 13, 115, 220, 3, 8, 10 };
What are the KEY
and IV
doing?
- The
KEY
: It's the secret password used to scramble (encrypt) and unscramble (decrypt) your data. - The Initial Vector (
IV
): This is typically a random number used to ensure that the same data doesn't look the same when encrypted twice. However, we'll be hard-coding the IV
in our case, so the same data will look identical each time it's encrypted.
During the encryption process, the original bytes (or text) will be separated into multiple blocks. Both the KEY
and IV
influence the outcome of the scrambling of each block.
When decrypting (reversing/unscrambling) the blocks, the same KEY
and IV
must be used to reveal the original context, or else you'll get a jumble of alien language!
After a block is completely scrambled, a new IV
(a Vector) will be generated for influencing the scrambling of the next block by following a specific pattern from the previous data block. The same KEY
will always be used for each block.
The KEY
, often referred to as the Password
, is typically provided by the end user (whoever is using the software). No one, including you (the developer), will know what the KEY
data is that can reverse the scrambling. (That is, unless you save the user's 'Password' in the database).
As for the IV
, it's usually hard-coded within the software. Only you (the developer) will know what the IV
data is. (Unless, of course, the source code is leaked or someone reverse-engineers or decompiles the software binary code).
Let's use this analogy: The KEY
is like the first password (public), and the IV
is the second password (private).
However, in this Arduino project, both 'passwords' (KEY
and IV
) will be hard-coded and kept private.
Padding Mode
The final element that we'll be examining is the 'Padding Mode'.
In C#, the available padding modes are as follows:
namespace System.Security.Cryptography
{
[Serializable]
[ComVisible(true)]
public enum PaddingMode
{
None = 1,
PKCS7,
Zeros,
ANSIX923,
ISO10126
}
}
PKCS7
is the default padding mode used by C#'s AES.
However, in the Arduino AESLib
, upon exploring the source code provided by the author (found here: https://github.com/suculent/thinx-aes-lib/blob/master/src/AES.h), the available padding modes are listed as follows (see line 41):
enum class paddingMode {
CMS,
Bit,
ZeroLength,
Null,
Space,
Random,
Array
};
Interestingly, these don't seem to directly match what's available in C#.
Then, I continued to explore another C++ source file: https://github.com/suculent/thinx-aes-lib/blob/master/src/AES.cpp.
At line 442:
...
* CMS (Cryptographic Message Syntax).
* This pads with the same value as the number of padding bytes.
* Defined in RFC 5652, PKCS#5, PKCS#7 (X.509 certificate) and RFC 1423 PEM.
...
The keyword PKCS#7
is mentioned in the description of the CMS
padding mode, which aligns with the PKCS7
padding mode in C#. Therefore, we'll select the CMS
padding mode for implementation in Arduino C/C++.
Till here, all parameters are identified.
- Cipher Mode: CBC
- Block/Key size: 128 bits
- Key: 16 bytes
- IV: 16 bytes
- Padding Mode: PKCS7 in C#, CMS in Arduino C/C++
Let's Begin the Coding
I used a library called base64_encode
provided by dojyorin, to perform base64
encoding.
Let's start from the encryption in Arduino C/C++. Code explanation is provided in the comments within the lines of code:
#include "AESLib.h"
#include "arduino_base64.hpp"
AESLib aesLib;
String encrypt(String inputText) {
int bytesInputLength = inputText.length() + 1;
byte bytesInput[bytesInputLength];
inputText.getBytes(bytesInput, bytesInputLength);
int outputLength = aesLib.get_cipher_length(bytesInputLength);
byte bytesEncrypted[outputLength];
byte aesKey[] = { 23, 45, 56, 67, 67, 87, 98, 12, 32, 34, 45, 56, 67, 87, 65, 5 };
byte aesIv[] = { 123, 43, 46, 89, 29, 187, 58, 213, 78, 50, 19, 106, 205, 1, 5, 7 };
aesLib.set_paddingmode((paddingMode)0);
aesLib.encrypt(bytesInput, bytesInputLength, bytesEncrypted, aesKey, 16, aesIv);
char base64EncodedOutput[base64::encodeLength(outputLength)];
base64::encode(bytesEncrypted, outputLength, base64EncodedOutput);
return String(base64EncodedOutput);
}
The C# version of encryption:
using System.Security.Cryptography;
static byte[] aesKey = { 23, 45, 56, 67, 67, 87, 98, 12, 32, 34, 45, 56, 67, 87, 65, 5 };
static byte[] aesIv = { 123, 43, 46, 89, 29, 187, 58, 213, 78, 50, 19, 106, 205, 1, 5, 7 };
public static string AesEncrypt(string originalText)
{
originalText += "\0";
byte[] data = System.Text.Encoding.UTF8.GetBytes(originalText);
using (Aes aes = Aes.Create())
{
aes.KeySize = 128;
aes.BlockSize = 128;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.Key = aesKey;
aes.IV = aesIv;
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt =
new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
csEncrypt.Write(data, 0, data.Length);
csEncrypt.FlushFinalBlock();
data = msEncrypt.ToArray();
}
}
}
return Convert.ToBase64String(data);
}
Next, the decryption, first, the Arduino version:
String decrypt(String encryptedBase64Text) {
int originalBytesLength = base64::decodeLength(encryptedBase64Text.c_str());
byte encryptedBytes[originalBytesLength];
byte decryptedBytes[originalBytesLength];
base64::decode(encryptedBase64Text.c_str(), encryptedBytes);
byte aesKey[] = { 23, 45, 56, 67, 67, 87, 98, 12, 32, 34, 45, 56, 67, 87, 65, 5 };
byte aesIv[] = { 123, 43, 46, 89, 29, 187, 58, 213, 78, 50, 19, 106, 205, 1, 5, 7 };
aesLib.set_paddingmode((paddingMode)0);
aesLib.decrypt(encryptedBytes, originalBytesLength,
decryptedBytes, aesKey, 16, aesIv);
String decryptedText = String((char*)decryptedBytes);
return decryptedText;
}
The C# version of decryption:
public static string AesDecrypt(string base64str)
{
byte[] data = null;
byte[] encryptedData = Convert.FromBase64String(base64str);
using (Aes aes = Aes.Create())
{
aes.KeySize = 128;
aes.BlockSize = 128;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.Key = aesKey;
aes.IV = aesIv;
ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
using (MemoryStream msDecrypt = new MemoryStream(encryptedData))
{
using (CryptoStream csDecrypt =
new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (MemoryStream originalMemoryStream = new MemoryStream())
{
byte[] buffer = new byte[1024];
int readBytes;
while ((readBytes = csDecrypt.Read(buffer, 0, buffer.Length)) > 0)
{
originalMemoryStream.Write(buffer, 0, readBytes);
}
data = originalMemoryStream.ToArray();
}
}
}
}
string text = System.Text.Encoding.UTF8.GetString(data);
text = text.Remove(text.Length - 1, 1);
return text;
}
Building the Application
First, the ASP.NET Website
Now, let's build the ASP.NET WebForms Application for handling the request sent from Arduino.
In the ASP.NET solution explorer, add two pages:
aes-decrypt.aspx
aes-encrypt.aspx
(You can name it anything.)
If you would like to, you can route the page. By adding a Global.asax into your project and route the pages like this:
protected void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.MapPageRoute("aes-encrypt", "aes-encrypt", "~/aes-encrypt.aspx");
RouteTable.Routes.MapPageRoute("aes-decrypt", "aes-decrypt", "~/aes-decrypt.aspx");
}
and therefore, these pages can be accessed like this:
http://192.168.1.100:8080/aes-encrypt
http://192.168.1.100:8080/aes-decrypt
I've written another article explaining routing in ASP.NET WebForms. Feel free to check it out at this link:
The port 8080 shown above is a custom port I used in my demo project, and this site is run by the Windows IIS Web Server. Since the page only processes HTTP GET requests, you can easily migrate the code to ASP.NET Core, which can also be hosted on Linux.
When you open the front end of both pages (aes-encrypt.aspx and aes-decrypt.aspx), you'll find the following code:
<%@ Page Language="C#" AutoEventWireup="true"
CodeBehind="WebForm1.aspx.cs" Inherits="System.WebForm1" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
</div>
</form>
</body>
</html>
Delete everything, except for the first line:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs"
Inherits="System.WebForm1" %>
Now, edit the code behind for the page aes-encrypt.aspx. This will simulate the action of sending encrypted message to Arduino.
protected void Page_Load(object sender, EventArgs e)
{
try
{
string originalText = "mirror mirror on the wall who is the fairest of them all";
string base64str = aes.AesEncrypt(originalText);
Response.Write(base64str);
}
catch(Exception ex)
{
Response.Write("Error: " + ex.Message);
}
}
Next, edit another page aes-decrypt.aspx. This will simulate the action of receiving encrypted data sent by Arduino:
protected void Page_Load(object sender, EventArgs e)
{
try
{
string base64str = Request.Url.Query;
base64str = base64str.Remove(0, 1);
base64str = Server.UrlDecode(base64str);
string text = aes.AesDecrypt(base64str);
Response.Write(text);
}
catch (Exception ex)
{
Response.Write("Error: " + ex.Message);
}
}
Done for the web server side.
Writing the Code for Arduino
Next, move on to the coding for Arduino. Here, I'm using the ESP32 module, which has WiFi built-in.
#include <WiFi.h>
#include <HTTPClient.h>
#include "AESLib.h"
#include "arduino_base64.hpp"
AESLib aesLib;
void setup() {
}
Inside the function of setup()
, continues the following:
Connecting WiFi
Serial.begin(115200);
String encryptedText = "";
String decryptedText = "";
String url = "";
String wifi_ssid = "your_ssid";
String wifi_password = "your_ssid_pwd";
WiFi.begin(wifi_ssid, wifi_password);
while (WiFi.status() != WL_CONNECTED) {
Serial.println("Connecting WiFi...");
delay(1000);
}
Serial.println("WiFi connected!");
Test 1: Local Arduino encrypt and decrypt
Serial.println();
Serial.println("** Round 1: Local Test - Arduino Encrypt >> Arduino Decrypt");
Serial.println();
String text1 = "Luke, I am your father";
String text2 = encrypt(text1);
String text3 = decrypt(text2);
Serial.println("Original text: \"" + text1 + "\"");
Serial.println("Encrypted text: \"" + text2 + "\"");
Serial.println("Decrypted text: \"" + text3 + "\"");
Test 2: HTTP Get Request - Arduino Encrypt >> C# Decrypt
Serial.println();
Serial.println("** Round 2: HTTP Get Request - Arduino Encrypt >> C# Decrypt");
Serial.println();
String originalText = "What if I told you everything you know to be true is wrong";
Serial.println("Original Text: \"" + originalText + "\"");
Serial.println("Begin arduino encryption process...");
encryptedText = encrypt(originalText);
Serial.println("Arduino encrypted text: \"" + encryptedText + "\"");
Serial.println("Sending encrypted text to server...");
HTTPClient http;
encryptedText.replace("+", "%2B");
encryptedText.replace("/", "%2F");
encryptedText.replace("=", "%3D");
url = "http://192.168.1.100:8080/aes-decrypt?" + encryptedText;
Serial.println("URL: " + url);
http.begin(url);
int httpResponseCode = http.GET();
if (httpResponseCode > 0) {
Serial.print("HTTP success: ");
Serial.println(httpResponseCode);
decryptedText = http.getString();
Serial.println("Returned server decrypted Text: \"" + decryptedText + "\"");
} else {
Serial.print("Error occurred while sending HTTP request. Error code: ");
Serial.println(httpResponseCode);
}
http.end();
Test 3: HTTP Get Request - C# Encrypt >> Arduino Decrypt
Serial.println();
Serial.println("** Round 3: HTTP Get Request - C# Encrypt >> Arduino Decrypt");
Serial.println();
Serial.println("Downloading encrypted text from server...");
url = "http://192.168.1.100:8080/aes-encrypt";
Serial.println("URL: " + url);
http.begin(url);
httpResponseCode = http.GET();
bool round2DownloadSuccess = false;
if (httpResponseCode > 0) {
Serial.print("HTTP success: ");
Serial.println(httpResponseCode);
round2DownloadSuccess = true;
encryptedText = http.getString();
Serial.println("Received server encrypted text: \"" + encryptedText + "\"");
} else {
Serial.print("Error occurred while sending HTTP request. Error code: ");
Serial.println(httpResponseCode);
}
http.end();
if (round2DownloadSuccess) {
Serial.println("Begin arduino decrypting process...");
decryptedText = decrypt(encryptedText);
Serial.println("Arduino decrypted Text: \"" + decryptedText + "\"");
}
Done.
If you've successfully built and set up the ASP.NET Web Server, and you run the Arduino code, you should see an output that looks something like this:
(Click on the image to enlarge)
Thanks for reading. Happy coding!