Encrypted NRF24L01 Transport Protocol

This memo describes an idea for secure protocol used for communication with NRF24L01 chips in pair with STM32F103 chips.

This "format" used instead of standard TLS (like HTTPS over TLS over TCP/IP) because you can't really afford having these with something like 20K of RAM and 128K of ROM.

NRF devices should turn off the NRF's packet confirmation, configure address size to 3 bytes, switch to default channel and listen on default address.

Communication uses RSA encryption (2048 bit) to exchange AES key (128 bit), the latter then is used to encrypt the messages.
Every device has: a copy of CA root certificate (ca), a signed certificate for the device itself (crt) and a private key to said certificate (priv).
Upon communication, devices verify certificates using ca, encrypt data with crt of the remote party, and then remote party decrypts the data with priv.

Communication Flow

In ourder to achieve successfull encrypted communication, devices should:

  1. Find each other (0x1 Discover / 0x2 Hello)
  2. Exchange X509 Certificates (0x3 Get Certificate / 0x4 Certificate)
  3. Negotiate on a AES key (0x5 Key Negotiate / 0x6 Negotiate Proceed / 0x7 Key Data / 0x8 Key Finish / 0x9 Negotiate Confirm)

Then they could do requests:

  1. Open a connection (0xA Request)
  2. Approve the connection (0xB Proceed)
  3. Send the data (0xC Request Body)
  4. Receive the response (0xD Response / 0xE Response Finish / 0xF Close Request)

It looks like this:

NRF24

Packet format

Kind Length Description
CRC 4 bytes CRC32 hash of packet including flags
FLAGS 1 byte Packet Flags
  • The rest of the packet depends on the packet type (TYP)
  • Packet size is fixed 32 bytes (5 of which is metadata and 27 is payload)
  • If CRC does not match, the packet should be dropped

Packet Flags

Name Bits Description
TYP first 4 bits, 0x0F Kind of the packet
Reserved Last 4 bits, 0xF0 Reserved

Packet Types

First 4 bits of the packet flags (TYP) contain packet type

0x1 Discover

This packet is sent to find devices nearby. By default, NRFs should turn off packet confirmation, switch to default channel and listen to default address.

Kind Length Description / Value
CRC 4 bytes CRC32
FLAGS 1 byte TYP = 0x1
V 1 byte Protocol version = 0x01
DEVICE_ID 3 bytes ID of the current device

Everyone who receives the packet should respond within specific time window with packet 0x2 Hello. The time to wait is picked randomly.

0x2 Hello

A response to 0x1 Discover packet. Normally, everyone sho receive the message should respond. If the device is busy, the packet should be ignored.

Kind Length Description / Value
CRC 4 bytes CRC32
FLAGS 1 byte TYP = 0x2
DEVICE_ID 3 bytes ID of the current device
CA_CRC 4 bytes CRC32 of the CA certificate + DEVICE_ID
CRT_CRC 4 bytes CRC32 of the certificate of the device
CRT_SIZ 2 bytes Size of the certificate of the devices
PRIORITY 1 byte Used-defined priority for the device

The data specified in PRIORITY shouldn't be trusted and must be only used to optimize network.

0x3 Get Certificate

If device chooses to connect to another device, but do not yet have its certificate, it needs to download it first.

Kind Length Description / Value
CRC 4 bytes CRC32
FLAGS 1 byte TYP = 0x3
DEVICE_ID 3 bytes ID of the target device
ADDR 3 bytes Random NRF Address
CH 1 byte Random NRF Channel

After receiving this message, the sender will switch to randomly picked channel (CH) and start listening on random address ADDR for limited time. This is implemented to reduce traffic issues.

If a target device received this message, it should switch to the channel CH, listen on address ADDR and respond with 0x4 Certificate message to the same address ADDR. If the target device is busy, it should ignore this message, and the sender will fail with timeout.

0x4 Certificate

If a device received a 0x3 Get Certificate message it should switch on channel/address required and respond with this message.

A certificate has length up to 1K bytes, so considering limitation of NRF's max packet size (32 bytes), it has to be delivered sequentially.

The number of packets (and lenght of the certificate) is received with 0x2 Hello. It is calculated by formula (certificate size) / 26 (rounding up).

Kind Length Description / Value
CRC 4 bytes CRC32
FLAGS 1 byte TYP = 0x4
SEQ 1 byte Number of the packet in the requence
DATA up to 26 bytes Part of Certificate

Packets are sent one by one, without confirmation for the receiving end. If a packet is received out of order, or same packet received twice, the receiver should just drop the request and switch to default channel / default address.

