Base64 is an encoding scheme that allows representing binary data in an ASCII text format.
This is useful when we need to transmit binary data through protocols that only support text (for example, emails or URLs).
Features of Base64
- Binary data representation: Base64 converts binary data into text, making it easier to transmit and store.
- Increased size: Base64 encoded data takes up approximately 33% more space than the original data.
- Common usage: It is used in applications such as emails (attachments), URLs, HTTP authentication, and data storage.
The Base64 system is also related to security applications. Many times it plays a part in the encryption or authentication process.
However, it is itself not an encryption method. Data encoded in Base64 can be easily decoded.
If you need to protect the confidentiality of the data, you should use encryption methods like AES or RSA.
How does Base64 work?
Base64 encoding uses a set of 64 characters (hence its name), chosen for being common in most systems.
These characters are uppercase letters (A-Z), lowercase letters (a-z), numbers (0-9), and two special characters, generally ”+” and ”/“.
Each of these characters can encode 6 bits of information (2^6 = 64). But our original information was encoded in multiples of 8 bits.
To perform the conversion,
- We divide the binary data into blocks of 3 bytes (3 x 8 = 24 bits)
- We convert the blocks into 4 ASCII characters (4 x 6 = 24 bits).
But the information we want to encode will not always be a multiple of 3 bytes. At the end of the sequence, we may have 1 or 2 leftover bytes.
So the character ”=” is used as padding.
- If 1 byte is leftover, 2 ”=” characters are added.
- If 2 bytes are leftover, 1 ”=” character is added.
- If the number of bytes is a multiple of 3, padding is not necessary.
Example of Base64 encoding
Let’s see it with an example. Imagine we have this binary data that we want to send.
01001000 01101111 01101100 01100001
To convert it to Base64, divide these bytes into blocks of 6 bits:
010010 000110 111101 101100 011000 01
I complete the final block with 0
010010 000110 111101 101100 011000 0100000
Each 6-bit block is converted into a Base64 character:
S G 9 s Y Q
Since I only have 6 letters, and I need a multiple of 4, I add two =
.
SG9sYQ==
Base64 Implementation in ESP32
The ESP32 has a library (base64.h
) that allows us to encode and decode data in Base64.
Let’s see how to use the library in an ESP32 project.
#include "stdlib.h"
#include "mbedtls/base64.h"
void printBlockAsUtf8(std::vector<uint8_t> data)
{
for(int i = 0; i < data.size(); i++)
{
Serial.print((char)data[i]);
}
}
std::string encode_base64(std::vector<uint8_t>& data)
{
size_t outputLength;
mbedtls_base64_encode(nullptr, 0, &outputLength, (unsigned char*)data.data(), data.size());
std::vector<uint8_t> encoded(outputLength);
mbedtls_base64_encode(encoded.data(), outputLength, &outputLength, (unsigned char*)data.data(), data.size());
std::string rst((char*)encoded.data(), outputLength);
return rst;
}
std::vector<uint8_t> decode_base64(std::string& text)
{
size_t outputLength;
mbedtls_base64_decode(nullptr, 0, &outputLength, (unsigned char*)text.c_str(), text.length());
std::vector<uint8_t> decoded(outputLength);
mbedtls_base64_decode(decoded.data(), outputLength, &outputLength, (unsigned char*)text.c_str(), text.length());
return decoded;
}
void setup()
{
Serial.begin(115200);
delay(2000);
std::string message = "www.luisllamas.es";
std::vector<uint8_t> data(message.begin(), message.end());
auto encoded = encode_base64(data);
auto decoded = decode_base64(encoded);
Serial.printf("Original (%d bytes): ", data.size());
printBlockAsUtf8(data);
Serial.println();
Serial.printf("Encoded (%d bytes): ", encoded.length());
Serial.print(encoded.c_str());
Serial.println();
Serial.printf("Decoded (%d bytes): ", decoded.size());
printBlockAsUtf8(decoded);
}
void loop()
{
delay(1000);
}
And if we run it, you will see something like this
Practical Examples
Transmission of binary data
Base64 is useful when we need to transmit binary data through protocols that only support text, such as HTTP or MQTT. For example, we can encode an image in Base64 and send it through an HTTP request.
#include <WiFi.h>
#include <HTTPClient.h>
#include <base64.h>
const char* ssid = "your_SSID";
const char* password = "your_PASSWORD";
void setup() {
Serial.begin(115200);
// Connect to WiFi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
Serial.println("Connected to WiFi");
// Read image from the filesystem
File file = SPIFFS.open("/image.jpg", "r");
if (!file) {
Serial.println("Error opening image");
return;
}
// Read image data
size_t fileSize = file.size();
uint8_t* buffer = (uint8_t*)malloc(fileSize);
file.read(buffer, fileSize);
file.close();
// Encode image in Base64
String encodedImage = base64::encode(buffer, fileSize);
free(buffer);
// Send encoded image via HTTP
HTTPClient http;
http.begin("http://yourserver.com/upload-image");
http.addHeader("Content-Type", "application/json");
String jsonPayload = "{\"image\": \"" + encodedImage + "\"}";
int httpResponseCode = http.POST(jsonPayload);
if (httpResponseCode > 0) {
Serial.println("Image sent successfully");
} else {
Serial.println("Error sending image");
}
http.end();
}
void loop() {
// No need to do anything in the loop
}
Storing data in text format
Base64 is also useful for storing binary data in text formats, such as JSON or XML. For example, we can store a digital signature in Base64 in a JSON file.
#include <SPIFFS.h>
#include <base64.h>
void setup() {
Serial.begin(115200);
// Initialize SPIFFS
if (!SPIFFS.begin(true)) {
Serial.println("Error initializing SPIFFS");
return;
}
// Generate digital signature (simulated)
uint8_t signature[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0};
size_t signatureSize = sizeof(signature);
// Encode signature in Base64
String encodedSignature = base64::encode(signature, signatureSize);
// Create JSON file with the signature
File file = SPIFFS.open("/signature.json", "w");
if (!file) {
Serial.println("Error creating JSON file");
return;
}
String jsonContent = "{\"signature\": \"" + encodedSignature + "\"}";
file.print(jsonContent);
file.close();
Serial.println("Signature stored successfully");
}
void loop() {
// No need to do anything in the loop
}