Secure C2 links
UAT 367 Cybersecurity, Command and Control Links and Unmanned Aircraft Systems Traffic Management
Lesson
By the end of this module you will be able to
- Explain the structure of MAVLink 2 signatures and the replay protection rules
- Create and verify MAVLink 2 frame signatures with SHA-256
- Distinguish authentication from encryption and use DTLS/TLS and AES-GCM correctly
- Design a dual-path C2 and assess its availability
Why this matters
The C2 (command and control) link carries commands to the drone and status back. UAT 312 covered designing a link with enough signal and procedures for when it is lost. This module asks the next question: how does the drone know a command really came from its pilot? Without authentication, anyone with a radio on the same frequency who knows the protocol can send a land or reroute command. In Module 1’s threat model this threat scored 15, high risk.
MAVLink 2 signatures
The MAVLink guide specifies that a signed frame sets the first incompat flag (0x01) and appends a 13-byte signature block:
- Link ID, 1 byte, separating paths such as radio and LTE.
- Timestamp, 6 bytes, in units of 10 microseconds since 1 January 2015 GMT.
- Signature, 6 bytes: the first 48 bits of SHA-256 over the 32-byte secret key followed by the header, payload, CRC, link ID and timestamp.
Replay protection rules from the guide: the timestamp must be greater than that of the previous message on the same stream (system ID, component ID, link ID), and a message whose timestamp is more than 1 minute behind the receiver’s time must be rejected. Without these rules, an attacker could record a valid land command and resend it later. For unsigned messages, the system decides which types to accept, such as RADIO_STATUS from its own radio; all others should be rejected. The guide also warns that the key must never appear in parameters or logs.
Example 1 Signing and verifying a HEARTBEAT frame
The key is derived from a sample phrase for teaching only; a real key must come from a secure random source.
import hashlib
import struct
def crc_x25(data, crc=0xFFFF):
for b in data:
tmp = b ^ (crc & 0xFF)
tmp = (tmp ^ (tmp << 4)) & 0xFF
crc = ((crc >> 8) ^ (tmp << 8) ^ (tmp << 3) ^ (tmp >> 4)) & 0xFFFF
return crc
KEY = hashlib.sha256(b"lesson-demo-passphrase").digest() # 32 bytes, for teaching only
HEARTBEAT = struct.pack("<IBBBBB", 0, 2, 3, 0x81, 4, 3) # custom_mode, type, autopilot, base_mode, status, version
def sign(seq, payload, ts, link_id=0, sysid=1, compid=1, msgid=0, crc_extra=50):
hdr = bytes([0xFD, len(payload), 0x01, 0x00, seq, sysid, compid]) + msgid.to_bytes(3, "little")
crc = crc_x25(hdr[1:] + payload + bytes([crc_extra]))
tail = struct.pack("<H", crc) + bytes([link_id]) + ts.to_bytes(6, "little")
return hdr + payload + tail + hashlib.sha256(KEY + hdr + payload + tail).digest()[:6]
last_ts = {}
def verify(frame, now):
body, sig = frame[:-6], frame[-6:]
if hashlib.sha256(KEY + body).digest()[:6] != sig:
return "reject: bad signature"
ts = int.from_bytes(body[-6:], "little")
stream = (body[5], body[6], body[-7]) # system ID, component ID, link ID
if stream in last_ts and ts <= last_ts[stream]:
return "reject: replayed or out-of-order timestamp"
if ts < now - 6_000_000: # 1 minute = 6,000,000 units of 10 µs
return "reject: older than 1 minute"
last_ts[stream] = ts
return "accept"
now = 123_456_839
f1 = sign(7, HEARTBEAT, 123_456_789)
print(f"frame {len(f1)} bytes: {f1.hex()}")
print("first delivery ->", verify(f1, now))
print("same frame again ->", verify(f1, now))
tampered = bytearray(sign(8, HEARTBEAT, 123_456_800))
tampered[13] ^= 0x01 # flip 1 payload bit
print("tampered payload ->", verify(bytes(tampered), now))
print("old frame, link 1->", verify(sign(9, HEARTBEAT, now - 7_000_000, link_id=1), now))
frame 34 bytes: fd0901000701010000000000000002038104033bac0015cd5b070000c82d318a978f
first delivery -> accept
same frame again -> reject: replayed or out-of-order timestamp
tampered payload -> reject: bad signature
old frame, link 1-> reject: older than 1 minute
The frame is 10 + 9 + 2 + 13 = 34 bytes. The instructor checked that these bytes match the frame pymavlink 2.4.50 produces with the same key, timestamp and sequence. Changing a single payload bit breaks the signature, and the same frame sent again is rejected because its timestamp does not increase.
Authentication is not encryption
MAVLink signing provides authentication and integrity, but the content is still readable. Anyone who intercepts the signal sees the drone’s position and status. Data that must stay confidential, such as video or parcel data, needs encryption as well.
- On IP networks such as LTE, use DTLS 1.3 (RFC 9147) over UDP or TLS 1.3 (RFC 8446) over TCP, which both encrypt and authenticate.
- If you design at packet level yourself, use a mode that both encrypts and authenticates, such as AES-GCM per NIST SP 800-38D, which warns that reusing an IV with the same key even once can open the door to forgeries, a requirement almost as important as keeping the key secret.
- Key management decides who generates keys, how they are distributed, where they are stored, when they are rotated and how they are revoked when a device is lost. It goes wrong more often than the algorithms do.
Dual-path C2
Using radio and LTE together raises availability, but watch for common-mode failures, such as a power cut at the control centre that takes out both paths at once. RTCA DO-362 sets minimum performance for terrestrial C2, and DO-400 gives guidance on lost-C2 procedures, which are still needed even with two paths.
Example 2 C2 availability
Availabilities are assumed values from route statistics; each flight lasts 30 minutes.
A_RADIO, A_LTE, P_COMMON = 0.97, 0.95, 0.002
FLIGHT_MIN = 30
options = {
"radio only": A_RADIO,
"radio + LTE (independent)": 1 - (1 - A_RADIO) * (1 - A_LTE),
"radio + LTE with common mode": (1 - P_COMMON) * (1 - (1 - A_RADIO) * (1 - A_LTE)),
}
for name, a in options.items():
print(f"{name:<30} availability {a:.4f} expected outage {(1 - a) * FLIGHT_MIN * 60:5.1f} s per flight")
radio only availability 0.9700 expected outage 54.0 s per flight
radio + LTE (independent) availability 0.9985 expected outage 2.7 s per flight
radio + LTE with common mode availability 0.9965 expected outage 6.3 s per flight
The second path cuts link outage time sharply, but a common-mode failure of just 0.2% more than doubles the outage compared with the independent case. Power, network and location of the two paths must be separated as far as possible.
Module lab
Lab: enabling signing and testing in SITL
- Run the code from Example 1 and compare with the frame pymavlink produces using the same key and timestamp.
- Enable MAVLink signing in SITL following the ArduPilot or PX4 guide and the GCS software in use.
- Send commands from a script without the key, check that they are rejected and record the log.
- Replay a recorded frame on loopback and check that replay protection works.
- Write a key management plan for the medical delivery network and compute availability with Example 2.
Common mistakes
Watch out
- Thinking signing encrypts the data.
- Accepting every unsigned message type for convenience.
- Storing the key in parameters or logs.
- Reusing an IV in AES-GCM.
- Assuming two paths remove the need for lost-C2 procedures.
Summary
- The MAVLink 2 signature block is 13 bytes; the signature is SHA-256 over a 32-byte key and the frame, truncated to 6 bytes.
- Increasing timestamps per stream and the 1-minute limit protect against replay.
- Signing authenticates but does not hide content; use DTLS/TLS or AES-GCM when confidentiality is needed.
- Dual-path C2 raises availability, but common-mode failures must be reduced and lost-C2 procedures are still required.
Check your understanding
- What makes up the MAVLink 2 signature block, and how many bytes is it?
- How many MAVLink timestamp units are in 1 second?
- Why is a replayed frame rejected even though every byte is valid?
- Two independent paths each have availability 0.9. What is the combined availability?
- What can someone intercepting a signed radio link still see?
Answers
- Link ID 1 byte, timestamp 6 bytes and signature 6 bytes, 13 bytes in total.
- 100,000 units (10 µs each).
- Its timestamp is not greater than the latest timestamp on the same stream.
- All of the content, such as position and status, because signing does not encrypt.
Key formulas
| MAVLink 2 signature | |
| Availability of two paths |
Key references
- MAVLink Development Team. Message signing (authentication). MAVLink developer guide. link
- Dworkin, M. (2007). Recommendation for block cipher modes of operation: Galois/Counter Mode (GCM) and GMAC (NIST SP 800-38D). National Institute of Standards and Technology. link
- Rescorla, E., Tschofenig, H., & Modadugu, N. (2022). The Datagram Transport Layer Security (DTLS) protocol version 1.3 (RFC 9147). RFC Editor. link
- Rescorla, E. (2018). The Transport Layer Security (TLS) protocol version 1.3 (RFC 8446). RFC Editor. link
- RTCA. (2016). Command and control (C2) data link minimum operational performance standards (MOPS) (terrestrial) (DO-362). link
- RTCA. (2023). Guidance material: Lost C2 link procedures (DO-400). link
Further reading
Study the assigned knowledge units in advance, review media and take the module quiz
C2 link design and lost-link procedures
Drone communications and MAVLink
In class / field
Intensive lab and field practice recorded in a lab notebook
Learning evidence: Lab notebook signed by the instructor