use crate::wire::stream::Cipher;
use sha2::{Digest, Sha256};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WireCryptPlugin {
Arc4,
ChaCha,
ChaCha64,
}
impl WireCryptPlugin {
pub fn name(self) -> &'static str {
match self {
WireCryptPlugin::Arc4 => "Arc4",
WireCryptPlugin::ChaCha => "ChaCha",
WireCryptPlugin::ChaCha64 => "ChaCha64",
}
}
}
#[derive(Clone)]
pub struct Rc4 {
s: [u8; 256],
i: u8,
j: u8,
}
impl Rc4 {
pub fn new(key: &[u8]) -> Self {
assert!(!key.is_empty(), "RC4 key must be non-empty");
let mut s = [0u8; 256];
for (i, b) in s.iter_mut().enumerate() {
*b = i as u8;
}
let mut j: u8 = 0;
for i in 0..256 {
j = j.wrapping_add(s[i]).wrapping_add(key[i % key.len()]);
s.swap(i, j as usize);
}
Rc4 { s, i: 0, j: 0 }
}
#[inline]
fn next_byte(&mut self) -> u8 {
self.i = self.i.wrapping_add(1);
self.j = self.j.wrapping_add(self.s[self.i as usize]);
self.s.swap(self.i as usize, self.j as usize);
let idx = self.s[self.i as usize].wrapping_add(self.s[self.j as usize]);
self.s[idx as usize]
}
}
impl Cipher for Rc4 {
fn process(&mut self, data: &mut [u8]) {
for b in data.iter_mut() {
*b ^= self.next_byte();
}
}
}
#[derive(Clone)]
pub struct ChaCha20 {
state: [u32; 16],
block: [u8; 64],
pos: usize,
wide_counter: bool,
}
const CHACHA_CONST: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574];
impl ChaCha20 {
pub fn new(key: &[u8], nonce: &[u8]) -> Self {
assert_eq!(key.len(), 32, "ChaCha20 key must be 32 bytes");
let mut state = [0u32; 16];
state[0..4].copy_from_slice(&CHACHA_CONST);
for i in 0..8 {
state[4 + i] = u32::from_le_bytes(key[i * 4..i * 4 + 4].try_into().unwrap());
}
let wide_counter = match nonce.len() {
12 => {
for i in 0..3 {
state[13 + i] = u32::from_le_bytes(nonce[i * 4..i * 4 + 4].try_into().unwrap());
}
false
}
8 => {
state[14] = u32::from_le_bytes(nonce[0..4].try_into().unwrap());
state[15] = u32::from_le_bytes(nonce[4..8].try_into().unwrap());
true
}
other => panic!("ChaCha nonce must be 12 or 8 bytes, got {other}"),
};
let mut c = ChaCha20 {
state,
block: [0; 64],
pos: 64,
wide_counter,
};
c.refill();
c
}
#[inline]
fn quarter_round(x: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize) {
x[a] = x[a].wrapping_add(x[b]);
x[d] = (x[d] ^ x[a]).rotate_left(16);
x[c] = x[c].wrapping_add(x[d]);
x[b] = (x[b] ^ x[c]).rotate_left(12);
x[a] = x[a].wrapping_add(x[b]);
x[d] = (x[d] ^ x[a]).rotate_left(8);
x[c] = x[c].wrapping_add(x[d]);
x[b] = (x[b] ^ x[c]).rotate_left(7);
}
fn refill(&mut self) {
let mut x = self.state;
for _ in 0..10 {
Self::quarter_round(&mut x, 0, 4, 8, 12);
Self::quarter_round(&mut x, 1, 5, 9, 13);
Self::quarter_round(&mut x, 2, 6, 10, 14);
Self::quarter_round(&mut x, 3, 7, 11, 15);
Self::quarter_round(&mut x, 0, 5, 10, 15);
Self::quarter_round(&mut x, 1, 6, 11, 12);
Self::quarter_round(&mut x, 2, 7, 8, 13);
Self::quarter_round(&mut x, 3, 4, 9, 14);
}
for (i, w) in x.iter_mut().enumerate() {
*w = w.wrapping_add(self.state[i]);
self.block[i * 4..i * 4 + 4].copy_from_slice(&w.to_le_bytes());
}
self.pos = 0;
let (c0, carry) = self.state[12].overflowing_add(1);
self.state[12] = c0;
if self.wide_counter && carry {
self.state[13] = self.state[13].wrapping_add(1);
}
}
}
impl Cipher for ChaCha20 {
fn process(&mut self, data: &mut [u8]) {
for b in data.iter_mut() {
if self.pos == 64 {
self.refill();
}
*b ^= self.block[self.pos];
self.pos += 1;
}
}
}
fn chacha_key(session_key: &[u8]) -> [u8; 32] {
let mut h = Sha256::new();
h.update(session_key);
h.finalize().into()
}
pub fn make_ciphers(
plugin: WireCryptPlugin,
key: &[u8],
nonce: &[u8],
) -> (Box<dyn Cipher>, Box<dyn Cipher>) {
match plugin {
WireCryptPlugin::Arc4 => (Box::new(Rc4::new(key)), Box::new(Rc4::new(key))),
WireCryptPlugin::ChaCha | WireCryptPlugin::ChaCha64 => {
let k = chacha_key(key);
(
Box::new(ChaCha20::new(&k, nonce)),
Box::new(ChaCha20::new(&k, nonce)),
)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rc4_known_answer() {
let mut c = Rc4::new(b"Key");
let mut data = b"Plaintext".to_vec();
c.process(&mut data);
assert_eq!(data, hex_to_vec("BBF316E8D940AF0AD3"));
}
#[test]
fn rc4_roundtrip() {
let key = b"firebird-session-key";
let mut enc = Rc4::new(key);
let mut dec = Rc4::new(key);
let mut buf = b"op_attach payload \x00\x01\x02".to_vec();
let orig = buf.clone();
enc.process(&mut buf);
assert_ne!(buf, orig);
dec.process(&mut buf);
assert_eq!(buf, orig);
}
fn hex_to_vec(s: &str) -> Vec<u8> {
let s: String = s.chars().filter(|c| !c.is_whitespace()).collect();
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
.collect()
}
#[test]
fn chacha20_rfc8439_block() {
let key: Vec<u8> = (0u8..32).collect();
let nonce = hex_to_vec("000000090000004a00000000");
let expected = hex_to_vec(
"10f1e7e4d13b5915500fdd1fa32071c4
c7d1f4c733c0680304 22aa9ac3d46c4e
d2826446079faa0914c2d705d98b02a2
b5129cd1de164eb9cbd083e8a2503c4e",
);
let mut c = ChaCha20::new(&key, &nonce);
let mut buf = vec![0u8; 128];
c.process(&mut buf);
assert_eq!(&buf[64..128], &expected[..]);
}
#[test]
fn chacha20_roundtrip_both_variants() {
let key = [0x42u8; 32];
for nonce in [vec![7u8; 12], vec![9u8; 8]] {
let mut enc = ChaCha20::new(&key, &nonce);
let mut dec = ChaCha20::new(&key, &nonce);
let orig = b"op_attach + segredo \x00\x01\x02 atravessa varios blocos ".repeat(4);
let mut buf = orig.clone();
enc.process(&mut buf);
assert_ne!(buf, orig);
dec.process(&mut buf);
assert_eq!(buf, orig);
}
}
}