Encrypted MQTT from and to a Mosquitto Server

Posted on December 23, 2015  (Last modified on December 13, 2022 )
7 minutes  • 1430 words  • Other languages:  Deutsch

The MQTT protocol is good to check the status of services. If the messages traverse the public Internet, they should be encrypted. I will explain a few methods in how to accomplish this.

Install Mosquitto and Set Up Authentication

I will explain this using a Debian/Ubuntu installation. On other systems, the installation should be pretty similar. The configuration files might be in different folders and differently structured. The installation of Mosquitto and the clients is fairly simple:

#You can also use packages from PPA which are likely to be more up to date:
# sudo add-apt-repository ppa:mosquitto-dev/mosquitto-ppa
sudo apt install mosquitto mosquitto-clients

Mosquitto can be secured by different means explained at length in the documentation . We have both encryption of the whole server and authentication for channels. The most simple method for authentication is to use a password file:

sudo mosquitto_passwd -c /etc/mosquitto/passwords mqtt

This will create a file /etc/mosquitto/passwords containing the user mqtt. The password can be entered on the command line. For testing purposes, I choose “12345” (obviously the best password one could choose).

We have created the user, what is he/she/it allowed to do? We create another file describing authorization:

sudo bash -c "cat > /etc/mosquitto/access.acl <<EOF
user mqtt
topic readwrite #
EOF"