If the last packet is received in order, the receiving party should:

  1. Ensure the CRC32 of the Certificate matches the one stated in 0x2 Hello.
  2. Verify the Certificate signature doing x509_crt_verify(certificate, ca)
  3. Extract the device ID from the certificate (Common Name)
  4. Validate if the device ID matches the one stated in the 0x2 Hello.

If the certificate is validated, the certificate should be stored in ROM by DEVICE_ID. The sender should immeadetely switch back to the default channel / address after sending the last packet out of sequesnce.

0x5 Negotiate

If a device wants to send a request to another device, but they have not yet negotiated on Key Data, the device should send this message first.

Kind Length Description / Value
CRC 4 bytes CRC32
FLAGS 1 byte TYP = 0x5
DEVICE_TGT 3 bytes ID of the target device
DEVICE_SRC 3 bytes ID of the current device
ADDR 3 bytes Random NRF Address
CH 1 byte Random NRF Channel

If the message was received by device with different DEVICE_ID from DEVICE_TGT, the packet should be ignored. The Certificate of the target device should be obtained first (see 0x3 Get Certificate).

After receiving this message, the sender will switch to randomly picked channel (CH) and start listening on random address ADDR for limited time. This is implemented to reduce traffic issues.

If a target device received this message, it should switch to the channel CH, listen on address ADDR. If the target device is busy, it should ignore this message, and the sender will fail the request with timeout.

If the target device does not have the Certificate of the current device yet, it should respond with 0x3 Get Certificate on the address ADDR channel CH.

If the target device has the Certificate of the current device, it should respond back with 0x6 Negotiate Proceed, inticating that it is ready for the negotiation process.

0x6 Negotiate Proceed

This message is used as a confirmation for beginning of the negotiation process.

Kind Length Description / Value
CRC 4 bytes CRC32
FLAGS 1 byte TYP = 0x6

0x7 Key Data

After receiving the 0x6 Negotiate Proceed message, the receiver should

  1. Generate random AES key (128 bits)
  2. Generate a random channel NG_CH (1 byte)
  3. Generate random address NG_ADDR (3 bytes)

Referred as Key Data, then:

  1. Encrypt Key Data (20 bytes) with Certificate of the requested device
  2. Sign the Key Data (20 bytes) with Private Key of the current device
  3. Store the Key Data into RAM
  4. Respond with this message on the address ADDR / channel CH (stated in 0x5 Negotiate).

Because of length of the encrypted data (256 + 256 bytes), it should be delivered sequentially
(encrypted Key Data and Key Data signature should be concatenated).

Kind Length Description / Value
CRC 4 bytes CRC32
FLAGS 1 byte TYP = 0x7
SEQ 1 byte Number of the packet in the requence
DATA up to 26 bytes Part of encrypted Key Data

Packets are sent one by one, without confirmation for the receiving end. If a packet is received out of order, or same packet received twice, the receiver should just drop the request and switch to default channel / default address.

After sending out this sequence, the sender should follow up with message 0x8 Key Finish.

0x8 Key Finish

After sending out the 0x7 Key Data sequence, the sender should follow up with this message.

Kind Length Description / Value
CRC 4 bytes CRC32
FLAGS 1 byte TYP = 0x8
SEQ 1 byte Number of the last (finish) packet in the requence
KEY_SZ 2 bytes Size of the encrypted key was sent before
SIG_SZ 2 bytes Size of the encrypted key key signature was sent before

The data looks like this:

  1. 0 .. KEY_SZ - 1 Encrypted Key Data
  2. KEY_SZ .. SIG_SZ - 1 Signature of Encrypted Key Data

If the last packet is received in order, the receiving party should:

  1. Verify the Encrypted Key Data with Signature of Encrypted Key Data (using a previously validated certificate of the sender)
  2. Decrypt the Encrypted Key Data (using 0x7 Key Data as a source of data and 0x8 Key Finish as a source of data length) with Private key of the current device
  3. If the RSA key was successfully decrypted, the Key Data should be stored in RAM by DEVICE_ID.
  4. The received should respond back with 0x9 Negotiate Confirm to indicate the key has been successfully received.

Regardless if key was delivered or not, bot device should switch back to the default address / channel.
Key Data should be automatically invalidated in reasonable amount of time.

0x9 Negotiate Confirm

This message sent as an indication of successfully received and processed Key Data (0x7 Key Data / 0x8 Key Finish)

Kind Length Description / Value
CRC 4 bytes CRC32
FLAGS 1 byte TYP = 0x9
DEVICE_TGT 3 bytes ID of the target device
DEVICE_SRC 3 bytes ID of the current device

If the message was received by device with different DEVICE_ID from DEVICE_TGT, the packet should be ignored.

