use rand::RngCore;
use rand::rngs::OsRng;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum EncryptionAlgorithm {
Aes256Gcm,
Aes256Cbc,
ChaCha20Poly1305,
XChaCha20Poly1305,
Aes256GcmSiv,
}
impl EncryptionAlgorithm {
pub fn key_size(&self) -> usize {
match self {
Self::Aes256Gcm => 32,
Self::Aes256Cbc => 32,
Self::ChaCha20Poly1305 => 32,
Self::XChaCha20Poly1305 => 32,
Self::Aes256GcmSiv => 32,
}
}
pub fn iv_size(&self) -> usize {
match self {
Self::Aes256Gcm => 12,
Self::Aes256Cbc => 16,
Self::ChaCha20Poly1305 => 12,
Self::XChaCha20Poly1305 => 24,
Self::Aes256GcmSiv => 12,
}
}
pub fn tag_size(&self) -> usize {
match self {
Self::Aes256Gcm => 16,
Self::Aes256Cbc => 0, Self::ChaCha20Poly1305 => 16,
Self::XChaCha20Poly1305 => 16,
Self::Aes256GcmSiv => 16,
}
}
pub fn recommended_iv_mode(&self) -> IvMode {
match self {
Self::Aes256Gcm => IvMode::Random,
Self::Aes256Cbc => IvMode::Random,
Self::ChaCha20Poly1305 => IvMode::Random,
Self::XChaCha20Poly1305 => IvMode::Random,
Self::Aes256GcmSiv => IvMode::Random,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum IvMode {
Random,
Derived,
Fixed,
Counter,
FileOffset,
}
impl IvMode {
pub fn requires_unique_per_file(&self) -> bool {
match self {
Self::Random => true,
Self::Derived => true,
Self::Fixed => false,
Self::Counter => true,
Self::FileOffset => true,
}
}
pub fn generate_iv(&self, context: &IvContext) -> Vec<u8> {
match self {
Self::Random => self.generate_random_iv(context.iv_size),
Self::Derived => self.generate_derived_iv(context),
Self::Fixed => context.fixed_iv.clone().unwrap_or_default(),
Self::Counter => self.generate_counter_iv(context),
Self::FileOffset => self.generate_file_offset_iv(context),
}
}
fn generate_random_iv(&self, size: usize) -> Vec<u8> {
let mut iv = vec![0u8; size];
for b in iv.iter_mut() {
*b = rand::random();
}
iv
}
fn generate_derived_iv(&self, context: &IvContext) -> Vec<u8> {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
if let Some(data) = &context.data {
hasher.update(data);
}
if let Some(file_path) = &context.file_path {
hasher.update(file_path.as_bytes());
}
if let Some(file_hash) = &context.file_hash {
hasher.update(file_hash);
}
let result = hasher.finalize();
result[..context.iv_size].to_vec()
}
fn generate_counter_iv(&self, context: &IvContext) -> Vec<u8> {
let counter = context.counter.unwrap_or(0);
let mut iv = vec![0u8; context.iv_size];
for i in 0..8 {
if i < context.iv_size {
iv[i] = ((counter >> (i * 8)) & 0xFF) as u8;
}
}
iv
}
fn generate_file_offset_iv(&self, context: &IvContext) -> Vec<u8> {
let offset = context.file_offset.unwrap_or(0);
let mut iv = vec![0u8; context.iv_size];
for i in 0..8 {
if i < context.iv_size {
iv[i] = ((offset >> (i * 8)) & 0xFF) as u8;
}
}
iv
}
}
#[derive(Debug, Clone)]
pub struct IvContext {
pub iv_size: usize,
pub data: Option<Vec<u8>>,
pub file_path: Option<String>,
pub file_hash: Option<String>,
pub counter: Option<u64>,
pub file_offset: Option<u64>,
pub fixed_iv: Option<Vec<u8>>,
}