Adding MbedTLS to your project is a great way to leverage a library designed to help secure your data, from authentication to encryption, MbedTLS can basically do it all, and we’re going to go over the basics of how to integrate it and use it.
Why MbedTLS?
Security has become critical in most IoT and connected products. But integrating security can be difficult. Past experience and best practices show that rolling your own security is typically a bad idea – the effort of putting together a security suite is challenging and requires proper knowledge.
There’s a few options as far as security libraries. OpenSSL is the 800-lb gorilla in the room. It’s a library used far and wide. But one downside of OpenSSL is that it’s much larger than MbedTLS and other libraries. It’s also targeted more towards x86 and Cortex-A ARM processors rather than microcontrollers. The OpenSSL Code size can sometimes be double or more than Mbed TLS.
WolfSSL is another option, even though it’s a proprietary library. It’s also optimized for size and small embedded devices.
One big issue with OpenSSL is that getting it working and compiling to do anything can be challenging. There are a lot of dependencies and it can take a while to get it up and running. On the other hand, MbedTLS can be up and running in less than 15 minutes, as you’ll see here.
Getting MbedTLS
Since MbedTLS is an open source project, it lives in Github and you can access the repository here:
You can use the master branch which is the latest, but the better approach is to use one of the releases. We’re going to be using Mbed TLS 3.6.0 LTS which is currently the latest release. Using a release means known good tested code, and an LTS release will have longer support for bug fixes. If there’s something that’s critical in security is to patch as quickly as possible.
You can download the right version from the Assets list below:
Since we’re running on Windows for this tutorial, we’ll use Source code zip, but tar.gz would make sense for Linux users.
MbedTLS Library Contents
When you extract the MbedTLS source code, you’ll see a large number of files and directories:
You can ignore many of these, but there are some critical ones:
include
is the include folder containing the main MbedTLS header files which you’ll need in a projectlibrary
contains the actual C source code for the MbedTLS library which you’ll also needprograms
contain various sample programs that you can use as reference to perform operationsvisualc
has many sample projects (which usesprograms
) and can be used as reference
Running MbedTLS Examples on Windows
Install Visual Studio 2022 Community from the Microsoft VS 2022 page
Inside visualc\VS2017 you will find mbedTLS.sln
, which is the Visual Studio solution file.
You can build the whole solution or individual projects. Let’s take a look at the crypt_and_hash
project which encrypts and hashes data. This is a very typical MbedTLS sample application.
Integrate MbedTLS in a new Application
Running the MbedTLS examples is great as a reference, but what we’re really looking to do is to create a new application that uses integrates, configures and uses MbedTLS.
Let’s start by creating a new C console project
Once the project is created you will have the following:
The default visual studio C Console project is a nice starting point, but it needs a few changes to work for our purposes:
- Rename
MbedTLS Example.cpp
to main.c since we’re going to be using C - Change the application Character Set to
Use Multi-Byte Character Set
. You can access these properties by right clicking on the project and selectingProperties
- Remove all the code from main.c and change it to the following
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("MbedTLS Starting Application");
}
We’re going to start from the basics so you cans see how to build on an application.
Adding MbedTLS to the Project
Our next step is to add the MbedTLS source. Copy the include
and the library
folders to the application folder, at the same level as main.c.
We need to help the compiler find the library. Because there are different references inside of MbedTLS, we need to add several includes
- include
- ./
- include/mbedtls
To add all these includes, we put them together separated by a comma in Visual Studio’s User Include list:
include; ./; include/mbedtls
;
Click on OK.
We need to add the MbedTLS library files to the project so they’re compiled and linked. However, we’re not going to add the complete library. We’re going to selectively add files that are relevant to the operations we’re going to perform, specifically AES encryption and decryption. MbedTLS includes SSL and a ton of cryptographic primitives, and we don’t want to include everything.
To add files to the project right click the project, and go to Add-> Existing Files . Locate the file aes.c that’s inside the library
folder. Once you add it, run it to build it.
We added aes.c from the MbedTLS library because we want to do AES operations, but we’re getting errors. What’s going on?
Part of the reason of why we’re going through this approach is because I want to show you how to understand how the MbedTLS pieces fit, and not just be told what to do without any explanation. When you’re working with MbedTLS you will run into issues, especially on embedded systems and as you’re optimizing the library.
What wer’e seeing is the linker is looking for a variety of functions such as mbedtls_aesni_crypt_ecb
and other aesni functions, and also mbedtls_platform_zeroize
. These functions are being called from inside aes.c.
- The AES-NI related functions are being called because AES-NI acceleration is enabled. MbedTLS enables this on x86 platforms by default.
- We’re missing the
mbedtls_platform_zeroize
function as well that’s not related to AES at all. This is actually a generic zeroing utility function
You can find in all the C files in the library folder (it’s easiest to search for void
mbedtls_platform_zeroize
or else you will find a large number of references). This function is defined in platform_util.c
. Let’s include that file to our project as we did for aes.c
.
With this file included in the project, we’ll have resolved the last error. But the AESNI errors remain. How do we disable them? We could include aesni.c
, but for demonstration purposes let’s disable AES-NI.
The MbedTLS Configuration File
AES-NI and almost every configuration is included in include/mbedtls/mbedtls_config.h
This is a critical file that’s included everywhere and defined what’s enabled. When you build and run into issues, it’s important to check this file. Another header file, check_config.h
checks mbedtls_config.h
and ensures that the configuration makes sense as far as dependencies.
If you search in mbedtls_config.h
for AES-NI you will find on line 2252 that there’s a definition called MBEDTLS_AESNI_C
.
The documentation shows this enables AES-NI support. The documentation in mbedtls_config.h
has a lot of extremely useful documentation. For example, the Module and Caller details tell you that aes.c
calls this, but aesni.c
is the module that needs to be included.
You can see below that with this definition removed, we can successfully compile.
Adding AES Crypto
Now that we have some basic pieces we can start actually building our application. Looking at aes.c
we can see that there are many functions. Some are internal. But we’re looking for encryption and we stumble on mbedtls_aes_crypt_ecb
. The AES crypto is rarely used by itself because it’s a block cypher – it encrypts and decrypts blocks of a fixed size. But things we want to encrypt can be text, images of varying length. The basic vanilla encryption code is called Electronic Code Book (ECB), so called because you’re encrypting as if you’re just doing a replacement using a code book.
You can do a straight encryption of blocks, but this ends up working terribly because you can often still see what’s being encrypted depending on the case. So various AES operation modes such as CBC, OFB, CTR, XTS and others that have various specific uses, advantages and disadvantages.
For our basic example we are going to use ECB since it demonstrates many aspects of MbedTLS. You can use other APIs.
If you go to the aes.h header for aes.c, you will find a detailed description of the mbedtls_aes_crypt_ecb
function. This can be done easily in Visual Studio by holding CTRL and clicking the function name with the mouse.
This function takes a few parameters. An AES context object of type mbedtls_aes_context
. In addition, we have mode, indicating whether we’re encrypting or decrypting, the input and the output. ECB is relatively straightforward.
The mode, input and output are straightforward parameters, but ctx
is a pointer to an object. And the documentation says we have to be initialized and bound to key. This explains one thing the function is missing: the AES key that is used to encrypt the data.
Looking some more in aes.c
we can find that many functions use mbedtls_aes_context
as an input, but we can also find two other functions of interest: mbedtls_aes_init
and mbedtls_aes_free
.
mbedtls_aes_init’s comments show that “It must be the first API called before using the context”. Free has the opposite action – it releases the AES context and should be used when we’re done with our operations.
That’s great, but the original comments mentioned we need to bind a key. Looking around we can find mbedtls_aes_setkey_enc
, a function whose description says that it sets the encryption key. There is also mbedtls_aes_setkey_dec
which does the same but for the encryption key.
You may be wondering why not just look at the online Mbed TLS documentation? Well, the code doesn’t lie. One thing we’ve experienced over the years is that devices can run different Mbed TLS versions, which means that APIs can change. It’s also important you familizarize youreself with the MbedTLS library itself, how it works. Using it as a block box can lead to problems later on. Mbed TLS is a tool, a great one, but one that should be employed carefully to achieve your security goals.
Now that we’ve looked, we seem to have all the building blocks. So we’ve put together the following code:
#include <stdio.h>
#include <stdlib.h>
#include "aes.h"
#define AES_256_KEY_LEN 32
#define AES_256_KEY_BIT_LEN 256
#define AES_256_BLOCK_SIZE 16
uint8_t aes_key[AES_256_KEY_LEN] = {
0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
uint8_t aes_plaintext[AES_256_BLOCK_SIZE] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00
};
uint8_t aes_expected_crypto[AES_256_BLOCK_SIZE] = {
0xf2, 0xb2, 0x1b, 0x4e, 0x76, 0x40, 0xa9, 0xb3, 0x34, 0x6d, 0xe8, 0xb8, 0x2f, 0xb4, 0x1e,
0x49
};
uint8_t aes_crypto[AES_256_BLOCK_SIZE];
int main()
{
int rc = -1;
mbedtls_aes_context context;
printf("MbedTLS Starting Application");
mbedtls_aes_init(&context);
rc = mbedtls_aes_setkey_enc(&context, aes_key, AES_256_KEY_BIT_LEN);
if (rc != 0)
{
printf("mbedtls_aes_setkey_enc failed!\n");
}
memset(aes_crypto, 0, sizeof(aes_crypto));
rc = mbedtls_aes_crypt_ecb(&context, MBEDTLS_AES_ENCRYPT, aes_plaintext, aes_crypto);
if (rc != 0)
{
printf("mbedtls_aes_crypt_ecb failed!\n");
}
else
{
if (memcmp(aes_expected_crypto, aes_crypto, sizeof(aes_crypto)) != 0)
{
printf("Crypto failed!\n");
}
else
{
printf("Encryption Succesful\n");
}
}
mbedtls_aes_free(&context);
}
Let’s take a closer look at the code. We first define a few constants. We are using AES-256 which uses 256-bit keys, which is 32 bytes. It still operates on 128-bit blocks (16 bytes).
The first thing we do is to declare an mbedtls_aes_context
. This object is placed on the stack, which means it has unknown values internally. For this, we call mbedtls_aes_init
, which initializes the object properly for the Mbed TLS library.
With this we set the key in the context. How did we get this key? Well, it’s best to do a proper verification of AES operations. NIST publishes the AES Known Answer Test (KAT) Vectors which is a file containing keys, messages and expected results for AES cryptography. This is a great reference for testing implementations and code.
We clear the aes_crypto
buffer just in case, although it’s not strictly necessary.
On line 45 we perform the actual encryption operation, and the rest of the code checks the results to make sure that
Using MbedTLS in ARM and Microcontrollers
MbedTLS is an excellent library, but it does have its drawbacks. It can be sometimes too large, even when you optimize it by removing support for various crypto primitives.
One good example is Mbed TLS and bootloaders. You will not find MbedTLS used in bootloaders because in many cases the code size is too large. When every kB of code space counts, MbedTLS ends up being several kB larger than other implementations.
For example, we’ve created custom bootloaders that leveraged uECC and similar libraries. They contain everything you need and are optimized for size. This can make a huge difference in the size of the bootloader.
Another extremely important factor is memory utilization. Certificates, buffers and data can use a lot of data. MbedTLS can operate with both static and dynamic memory allocations
Leveraging Acceleration in MbedTLS
Cryptographic functions can require a lot of CPU cycles to run. CPU only implementations can be slow, but thankfully a lot of IC vendors include hardware acceleration for cryptographic primitives. AES, RSA, ECC and SHA are some of the crypto primitive accelerators you can take advantage of, and they help speed up your system and lower your power consumption. For example, AES-NI are an AES instruction sets available on x86 CPUs that help speed up AES operations.
MbedTLS already comes with these crypto algorithms integrated, but using the hardware implementation is usually better. Luckily MbedTLS comes prepared – in many cases one can select to make use of hardware acceleration.