This message should only processed in short time window after sending out the Key data to indicate the data was received and the session can be marked as negotiated.

0xA Request

If the two devices have exchanged the Certificates (0x3 Get Certificate / 0x4 Certificate) and negotiated on Key Data (0x5 Negotiate / 0x6 Negotiate Confirm / 0x7 Key Data) they might open a request exchange.

Kind Length Description / Value
CRC 4 bytes CRC32
FLAGS 1 byte TYP = 0xA
DEVICE_TGT 3 bytes ID of the target device
DEVICE_SRC 3 bytes ID of the current device
GCM_IV 12 bytes Random IV of the GCM for this request
GCM_TAG 8 bytes GCM tag for current requst
REQ_SZ 1 bytes Size of the Request Body divided by 16 (4080 max)

The request should use a AES-128-GCM algorithm to encrypt the request body with GCM_IV and produce a valid GCM_TAG.

If the target device has negotiated on Key Data, it should switch to channel NG_CH, listen on NG_ADDR. If the target device has not yet negotiated with the current device, it should ignore this message.

The currenct device should also switch to channel NG_CH, listen on NG_ADDR for message 0x9 Proceed for short time limit. If the device does not receive a 0x9 Proceed confirmation (or receives one that does not compute) within the time window, it should do several retries (on both defaul and NG_CH channels), and then declare the request failed.

0xB Proceed

If a device received a 0xA Request request, and the current device has negotiated the Key Data with the sender device, the device should respond back with this message .

Kind Length Description / Value
CRC 4 bytes CRC32
FLAGS 1 byte TYP = 0xB
STATUS 1 byte Status flag
SEQ 1 byte In case the STATUS = 0x01 Please retry contains last aknowledged packet

Status flag cat take these values:

  • 0x00 OK proceed
  • 0x01 Please retry (should restart sending out request body starting from SEQ)
  • 0x02 Busy

After receiving this message, the original sender of 0xA Request should start sending the request body 0xC Request Body within specific time window. If the response isn't received in time, unless the device is busy, the sender shourld retry.

0xC Request Body

After receiving the 0xB Proceed message, the original sender of 0xA Request should start sending previously encrypted request body within specific time window.

Because of length of the encrypted data (up to 256 bytes), it should be delivered sequentially.

Kind Length Description / Value
CRC 4 bytes CRC32
FLAGS 1 byte TYP = 0xC
SEQ 1 byte Number of the packet in the requence
DATA upt to 26 bytes encrypted data

Packets are sent one by one, without confirmation for the receiving end. If a packet is received out of order, or same packet received twice, the receiver should just drop the request and switch to default channel / default address.

  • If a packet with SEQ with value of 0 (zero) is received again, the process of receiving the request should be starter over again.

After delivering the message, the sender should wait on same channel / address either for 0xD Response or for 0xF Close Request

0xD Response

If a device received a 0xC Request Body request, and the current device has negotiated the key with the sender device, the device should:

  1. Decrypt the received Request Body with AES-128-GCM / Initial Vector (GCM_IV) / Authentication Tag GCM_TAG
  2. Process the request
  3. Encrypt the response with tthe AES-128-GCM / A new random GCM_IV / GCM_TAG
  4. Send this message back as a response sequentually
  5. This message should be followed by 0xE Response Finish

The request response should use a AES-128-GCM algorithm to encrypt the response body with GCM_IV and produce a valid GCM_TAG.

Kind Length Description / Value
CRC 4 bytes CRC32
FLAGS 1 byte TYP = 0xD
SEQ 1 byte Number of the packet in the requence
DATA upt to 26 bytes encrypted data

0xE Response Finish

After receiving the 0xD Response message, the original sender will follow up with this message.

Because of length of the encrypted data (SZ, up to 240 bytes), it should be delivered sequentially.

Kind Length Description / Value
CRC 4 bytes CRC32
FLAGS 1 byte TYP = 0xE
SEQ 1 byte Number of the packet in the requence
GCM_IV 12 bytes Random IV of the GCM for this response
GCM_TAG 8 byte GCM tag of the response
SZ 1 byte Accurate size of the Response Body divided by 16 (4080 max)

After delivering the response, the sender should wait for some time window for:

  1. Receive 0xA Request in case the device that made current request wants to make more.
  2. Receive 0xF Close Request in case the device that made current request has no more requests.

In case nothing's received, the device should switch back to the default channel / default address.

0xF Close Request

Issued by the party that made the original request to indicate that there is no future requests and the request-response session should be closed.

Kind Length Description / Value
CRC 4 bytes CRC32
FLAGS 1 byte TYP = 0xF
Select a repo