/*
* WhackerLink - WhackerLinkConsoleV2
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* Derrived from https://github.com/boatbod/op25/op25/gr-op25_repeater/lib/p25_crypt_algs.cc
*
* Copyright (C) 2025 Caleb, K4PHP
*
*/
namespace WhackerLinkConsoleV2
{
///
/// P25 Crypto class
///
public class P25Crypto
{
private ProtocolType protocol;
private byte algId;
private ushort keyId;
private byte[] messageIndicator = new byte[9];
private Dictionary keys = new Dictionary();
private byte[] adpKeystream = new byte[469];
private int adpPosition;
///
/// Creates an instance of
///
public P25Crypto()
{
this.protocol = ProtocolType.Unknown;
this.algId = 0x80;
this.keyId = 0;
this.adpPosition = 0;
}
///
/// Clear keys
///
public void Reset()
{
keys.Clear();
}
///
/// Add key to keys list
///
///
///
///
public void AddKey(ushort keyid, byte algid, byte[] key)
{
if (keyid == 0 || algid == 0x80)
return;
keys[keyid] = new KeyInfo(algid, key);
}
///
/// Prepare P25 encryption meta data info
///
///
///
///
///
///
public bool Prepare(byte algid, ushort keyid, ProtocolType protocol, byte[] MI)
{
this.algId = algid;
this.keyId = keyid;
Array.Copy(MI, this.messageIndicator, Math.Min(MI.Length, this.messageIndicator.Length));
if (!keys.ContainsKey(keyid))
{
return false;
}
if (algid == 0xAA)
{
this.adpPosition = 0;
this.protocol = protocol;
AdpKeystreamGen();
return true;
}
return false;
}
///
/// Process P25 frames for crypto
///
///
///
///
///
public bool Process(byte[] PCW, FrameType frameType, int voiceSubframe)
{
if (!keys.ContainsKey(keyId))
return false;
if (algId == 0xAA)
return AdpProcess(PCW, frameType, voiceSubframe);
return false;
}
///
/// Process RC4
///
///
///
///
///
private bool AdpProcess(byte[] PCW, FrameType frameType, int voiceSubframe)
{
int offset = 256;
switch (frameType)
{
case FrameType.LDU1: offset = 0; break;
case FrameType.LDU2: offset = 101; break;
case FrameType.V4_0: offset += 7 * voiceSubframe; break;
case FrameType.V4_1: offset += 7 * (voiceSubframe + 4); break;
case FrameType.V4_2: offset += 7 * (voiceSubframe + 8); break;
case FrameType.V4_3: offset += 7 * (voiceSubframe + 12); break;
case FrameType.V2: offset += 7 * (voiceSubframe + 16); break;
default: return false;
}
if (protocol == ProtocolType.P25Phase1)
{
offset += (adpPosition * 11) + 267 + (adpPosition < 8 ? 0 : 2);
adpPosition = (adpPosition + 1) % 9;
for (int j = 0; j < 11; ++j)
{
PCW[j] ^= adpKeystream[j + offset];
}
}
else if (protocol == ProtocolType.P25Phase2)
{
for (int j = 0; j < 7; ++j)
{
PCW[j] ^= adpKeystream[j + offset];
}
PCW[6] &= 0x80;
}
return true;
}
///
/// Create RC4 key stream
///
private void AdpKeystreamGen()
{
byte[] adpKey = new byte[13];
byte[] S = new byte[256];
byte[] K = new byte[256];
if (!keys.ContainsKey(keyId))
return;
byte[] keyData = keys[keyId].Key;
int keySize = keyData.Length;
int padding = Math.Max(5 - keySize, 0);
int i, j = 0, k;
for (i = 0; i < padding; i++)
adpKey[i] = 0;
for (; i < 5; i++)
adpKey[i] = keySize > 0 ? keyData[i - padding] : (byte)0;
for (i = 5; i < 13; ++i)
{
adpKey[i] = messageIndicator[i - 5];
}
for (i = 0; i < 256; ++i)
{
K[i] = adpKey[i % 13];
S[i] = (byte)i;
}
for (i = 0; i < 256; ++i)
{
j = (j + S[i] + K[i]) & 0xFF;
Swap(S, i, j);
}
i = j = 0;
for (k = 0; k < 469; ++k)
{
i = (i + 1) & 0xFF;
j = (j + S[i]) & 0xFF;
Swap(S, i, j);
adpKeystream[k] = S[(S[i] + S[j]) & 0xFF];
}
}
///
/// Preform a swap
///
///
///
///
private void Swap(byte[] S, int i, int j)
{
byte temp = S[i];
S[i] = S[j];
S[j] = temp;
}
///
/// P25 protocol type
///
public enum ProtocolType
{
Unknown = 0,
P25Phase1,
P25Phase2
}
///
/// P25 frame type
///
public enum FrameType
{
Unknown = 0,
LDU1,
LDU2,
V2,
V4_0,
V4_1,
V4_2,
V4_3
}
///
/// Key info object
///
private class KeyInfo
{
public byte AlgId { get; }
public byte[] Key { get; }
public KeyInfo(byte algid, byte[] key)
{
AlgId = algid;
Key = key;
}
}
}
}