skip to content

In RFC 8907 TACACS+, what protects the packet body after the 12-byte header, and why is that not encryption?

level: middleimportance: must knowfreq 55%

answer

  1. the header travels in the clear
  2. a keystream, not a cipher
  3. MD5 chained to body length
  4. session_id, secret, version, seq_no
  5. same pad both ways, XOR inverts

basics

~20 s

RFC 8907 XORs the TACACS+ body with a pseudo_pad: chained MD5 digests over session_id, the shared secret, version and seq_no. Every input but the secret is readable in the cleartext header, so it is keyed obfuscation, not encryption.

solid answer

~40 s

The body is masked by a single XOR: `ENCRYPTED {data} = data ^ pseudo_pad`. The pad is a chain of 16-byte MD5 digests truncated to the body's length, where `MD5_1 = MD5{session_id, key, version, seq_no}` and each later digest appends the previous digest to that same input stream. Three of those four inputs sit in the cleartext 12-byte header — `session_id`, the version and `seq_no` — so the shared secret is the only unknown. The receiver recomputes the same pad and XORs again, since XOR is its own inverse. RFC 8907 deliberately calls this obfuscation: there is no integrity check, no replay protection and no forward secrecy, and MD5 is cheap enough that a capture plus known plaintext makes offline guessing of the secret practical.

code

pseudocode · 14 lines
pseudocode
function pseudo_pad(session_id, key, version, seq_no, body_length):
    digest = MD5(session_id, key, version, seq_no)      # MD5_1
    pad    = digest
    while length(pad) < body_length:
        digest = MD5(session_id, key, version, seq_no, digest)
        pad    = pad concatenated with digest
    return first body_length octets of pad

function mask(body, pad):
    for each octet i in body:
        out[i] = body[i] XOR pad[i]
    return out

# de-obfuscation calls mask() with the same pad: XOR is its own inverse

go deeper

for a junior

Recall the shape: the 12-byte header is readable by anyone, and only the body is masked. The mask comes from a shared secret that the network device and the device-administration server both hold.

for a middle

Be able to state the four inputs to the first digest, point out that three of them are in the cleartext header, and explain why a keystream applied with XOR is obfuscation rather than encryption.

for a senior

Show what a capture is worth in practice: known plaintext recovers pad octets, offline guessing of the secret is unthrottled, and changing the key later does not undo what was already recorded.

for a principal

The judgment call is whether a mechanism with no integrity and no forward secrecy belongs on any path you do not physically control, and what a transport replacement costs across an estate.

