Cryptography for IoT Hackers - Part 3
Series Index
| Part | Title | Status |
|---|---|---|
| 1 | TLS Explained: Why Your IoT Device Needs It | |
| 2 | The TLS Certificate Chain Explained (Root, Intermediate, and Server Certificates) | |
| 3 - You are here | Setting Up Your Own CA and Issuing Certs with OpenSSL | |
| 4 | ESP32 + TLS: Your First Secure MQTT Connection | |
| 5 | What the TLS Handshake Looks Like on the Wire (Wireshark Lab) | |
| 6 | Mutual TLS (mTLS) Explained: When the Server Also Verifies the Client | |
| 7 | MITM Attack on a TLS IoT Device - What Breaks and What Doesn't | |
| 8 | Cert Pinning The Fix, and How Attackers Bypass It | |
| 9 | Embedded Crypto Pitfalls: Hardcoded Keys and Weak RNG | |
| 10 | Breaking mTLS: Stolen Certs and Certificate Confusion | |
| 11 | Secure Provisioning How to Get Certs Onto Devices Safely | |
| 12 | Building a Hardened ESP32 TLS Client Checklist and Final Lab | |
| 13 | Secure OTA Updates Why Your Update Channel Is an Attack Surface |
Before We Start - What You Need
- Ubuntu Linux, a Ubuntu VM, or WSL on Windows (all work fine)
- A terminal
- That’s it. OpenSSL is already installed.
If you are on WSL, everything in this blog works exactly the same way. Just open your WSL terminal and follow along.
What Are We Actually Doing Today?
In Part 2, we talked about the trust chain Root CA signs the Intermediate CA, Intermediate CA signs the Server Certificate, and your device trusts the whole thing because it trusts the Root CA.
We understood it in theory. Today we build that exact chain with real commands on our own machine.
I want to be upfront when I first looked at OpenSSL commands, I felt like I was reading alien text. Flags everywhere, weird file extensions, no idea what was happening. So in this blog I am going to explain every single flag before running any command. No copy-paste without understanding.
Let’s go.
The Analogy That Made Everything Click
Think of yourself as a government. You are Nepal. You are going to:
- Create your government’s secret stamp - Root CA private key
- Create your government’s official identity card - Root CA certificate
- Accept a passport application from a worker (server) - CSR
- Stamp that application and hand back a passport - Server certificate
Anyone who trusts Nepal government will automatically trust every passport Nepal issues. That’s the entire trust chain in one analogy.
Now let’s build it.
What is OpenSSL?
OpenSSL is a command line tool for working with cryptographic stuff generating keys, creating certificates, signing things, inspecting things. It is the most widely used tool for this kind of work. Almost every TLS certificate in the world has been touched by OpenSSL at some point.
It is already installed on Ubuntu. You do not need to install anything.
Step 0 - Create a Working Folder
Let’s keep everything organised in one place:
mkdir ~/my-ca && cd ~/my-ca
All our files will live here. When we are done, this folder will contain your entire CA setup.
Step 1 - Create the Root CA Private Key
This is the very first thing you create. Before you can be a CA, before you can sign anything, before you can issue any certificate you need the private key. This is the stamp. The secret. The thing that never leaves your hands.
openssl genrsa -out rootCA.key 2048
What each part means:
| Part | What it does |
|---|---|
openssl |
the tool we are using |
genrsa |
generate an RSA private key |
-out rootCA.key |
save the key to a file called rootCA.key |
2048 |
key size in bits — 2048 is standard, bigger means stronger |
What you should see:
Generating RSA private key, 2048 bit long modulus (2 primes)
......................+++++
..............+++++
e is 65537 (0x010001)
Those +++++ lines OpenSSL is collecting randomness from your system to generate the key. That randomness is important. The e is 65537 is the public exponent used in RSA math, don’t worry about it for now.
Now let’s peek at what that file actually contains:
cat rootCA.key
You will see something like:
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA6RBnByZhNn3lyT6PZETnT3gL1Jht5j/m4opeWcc0frgpcE/f
... (a wall of Base64 encoded text) ...
-----END RSA PRIVATE KEY-----
That wall of text that IS your private key. It is encoded in Base64 so it can be stored as text. The -----BEGIN RSA PRIVATE KEY----- markers just tell OpenSSL what type of file this is.
Important: You just printed your private key in the terminal. In a real production setup, you would never do this. You keep this file locked down and never share it with anyone. For our lab it is fine but get into the habit of treating
.keyfiles like passwords.
Step 2 - Create the Root CA Certificate
Right now you have a private key but nobody knows who you are. If someone asks “are you really the Root CA, prove it” you cannot prove it with just the private key. You cannot show anyone the private key.
So you need a certificate. The Root CA certificate contains:
- Your public key (so people can verify your signatures)
- Your identity (country, organisation, common name)
- Your own signature on it (because nobody is above you to sign it)
This last point is important. The Root CA signs its own certificate. That is what self-signed means. You are the top of the chain. There is no one above you.
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.crt
What each flag means:
| Flag | What it does |
|---|---|
req |
certificate request operation |
-x509 |
output a certificate directly, not a CSR |
-new |
we are creating a new request |
-nodes |
no password on the private key |
-key rootCA.key |
use this private key to sign the certificate |
-sha256 |
use SHA256 as the hashing algorithm |
-days 3650 |
this certificate is valid for 10 years |
-out rootCA.crt |
save the certificate to this file |
It will ask you a bunch of questions. These become your CA’s identity fill them however you want for the lab:
Country Name (2 letter code) [AU]: NP
State or Province Name (full name) [Some-State]: Bagmati
Locality Name (eg, city) []: Kathmandu
Organization Name (eg, company) [Internet Widgits Pty Ltd]: IoTSec
Organizational Unit Name (eg, section) []: IoTSec Root CA
Common Name (e.g. server FQDN or YOUR name) []: IoTSec Root CA
Email Address []: noreply@iotsec.in
Note on Common Name: For the Root CA, put something that clearly identifies it as the CA like
IoTSec Root CA. This matters later when we look at the certificate and want to tell the CA apart from the server.
After running this, check your files:
ls -la
You should see:
-rw-r--r-- 1 iotsec iotsec 1452 Mar 5 18:33 rootCA.crt
-rw------- 1 iotsec iotsec 1679 Mar 5 18:28 rootCA.key
Notice the permissions. OpenSSL automatically set them:
rootCA.key→-rw-------only you can read it. Secret.rootCA.crt→-rw-r--r--everyone can read it. This is meant to be shared.
OpenSSL already knows what should be secret and what should be public. The tool is smarter than it looks.
What is a CSR and Why Does It Exist?
Before we create the server certificate, we need to understand CSR Certificate Signing Request.
Here is the analogy: applying for a passport.
- You fill out an application form with your name, photo, details
- You submit it to the passport office
- The passport office stamps it and hands you back a passport
- You never gave them your house keys in the process
In crypto:
- CSR = the application form (contains your public key + your identity info)
- CA signing it = the passport office stamping it
- Your private key = your house keys they never leave your hands
The CA never sees your private key. They only see the CSR. They sign it, hand back a certificate, and that is it.
This is why the server needs its own private key separate from the Root CA’s private key. The server is not the government. The server is a citizen applying for a passport.
Step 3 - Create the Server Private Key
The server gets its own private key. This stays on the server. Always.
openssl genrsa -out server.key 2048
Same command as before. Same output. The only difference is the filename this is the server’s key, not the CA’s key.
Step 4 - Generate the CSR
Now we create the passport application the CSR.
openssl req -new -key server.key -out server.csr
| Flag | What it does |
|---|---|
req |
certificate request operation |
-new |
creating a new request |
-key server.key |
use the server’s private key |
-out server.csr |
save the CSR to this file |
Fill the details and this time, the Common Name matters a lot:
Country Name (2 letter code) [AU]: NP
State or Province Name (full name) [Some-State]: Bagmati
Locality Name (eg, city) []: Kathmandu
Organization Name (eg, company) [Internet Widgits Pty Ltd]: IoTSec
Organizational Unit Name (eg, section) []: IoTSec Server
Common Name (e.g. server FQDN or YOUR name) []: iotsec.local
Email Address []: noreply@iotsec.in
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
For the challenge password just press Enter. Leave it blank. Not needed for our lab.
Why is Common Name important? The Common Name for the server should be the domain name the certificate will protect like
iotsec.localormqtt.iotsec.in. This is how clients know which domain this certificate belongs to. When the ESP32 connects toiotsec.localand gets back a certificate withCN = iotsec.localit knows the certificate matches. If the CN was something random, verification would fail.
Common Beginner Mistake - Issuer and Subject Looking Identical
When I first ran through this lab, I filled the same details for both the Root CA and the server. Country NP, Bagmati, IoTSec and I forgot to change the Common Name.
The result? When I inspected the certificate later, Issuer and Subject looked exactly the same. It was confusing.
Here is the fix the location details (country, state, city) can be identical. Both are IoTSec, both are in Nepal. That is fine. The field that must be different is Common Name:
- Root CA Common Name =
IoTSec Root CA - Server Common Name =
iotsec.local
That one field is what separates the CA from the server in the certificate output.
Step 5 - Sign the CSR with the Root CA
This is the moment Nepal government stamps the passport. We take the server’s CSR and sign it with our Root CA’s key.
openssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out server.crt -days 365 -sha256
| Flag | What it does |
|---|---|
x509 |
work with certificates |
-req |
the input is a CSR |
-in server.csr |
this is the CSR we are signing |
-CA rootCA.crt |
this is the CA certificate |
-CAkey rootCA.key |
this is the CA private key — the stamp |
-CAcreateserial |
generate a unique serial number for this certificate |
-out server.crt |
save the signed certificate to this file |
-days 365 |
valid for 1 year |
-sha256 |
hashing algorithm |
What you should see:
Signature ok
subject=C = NP, ST = Bagmati, L = Kathmandu, O = IoTSec, OU = IoTSec Server, CN = iotsec.local, emailAddress = noreply@iotsec.in
Getting CA Private Key
Signature ok that is the government stamping the passport. Done.
Step 6 - Look Inside the Certificate
This is my favourite part. Everything we talked about in Part 2 Issuer, Subject, Validity, Public Key, Signature it is all right here in your terminal now.
openssl x509 -in server.crt -text -noout
| Flag | What it does |
|---|---|
-in server.crt |
read this certificate |
-text |
show everything in human readable format |
-noout |
do not print the raw Base64 output |
You will see something like this:
Certificate:
Data:
Version: 1 (0x0)
Serial Number:
0c:08:d8:77:1f:44:51:ae:3b:99:f2:bc:c4:61:31:3d:56:df:74:51
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = NP, ST = Bagmati, L = Kathmandu, O = IotSec, OU = IotSec Root CA, CN = IotSec Root CA
Validity
Not Before: Mar 5 13:22:30 2026 GMT
Not After : Mar 5 13:22:30 2027 GMT
Subject: C = NP, ST = Bagmati, L = Kathmandu, O = IoTSec, OU = IoTSec Server, CN = iotsec.local
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:da:a0:0b:66:95:7c:7f:50:b7:b4:e7:65:f6:7b:
...
Signature Algorithm: sha256WithRSAEncryption
5d:aa:01:7f:9c:bf:5a:de:f5:44:1d:58:da:72:0b:d1:...
Let’s break down what you are actually looking at:
Issuer - who signed this certificate. That’s your Root CA Nepal government.
Issuer: CN = IotSec Root CA
Subject - who this certificate was issued to. That’s your server.
Subject: CN = iotsec.local
Validity - the dates this certificate is valid between. We set -days 365 so it is good for exactly one year.
Subject Public Key Info - the server’s public key. This is what clients use to encrypt data to the server.
Signature Algorithm - sha256WithRSAEncryption. The Root CA used its private key and SHA256 to sign this. Anyone with the Root CA’s public key can verify this signature.
This is the theory from Part 2 but now it is real files on your machine.
Step 7 - Verify the Certificate Against the Root CA
This is what your ESP32 will do in code when it connects to a server. We are doing it manually first so you understand what is happening.
openssl verify -CAfile rootCA.crt server.crt
Expected output:
server.crt: OK
That OK means: OpenSSL checked the signature on server.crt, looked up who signed it, verified against rootCA.crt, and confirmed the chain is valid.
Two words. That is the entire trust chain working.
What Files Do You Have Now?
ls -la
rootCA.key
rootCA.crt
rootCA.srl
server.key
server.csr
server.crt
Let’s map each file back to the Nepal analogy:
| File | What it is | Who holds it | Nepal analogy |
|---|---|---|---|
rootCA.key |
Root CA private key | CA only never shared | Government’s secret stamp |
rootCA.crt |
Root CA certificate | Shared publicly goes on ESP32 | Government’s official identity card |
rootCA.srl |
Serial number tracker | CA keeps it | Government’s passport issuance log |
server.key |
Server private key | Server only never shared | Worker’s personal house keys |
server.csr |
Certificate signing request | Used once, can be deleted | Passport application form |
server.crt |
Server certificate | Server shares this during TLS | Stamped passport |
What goes on the ESP32? -
rootCA.crt. That is it. The ESP32 just needs to know which CA to trust. It does not need the private key. Nobody gets the private key.
The Full Picture - What Just Happened
Let’s trace the whole thing:
[rootCA.key] ──signs──► [rootCA.crt] (self-signed, Root CA identity)
│
│ signs
▼
[server.key] ──creates──► [server.csr] ──► [server.crt]
(signed by Root CA)
ESP32 holds: rootCA.crt
Server holds: server.key + server.crt
When your ESP32 connects to the server:
- Server sends
server.crt - ESP32 checks: who signed this? Root CA.
- ESP32 checks: do I trust Root CA? Yes I have
rootCA.crtflashed on me. - Signature verified. Connection trusted.
That is the TLS handshake at its core. We will go deeper on the handshake in Part 5 with Wireshark.
Quick Recap - Commands We Used
# Create Root CA private key
openssl genrsa -out rootCA.key 2048
# Create Root CA self-signed certificate
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.crt
# Create server private key
openssl genrsa -out server.key 2048
# Create server CSR
openssl req -new -key server.key -out server.csr
# Sign the CSR with Root CA (issue the server certificate)
openssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out server.crt -days 365 -sha256
# Inspect the certificate
openssl x509 -in server.crt -text -noout
# Verify the certificate against Root CA
openssl verify -CAfile rootCA.crt server.crt
What’s Next - Part 4
You now have real certificate files sitting on your machine. In Part 4, we take rootCA.crt and server.crt and flash them onto the ESP32 to make your first secure MQTT connection over TLS.
The files you created in this part are exactly what Part 4 needs. Keep them.