use super::utils::sha1_of;
use derive_more::Debug;
use rc4::{KeyInit, Rc4, StreamCipher};
#[derive(Debug)]
#[debug("Encryptor")]
pub struct Encryptor(Rc4);
impl Encryptor {
pub fn encrypt(&mut self, buf: &mut [u8]) {
self.0.apply_keystream(buf)
}
}
#[derive(Debug)]
#[debug("Decryptor")]
pub struct Decryptor(Rc4);
impl Decryptor {
pub fn decrypt(&mut self, buf: &mut [u8]) {
self.0.apply_keystream(buf)
}
}
#[derive(Debug)]
pub struct Crypto {
pub encryptor: Encryptor,
pub decryptor: Decryptor,
}
pub(super) fn crypto_for_outbound_connection(
secret: &crypto_bigint::U768,
info_hash: &[u8; 20],
) -> (Encryptor, Decryptor) {
let encryption_key = sha1_of![b"keyA", &secret.to_be_bytes(), info_hash];
let decryption_key = sha1_of![b"keyB", &secret.to_be_bytes(), info_hash];
let mut encryptor =
Encryptor(Rc4::new_from_slice(&encryption_key).expect("20-byte key is supported"));
let mut decryptor =
Decryptor(Rc4::new_from_slice(&decryption_key).expect("20-byte key is supported"));
let mut buf = [0u8; 1024];
encryptor.encrypt(&mut buf);
decryptor.decrypt(&mut buf);
(encryptor, decryptor)
}
pub(super) fn crypto_for_inbound_connection(
secret: &crypto_bigint::U768,
info_hash: &[u8; 20],
) -> (Encryptor, Decryptor) {
let encryption_key = sha1_of![b"keyB", &secret.to_be_bytes(), info_hash];
let decryption_key = sha1_of![b"keyA", &secret.to_be_bytes(), info_hash];
let mut encryptor =
Encryptor(Rc4::new_from_slice(&encryption_key).expect("20-byte key is supported"));
let mut decryptor =
Decryptor(Rc4::new_from_slice(&decryption_key).expect("20-byte key is supported"));
let mut buf = [0u8; 1024];
encryptor.encrypt(&mut buf);
decryptor.decrypt(&mut buf);
(encryptor, decryptor)
}
#[cfg(test)]
pub(crate) fn crypto_pair(info_hash: &[u8; 20]) -> (Encryptor, Decryptor) {
let secret: [u8; 96] = rand::random();
let key = sha1_of![b"keyA", &secret, info_hash];
(
Encryptor(Rc4::new_from_slice(&key).expect("20-byte key is supported")),
Decryptor(Rc4::new_from_slice(&key).expect("20-byte key is supported")),
)
}
#[cfg(test)]
mod tests {
use super::*;
use crypto_bigint::{Random, U768};
use rand::rng;
#[test]
fn test_inbound_outbound_compatibility() {
let info_hash: [u8; 20] = rand::random();
let secret = U768::random_from_rng(&mut rng());
let (mut out_enc, mut out_dec) = crypto_for_outbound_connection(&secret, &info_hash);
let (mut in_enc, mut in_dec) = crypto_for_inbound_connection(&secret, &info_hash);
let data: [u8; 100] = rand::random();
let mut encrypted = data;
out_enc.encrypt(&mut encrypted);
in_dec.decrypt(&mut encrypted);
assert_eq!(data, encrypted);
let mut encrypted2 = data;
in_enc.encrypt(&mut encrypted2);
out_dec.decrypt(&mut encrypted2);
assert_eq!(data, encrypted2);
}
#[test]
fn test_decrypt_in_chunks() {
let (mut enc, mut dec) = crypto_pair(&rand::random());
let plaintext: [u8; 100] = rand::random();
for chunk_size in 1..=100 {
let encrypted = {
let mut buf = plaintext;
enc.encrypt(&mut buf);
buf
};
let mut chunked_data = encrypted;
for chunk in chunked_data.chunks_mut(chunk_size) {
dec.decrypt(chunk);
}
assert_eq!(plaintext, chunked_data, "decryption failed for chunk size {chunk_size}");
}
}
#[test]
fn test_encrypt_in_chunks() {
let (mut enc, mut dec) = crypto_pair(&rand::random());
let plaintext: [u8; 100] = rand::random();
for chunk_size in 1..=100 {
let mut chunked_data = plaintext;
for chunk in chunked_data.chunks_mut(chunk_size) {
enc.encrypt(chunk);
}
dec.decrypt(&mut chunked_data);
assert_eq!(
plaintext, chunked_data,
"encryption/decryption failed for chunk size {chunk_size}"
);
}
}
}