## What is covered, and what is not A TACACS+ packet is a **12-byte cleartext header** followed by a body. The header carries `major_version` and `minor_version`, the packet type, `seq_no`, a `flags` byte, `session_id` and `length`. **None of the header is protected.** Everything after it — an authentication, authorization or accounting body — is masked, and on the classic transport that masking is the only protection the protocol offers. The operation RFC 8907 Section 4.5 defines is one XOR: ```pseudocode ENCRYPTED {data} = data ^ pseudo_pad ``` There is no cipher, no block mode, no initialisation vector and no authentication tag. There is a **keystream**, and the body is masked with it. ## How the pad is built - `MD5_1 = MD5{session_id, key, version, seq_no}` produces one 16-byte digest. `key` is the shared secret configured for that client; `version` is the header's version octet; `session_id` and `seq_no` are the header's own fields. - Each subsequent digest hashes **the same four inputs with the previous digest appended** to the stream, so the chain extends deterministically. - The digests are concatenated and the result is **truncated to the body's length**, giving a pad exactly as long as the data it masks. - The receiver, holding the same shared secret, recomputes an identical pad and XORs again. One routine serves both directions. Of the four inputs, **three are printed in the clear on the front of every packet**. The shared secret is the only unknown, and it is a long-lived value an administrator typed in — often years ago. ## Why this is not encryption | Property | Classic TACACS+ body obfuscation | |---|---| | Confidentiality | only against an observer who neither holds nor guesses the secret | | Integrity | none — there is no digest or message authentication code over the body | | Replay protection | none | | Forward secrecy | none — one secret opens every capture ever recorded | | Peer authentication | none directly; a wrong key surfaces only as a failed length check | The distinction matters because the word *encrypted* makes people reason about the traffic as if it were a protected channel. RFC 8907's security considerations (Section 10.1) state the position plainly: the protocol provides **no meaningful integrity, no privacy, no replay protection and no forward secrecy**; brute-force attacks on the secret are made cheaper by how efficiently MD5 computes; and known-plaintext and chosen-plaintext attacks reduce that cost further. An attacker with access to the data stream should be assumed able to read and modify every packet. ## What a capture actually yields 1. **Structure for free.** The packet type, the session, the sequence position and the body length are all in the clear, so an attacker knows exactly what kind of body they are looking at before they touch it. 2. **Pad octets from known plaintext.** Much of a body is predictable: fixed enumerations, a username the attacker already knows, a well-known command string. Wherever the plaintext is known, `pad_octet = ciphertext_octet XOR plaintext_octet` recovers that octet of the pad exactly. 3. **Offline guessing of the secret.** Every input to the digest but the key is visible, so a candidate key can be tested by recomputing the pad and checking whether the body unmasks into something structurally valid. Nothing rate-limits that loop, and it runs entirely offline from a recorded capture. 4. **Chosen plaintext.** An attacker who can cause logins or commands to be attempted chooses part of the plaintext, which sharpens both of the above. ## What varies, and what does not Within one TACACS+ session — one authentication sequence, one authorization exchange or one accounting exchange, named by its `session_id` — the secret, the version and `session_id` are constant, and `seq_no` increments with every packet. So the pad differs packet to packet, but **deterministically**, from values an observer can read. It is not a fresh random pad, and it is not a one-time pad in any useful sense: a one-time pad is unconditionally secure precisely because its pad is random, as long as the message and never reused, and none of those three things is true here. Because the derivation is fixed, recovering the shared secret at any point in the future retroactively unmasks every recorded session, and unmasks every future one until the key is changed. That is the concrete content of "no forward secrecy", and it is why RFC 9887 answers the problem by carrying TACACS+ inside TLS 1.3 rather than by repairing the pad. ## How to say it in an interview Lead with the mechanism, not the verdict: four inputs, three of them public, one XOR, a pad as long as the body. Then give the verdict the specification itself gives, which is that this is obfuscation. A candidate who only knows the slogan says "TACACS+ encrypts the whole packet"; a candidate who knows the mechanism can say which three header fields an attacker already has and what that leaves them needing.

  • What makes the pad differ from one packet to the next inside a single TACACS+ session?
    Only `seq_no`. The shared secret, the version octet and `session_id` are fixed for the life of that session, and `seq_no` increments with every packet, so `MD5_1` and the whole chain below it change. The variation is deterministic and derived from a field anyone can read, not from fresh randomness.
  • Why does this scheme give no forward secrecy?
    The pad is a pure function of the shared secret plus three cleartext header fields, and nothing ephemeral enters it. Anyone who later learns the secret — from a configuration backup, a log, or offline guessing against a capture — can recompute every pad for every packet they recorded, and for every packet sent until the key is changed.
  • Does the header's length field reveal anything useful on its own?
    Yes. Combined with the packet type it narrows what the body can be: a short authorization REQUEST carries few argument-value pairs, a long one carries many. The specification also recommends a maximum packet size of 2^16, so an unusually large `length` is itself a signal. None of this needs the secret.

Think of a stencil cut once from a secret an operator typed in years ago: the receiver lays the same stencil over the bytes to read them, and anyone who ever recovers that stencil can read every conversation they recorded, past and future.

saying these in an interview costs you the question

  • Says TACACS+ encrypts the whole packet, header included.
  • Calls the MD5 pad strong encryption because MD5 is cryptographic.
  • Believes each packet is masked with a fresh random pad.
  • Thinks a captured body cannot be altered without the secret.
  • Assumes the shared secret is used only at connection setup.