Also known as: ADP, P25 keystream, P25 decryption
P25 link-layer encryption protects voice by generating a keystream from the key and
the call’s message indicator, then XORing that keystream onto the IMBE voice codewords
frame by frame.1 Every P25 cipher is used as a stream cipher in this sense:2
whether the primitive is RC4 or a block cipher, the output is a byte stream XORed onto the
protected bits. This means a monitor that has the key can decrypt purely by regenerating
the same keystream — no inversion of the cipher is needed. GopherTrunk’s p25crypto package
realises these keystreams so its assessment harness can attempt decryption against
candidate, known, or weak keys; it performs no key recovery.
The generators
Each algorithm ID selects a keystream construction, all seeded by the Message Indicator (MI) as the IV. The constructions follow the common open implementations (OP25, DSD-FME):
| ALGID | Algorithm | Key | Keystream construction |
|---|---|---|---|
0xAA |
ADP / RC4 | 5 bytes | RC4 keyed with key ‖ first 8 MI octets, discard 256 bytes, then XOR |
0x81 |
DES-OFB | 8 bytes | Single-DES in OFB; IV = first 8 MI octets |
0x85 |
AES-128 | 16 bytes | AES-OFB; IV = MI left-justified into 16 bytes |
0x84/0x89 |
AES-256 (-OFB) | 32 bytes | AES-256-OFB; IV = MI left-justified into 16 bytes |
0x83 |
TDES-2 | 16 bytes | Two-key 3DES expanded to K1‖K2‖K1, OFB |
0x86 |
TDES | 24 bytes | Three-key 3DES in OFB |
For the block ciphers, OFB (output feedback) mode turns the block cipher into a byte-stream generator: the IV is repeatedly re-encrypted and the output is the keystream, independent of the plaintext. The MI supplies that IV — exactly 8 octets for DES, and left-justified into a 16-byte block for AES (the exact TIA MI-to-IV expansion for AES is a refinement over this simplest documented form).
The ADP 256-byte discard
ADP is RC4 keyed with the 5-octet key followed by the first eight
octets of the MI, but with one non-obvious step: the first 256 keystream bytes are thrown
away before any encryption begins. This warm-up discard is a spec / reverse-engineering
detail — it is the OP25/DSD-FME convention and is what makes GopherTrunk’s ADP keystream
line up with real ADP traffic. Getting it wrong (or omitting it) produces a keystream that
XORs to garbage even with the correct key, which is why the constant is called out
explicitly (adpDiscard = 256). The discard mitigates the well-known RC4 key-schedule
weakness where early output leaks key structure — though it does not rescue ADP’s tiny
40-bit key from brute force.
Relevance to SDR
internal/cryptolab/engine/p25crypto/keystream.go realises these generators so GopherTrunk
can attempt decryption when a key is supplied or under test — including a small dictionary
of weak/default keys (all-zero, all-FF, incrementing) that a misconfigured radio might
carry. The package deliberately stops at producing the byte-stream keystream; mapping it onto
the exact IMBE voice-bit positions is the caller’s job, and no key recovery is performed. This
keeps decryption strictly a function of the key ID and key an
operator already holds — the honest boundary between monitoring and attacking a system.
Sources
-
Project 25 — Wikipedia, on the P25 standard and its encryption. Keystream constructions follow the open OP25 / DSD-FME implementations. ↩
-
Stream cipher — Wikipedia, on XOR-keystream encryption and OFB-mode block-cipher keystreams. ↩