The file /etc/mosquitto/access.acl contains two entries. First, we define our user. The lines that follow define the privileges of this user. In our case, we have a single topic defined which our user can read from and write to. The topic (#) is special and means “all topics”. In live scenarios, you might want to create a more elaborate privilege list, for testing, this is sufficient.

We now tell Mosquitto the locations of our password and access files:

sudo bash -c "cat > /etc/mosquitto/conf.d/auth.conf <<EOF
acl_file /etc/mosquitto/access.acl
allow_anonymous false
password_file /etc/mosquitto/passwords
EOF"
sudo chmod 600 /etc/mosquitto/passwords

Thus, we extended the configuration and pointed it to the files. Moreover, we disabled the anonymous access to the server. We can now test out new settings:

mosquitto_sub -t '#' &
# This should still work (for now).

sudo systemctl restart mosquitto.service

# should output: Connection Refused: not authorised.
mosquitto_pub -m "Hello world!" -t "test"
# should output: Connection Refused: not authorised.

# The following lines should work (you can test in two terminals, too):
mosquitto_sub -u mqtt -P 12345 -t '#' &
mosquitto_pub -u mqtt -P 12345 -m "Hello world!" -t "test"

Albeit, authentication does not really increase security yet. Passwords are still passed in clear text. Consequently, encryption is paramount.

TLS-PSK Encryption

A simple encryption uses what we called a “preshared key”. Both client and server must know a common password (or passphrase). This practice is less secure, because the common password can be compromised more easily, which is why people like to use TLS/SSL using public and private keys. I will come to this in a minute. Another disadvantage is that not a lot of libraries seem to support TLS-PSK (Paho, mqtt for Nodejs etc.).

We keep the authentication and just define a secure connection on port 8883:

sudo bash -c "cat > /etc/mosquitto/conf.d/ssl.conf <<EOF
port 8883
psk_hint myrandomhint
psk_file /etc/mosquitto/pskfile
EOF"

The hint can be chosen freely and must be known by the client, too. The file /etc/mosquitto/access.acl keeps the keys of all clients:

sudo bash -c "cat > /etc/mosquitto/pskfile <<EOF
mqtt:73656372657450534b
EOF"
sudo chmod 600 /etc/mosquitto/pskfile

Similar to the password file, we create one user per line, followed by a hexadecimal string. Note that the username of pskfile does not have to match the authenticated user (can be completely different).

Communication can now be encrypted:

#Restart server
sudo systemctl restart mosquitto.service

# Receiver
mosquitto_sub --psk-identity mqtt --psk 73656372657450534b -u mqtt -P 12345 -t '#' &
# Sender
mosquitto_pub -u mqtt -P 12345 --psk-identity mqtt --psk 73656372657450534b -m "Hello world!" -t "test"

TLS/SSL Encryption

This encryption is similar to the one above, but uses a public and a private key. It is easy to use with a self-signed certificate. Owentracks has created a script to create one of those:

cd /etc/mosquitto/certs
wget https://raw.githubusercontent.com/owntracks/tools/master/TLS/generate-CA.sh -O - | sudo bash

You can refer to the documentation for more information: http://mosquitto.org/man/mosquitto-tls-7.html .

We create an SSl configuration setting with the newly created key files (note port 8883):

host=$(hostname -f)
sudo bash -c "cat > /etc/mosquitto/conf.d/ssl.conf <<EOF
port 8883
cafile /etc/mosquitto/certs/ca.crt
certfile /etc/mosquitto/certs/${host}.crt
keyfile /etc/mosquitto/certs/${host}.key
EOF"

Clients only have to know the CA file. This public key can be shared among the clients without compromising security (it can only be used to encrypt data, not to decrypt it).

To test this, we can run the following commands:

#Restart server
sudo systemctl restart mosquitto.service

#Receiver
mosquitto_sub --cafile /etc/mosquitto/certs/ca.crt -u mqtt -P 12345 -t '#'
#Sender
mosquitto_pub -u mqtt -P 12345 --cafile /etc/mosquitto/certs/ca.crt -m "Hello world!" -t "test"

Comparing the Transfer Methods

How do the transfer sized differ? I have checked the TCP streams of the different methods and created the following table to exemplify this:

(in Bytes) Quality 0 Quality 1 Quality 2
without encryption 72 78 86
TLS-PSK 1172 1224 1331
TLS/SSL 3742 3777 3843

Note there is an additional overhead created by the protocols (TCP and IP), although this is only relevant when considering the transmission without encryption. This is because the overhead is bigger than the transmission size (mostly due to TCP handshake). Still it should become clear that encrypted transmissions carry a significant overhead. The reason is mostly contained in the TLS procedure which also contains certain handshakes and the exchange of keys. In general, keys are much bigger than the data transmitted (e.g. in above case of TLS/SSL, the key was 2048 bytes long). As a consequence, a MQTT publish is quite big. QoS qualities do not raise this much, although more IP packages have to be exchanged.

When implementing MQTT in this way, this should be considered. A small math example: A service publishes an update every minute to the MQTT server, 1440 times a day or about 43500 times a month. Without encryption and quality 0, the amount of data transferred is about 3 MB. Adding network overhead, the amount will be around 9 MB, not really a lot.

Using encryption, this is quite different. In a TLS-PSK scenario, the amount of data will rise to around 48 MB, in a TLS/SSL one it will total at about 155 MB, not considering protocal overhead. If you monitor 10 services, calculate 1.5 GB of MQTT volume per month.

An important note: The volumes will only be this high, if each publish creates a new connection. This is the case in our command line program above. Consequently, a good service will keep the connection open and reuse it to publish updates. With TLS, only the initial connection takes up 3–4 kB, subsequent transmissions will still have a little overhead, but far not so much.1

Encrypted communication makes sense for services that either publish very rarely or quite frequently. In the latter scenario, they should keep connections open (my example above is one of those).

Alternatives

There are some alternatives if you want to send unencrypted data via an encrypted channel:

In my original version of this article, I recommended using an SSH tunnel. This was 2015 and VPN was pretty convoluted to set up (IpSec via OpenSwan/StrongSwan or something along the lines, not easy). SSH does make it easy to create encrypted tunnels, so it can be used in MQTT. Unfortunately, these tunnels have to be created and monitored. One way to do this, is autossh. You can find nice howtos in the Internet on how to set it up.

My experiences with autossh are mixed. The SSH connections we not stable and sometimes autossh could not recreate closed connection for some reason. Consequently, I do not recommend using SSH tunnels in production scenarios. If somebody has different experiences, feel free to drop some feedback.

A much better alternative has come: VPN using WireGuard. You can find many guides in bow to make it run, your router might actually be able to act as server, too (e.g. AVM’s Fritz!Box 7590 can do it). So this might be an easy alternative, because you can set up your publisher as WireGuard client and connect to your MQTT server easily.


  1. Thanks to Janos Kutscherauer for testing this 2018: A 12 byte MQTT message would grow to 29 bytes using TLS. This sounds like much, but the network stack is much larger than this. The example package would have a total size of 149 bytes in TLS (IP, TCP, TLS, MQTT, payload) compared to 120 bytes without encryption (IP, TCP, MQTT, payload). ↩︎

By logging in into comments, two cookies will be set! More information in the imprint.
Follow me