use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive;
use hex_literal::hex;
use ring::digest::{Context, SHA256, SHA512};
use salsa20::cipher::{KeyIvInit, StreamCipher};
use salsa20::Key as Salsa20_Key;
use salsa20::Salsa20;
use generic_array::GenericArray;
use chacha20::ChaCha20;
use chacha20::cipher::StreamCipherSeek;
mod arc4variant;
use arc4variant::ArcFourVariant;
#[derive(FromPrimitive, ToPrimitive)]
pub enum CipherType {
Null = 0,
ArcFourVariant = 1,
Salsa20 = 2,
ChaCha20 = 3,
}
pub enum CipherValue {
Null,
ArcFourVariant(ArcFourVariant),
Salsa20(Salsa20),
ChaCha20(ChaCha20),
}
impl CipherValue {
pub fn apply_keystream(&mut self, buf: &mut [u8]) {
match self {
Self::Null => (),
Self::ArcFourVariant(c) => c.gen(buf),
Self::Salsa20(c) => c.apply_keystream(buf),
Self::ChaCha20(c) => c.apply_keystream(buf),
}
}
pub fn apply_keystream_pos(&mut self, buf: &mut [u8], pos: usize) {
match self {
Self::Null => (),
Self::ArcFourVariant(c) => c.seek(pos),
Self::Salsa20(c) => c.try_seek(pos as u64).unwrap(),
Self::ChaCha20(c) => c.try_seek(pos as u64).unwrap(),
}
self.apply_keystream(buf);
}
}
impl Default for CipherValue {
fn default() -> Self {
Self::Null
}
}
#[derive(Debug)]
pub enum Error {
InvalidCipher(u32),
}
pub fn new_stream(cipher: u32, key: &[u8]) -> Result<CipherValue, Error> {
let r#type = CipherType::from_u32(cipher).ok_or(Error::InvalidCipher(cipher))?;
Ok(match r#type {
CipherType::Null => CipherValue::Null,
CipherType::ArcFourVariant => CipherValue::ArcFourVariant(ArcFourVariant::new(key)),
CipherType::Salsa20 => {
let nonce = hex!("E830094B97205D2A");
let mut context = Context::new(&SHA256);
context.update(key);
let p2_key = context.finish().as_ref().to_owned();
let key = Salsa20_Key::from_slice(&p2_key[0..32]);
CipherValue::Salsa20(Salsa20::new(&key, &nonce.into()))
}
CipherType::ChaCha20 => {
let mut context = Context::new(&SHA512);
context.update(key);
let p2_key = context.finish().as_ref().to_owned();
let key = GenericArray::from_slice(&p2_key[0..32]);
let nonce = GenericArray::from_slice(&p2_key[32..32 + 12]);
CipherValue::ChaCha20(ChaCha20::new(&key, &nonce))
}
})
}
#[cfg(test)]
mod tests {
use std::convert::TryInto;
use num_traits::ToPrimitive;
use super::*;
#[test]
fn test_null() {
let mut c = new_stream(CipherType::Null.to_u32().unwrap(), &[]).unwrap();
let mut ciphertext = [0x61, 0x62, 0x63, 0x64];
let expected = "abcd";
c.apply_keystream(&mut ciphertext);
let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
assert_eq!(actual, expected);
c.apply_keystream_pos(&mut ciphertext, 10);
let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
assert_eq!(actual, expected);
c.apply_keystream_pos(&mut ciphertext, 0);
let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
assert_eq!(actual, expected);
}
const ARC4_VARIANT_KEY: [u8; 32] = hex!(
"db6fc5e8fc6b3d95497d52e4b215ed7d"
"e04824c12f52f8877762d09c276b3775");
const ARC4_VARIANT_OFFSET: usize = 0;
const ARC4_VARIANT_CIPHERTEXT: [u8; 5] = [
0x90, 0x21, 0xA1, 0x07, 0x53,
];
const ARC4_VARIANT_PLAINTEXT: &str = "Notes";
const ARC4_VARIANT_CIPHERTEXT2: [u8; 8] = [
0x43, 0xE2, 0x7F, 0xA2, 0x1A, 0x75, 0x67, 0xEE,
];
const ARC4_VARIANT_OFFSET2: usize = 5;
const ARC4_VARIANT_PLAINTEXT2: &str = "Password";
#[test]
fn test_arc4_variant() {
let mut c = new_stream(CipherType::ArcFourVariant.to_u32().unwrap(), &ARC4_VARIANT_KEY).unwrap();
let mut ciphertext = ARC4_VARIANT_CIPHERTEXT.clone();
let expected = ARC4_VARIANT_PLAINTEXT;
c.apply_keystream(&mut ciphertext);
let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
assert_eq!(actual, expected);
}
#[test]
fn test_arc4_variant_identity() {
let mut c = new_stream(CipherType::ArcFourVariant.to_u32().unwrap(), &ARC4_VARIANT_KEY).unwrap();
let mut actual = ARC4_VARIANT_CIPHERTEXT.clone();
c.apply_keystream_pos(&mut actual, 0);
c.apply_keystream_pos(&mut actual, 0);
assert_eq!(actual, ARC4_VARIANT_CIPHERTEXT);
}
#[test]
fn test_arc4_variant_offsets() {
let mut c = new_stream(CipherType::ArcFourVariant.to_u32().unwrap(), &ARC4_VARIANT_KEY).unwrap();
let mut ciphertext = ARC4_VARIANT_CIPHERTEXT2.clone();
let expected = ARC4_VARIANT_PLAINTEXT2;
let mut ciphertext = ARC4_VARIANT_CIPHERTEXT.clone();
let expected = ARC4_VARIANT_PLAINTEXT;
c.apply_keystream_pos(&mut ciphertext, ARC4_VARIANT_OFFSET);
c.apply_keystream_pos(&mut ciphertext, ARC4_VARIANT_OFFSET);
c.apply_keystream_pos(&mut ciphertext, ARC4_VARIANT_OFFSET);
let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
assert_eq!(actual, expected);
}
const CHACHA20_KEY: [u8; 64] = hex!(
"47d3d820a2eb2b5b0b57e3397875c5fb"
"ef0676f0f9425b5f0a9ba9f32060134e"
"9a612a5b3be2366f0fab2c8f16980760"
"c82e194a800c0c60c2f9000d5a64daab");
const CHACHA20_OFFSET: usize = 0;
const CHACHA20_CIPHERTEXT: [u8; 8] = [
0x07,
0x69,
0xE8,
0xD6,
0x95,
0x5F,
0x4D,
0x82,
];
const CHACHA20_PLAINTEXT: &str = "Password";
const CHACHA20_CIPHERTEXT2: [u8; 10] = [
0x3C,
0xBC,
0xB1,
0xB5,
0x08,
0xD3,
0x1A,
0x65,
0xD0,
0x52,
];
const CHACHA20_OFFSET2: usize = 94;
const CHACHA20_PLAINTEXT2: &str = "don't tell";
#[test]
fn test_chacha20() {
let mut c = new_stream(CipherType::ChaCha20.to_u32().unwrap(), &CHACHA20_KEY).unwrap();
let mut ciphertext = CHACHA20_CIPHERTEXT.clone();
let expected = CHACHA20_PLAINTEXT;
c.apply_keystream(&mut ciphertext);
let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
assert_eq!(actual, expected);
}
#[test]
fn test_chacha20_identity() {
let mut c = new_stream(CipherType::ChaCha20.to_u32().unwrap(), &CHACHA20_KEY).unwrap();
let mut actual = CHACHA20_CIPHERTEXT.clone();
c.apply_keystream_pos(&mut actual, 0);
c.apply_keystream_pos(&mut actual, 0);
assert_eq!(actual, CHACHA20_CIPHERTEXT);
}
#[test]
fn test_chacha20_offsets() {
let mut c = new_stream(CipherType::ChaCha20.to_u32().unwrap(), &CHACHA20_KEY).unwrap();
let mut ciphertext = CHACHA20_CIPHERTEXT2.clone();
let expected = CHACHA20_PLAINTEXT2;
c.apply_keystream_pos(&mut ciphertext, CHACHA20_OFFSET2);
let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
assert_eq!(actual, expected);
let mut ciphertext = CHACHA20_CIPHERTEXT.clone();
let expected = CHACHA20_PLAINTEXT;
c.apply_keystream_pos(&mut ciphertext, CHACHA20_OFFSET);
let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
assert_eq!(actual, expected);
}
const SALSA20_KEY: [u8; 32] = hex!(
"578b10cfc954562053f926dfdbfa26d1"
"7edc7c7e5f7bedeff88ecc22a8469a08");
const SALSA20_OFFSET: usize = 0;
const SALSA20_CIPHERTEXT: [u8; 6] = [
0x10,
0xE8,
0xFC,
0x22,
0xCF,
0xE4,
];
const SALSA20_PLAINTEXT: &str = "hidden";
const SALSA20_CIPHERTEXT2: [u8; 5] = [
0x70,
0x8C,
0x76,
0xA0,
0xF8,
];
const SALSA20_OFFSET2: usize = 12;
const SALSA20_PLAINTEXT2: &str = "value";
#[test]
fn test_salsa20() {
let mut c = new_stream(CipherType::Salsa20.to_u32().unwrap(), &SALSA20_KEY).unwrap();
let mut ciphertext = SALSA20_CIPHERTEXT.clone();
let expected = SALSA20_PLAINTEXT;
c.apply_keystream(&mut ciphertext);
let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
assert_eq!(actual, expected);
}
#[test]
fn test_salsa20_identity() {
let mut c = new_stream(CipherType::Salsa20.to_u32().unwrap(), &SALSA20_KEY).unwrap();
let mut actual = SALSA20_CIPHERTEXT.clone();
c.apply_keystream_pos(&mut actual, 0);
c.apply_keystream_pos(&mut actual, 0);
assert_eq!(actual, SALSA20_CIPHERTEXT);
}
#[test]
fn test_salsa20_offsets() {
let mut c = new_stream(CipherType::Salsa20.to_u32().unwrap(), &SALSA20_KEY).unwrap();
let mut ciphertext = SALSA20_CIPHERTEXT2.clone();
let expected = SALSA20_PLAINTEXT2;
c.apply_keystream_pos(&mut ciphertext, SALSA20_OFFSET2);
let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
assert_eq!(actual, expected);
let mut ciphertext = SALSA20_CIPHERTEXT.clone();
let expected = SALSA20_PLAINTEXT;
c.apply_keystream_pos(&mut ciphertext, SALSA20_OFFSET);
let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
assert_eq!(actual, expected);
}
}