Also known as: Motorola alias cipher, per-byte alias cipher
The Motorola talker-alias cipher is the proprietary per-byte obfuscation Motorola applies to a radio’s talker alias — the human-readable display name — before it is fragmented across the air.1 It is not encryption for confidentiality in the AES sense; it is a lightweight, undocumented scrambling of the alias string that a receiver must reverse to read the name. Its structure is inferred from public protocol notes: a length-seeded 16-bit accumulator, a 256-byte substitution table, and an odd-multiplier (modular-inverse-mod-256) step, with the decoded bytes read as UTF-16 BE.
This cipher is reverse-engineered and unverified. GopherTrunk does not decode it by default and never presents its output as a confirmed name — the caveats below are not incidental, they are the whole point of the page.
The per-byte transform
The decode-side transform lives in internal/radio/p25/motorola/alias.go. It exists only in
reverse-engineering notes — there is no spec table to point at — so the exact algorithm is
worth showing:
// accum seeded with the encoded length; LUT is a 256-byte signed table.
accum = uint16(len(encoded))
for i, raw := range encoded {
accum = uint16(uint32(accum)*293 + 0x72E9) // stepped accumulator
lut := motorolaAliasLUT[int(int8(raw))+128] // signed table lookup
m1 := int8(int(lut) - int(int8(accum>>8)))
var m2 int8 = 1 // odd-multiplier search
stop := int8(accum | 1)
increment := stop << 1
for stop != 1 && m2 != -1 {
stop += increment
m2 += 2
}
decoded[i] = byte(int8(int(m1) * int(m2)))
accum = uint16(uint32(accum) + uint32(raw) + 1) // advance
}
The m2 loop finds the multiplicative inverse of accum | 1 modulo 256 (odd values are
units mod 256), and m1 mixes the table output with the high byte of the accumulator. The
decoded byte stream is then read as UTF-16 BE and rendered to printable ASCII.
Verification status — read this
The SUID framing around the cipher (the WACN / System / Radio ID
prefix) is verified against real traffic. The cipher itself is not. Per GopherTrunk issue
#773: the 256-byte table (motorolaAliasLUT) is a placeholder permutation of unconfirmed
provenance, the accumulator constants (293, 0x72E9) are inferred, and the routine decodes
nothing on live traffic. The one partial capture available (RID 200062) is mathematically
underdetermined — dozens of distinct constant-sets reproduce its known bytes while disagreeing
on the unknown ones, and one alias character is unrecoverable from that sample — so it cannot
pin the table. This mirrors GopherTrunk’s own house rule of labelling
reverse-engineered work as best-effort and not hardware-confirmed.
Because a wrong table could fabricate a plausible name, the decode is gated behind the
CipherVerified constant, which is false. While it is false, DecodeMessage never reports
an alias as reliable, and callers must not surface the output as a confirmed name. The
constant is to be flipped to true only together with a committed regression fixture mapping
real encoded bytes to the correct plaintext for the same RID — never on inference alone.
Licensing
A working implementation exists in SDRTrunk, but SDRTrunk is GPLv3 and GopherTrunk is Apache-2.0, so that table and decode must not be ported into GopherTrunk. A verified table therefore requires either ground-truth captures GopherTrunk can solve independently or a separate licensing path. Until then the cipher stays off, and GopherTrunk instead logs the raw encoded region so the cryptanalysis can be finished from committed data.
Sources
-
Project 25 — Wikipedia, on the P25 standard. The cipher is a proprietary Motorola extension, reverse-engineered and unverified per GopherTrunk issue #773; no vendor specification exists. ↩