herolib_rust/vault/src/key/symmetric.rs
Lee Smet 2adda10664
Basic API
Signed-off-by: Lee Smet <lee.smet@hotmail.com>
2025-05-15 13:53:16 +02:00

105 lines
3.4 KiB
Rust

//! An implementation of symmetric keys for ChaCha20Poly1305 encryption.
//!
//! The ciphertext is authenticated.
//! The 12-byte nonce is appended to the generated ciphertext.
//! Keys are 32 bytes in size.
use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce, aead::Aead};
use crate::error::CryptoError;
pub struct SymmetricKey([u8; 32]);
/// Size of a nonce in ChaCha20Poly1305.
const NONCE_SIZE: usize = 12;
impl SymmetricKey {
/// Generate a new random SymmetricKey.
pub fn new() -> Self {
let mut key = [0u8; 32];
rand::fill(&mut key);
Self(key)
}
/// Create a new key from existing bytes.
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<SymmetricKey, CryptoError> {
if bytes.len() == 32 {
let mut key = [0u8; 32];
key.copy_from_slice(bytes);
Ok(SymmetricKey(key))
} else {
Err(CryptoError::InvalidKeySize)
}
}
/// View the raw bytes of this key
pub(crate) fn as_raw_bytes(&self) -> &[u8; 32] {
&self.0
}
/// Encrypt a plaintext with the key. A nonce is generated and appended to the end of the
/// message.
pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, CryptoError> {
// Create cipher
let cipher = ChaCha20Poly1305::new_from_slice(&self.0)
.expect("Key is a fixed 32 byte array so size is always ok");
// Generate random nonce
let mut nonce_bytes = [0u8; NONCE_SIZE];
rand::fill(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
// Encrypt message
let mut ciphertext = cipher
.encrypt(nonce, plaintext)
.map_err(|_| CryptoError::EncryptionFailed)?;
// Append nonce to ciphertext
ciphertext.extend_from_slice(&nonce_bytes);
Ok(ciphertext)
}
/// Decrypts a ciphertext with appended nonce.
pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> {
// Check if ciphertext is long enough to contain a nonce
if ciphertext.len() <= NONCE_SIZE {
return Err(CryptoError::DecryptionFailed);
}
// Extract nonce from the end of ciphertext
let ciphertext_len = ciphertext.len() - NONCE_SIZE;
let ciphertext = &ciphertext[0..ciphertext_len];
let nonce_bytes = &ciphertext[ciphertext_len..];
// Create cipher
let cipher = ChaCha20Poly1305::new_from_slice(&self.0)
.expect("Key is a fixed 32 byte array so size is always ok");
let nonce = Nonce::from_slice(nonce_bytes);
// Decrypt message
cipher
.decrypt(nonce, ciphertext)
.map_err(|_| CryptoError::DecryptionFailed)
}
/// Derives a new symmetric key from a password.
///
/// Derivation is done using pbkdf2 with Sha256 hashing.
pub fn derive_from_password(password: &str) -> Self {
/// Salt to use for PBKDF2. This needs to be consistent accross runs to generate the same
/// key. Additionally, it does not really matter what this is, as long as its unique.
const SALT: &[u8; 10] = b"vault_salt";
/// Amount of rounds to use for key generation. More rounds => more cpu time. Changing this
/// also chagnes the generated keys.
const ROUNDS: u32 = 100_000;
let mut key = [0; 32];
pbkdf2::pbkdf2_hmac::<sha2::Sha256>(password.as_bytes(), SALT, ROUNDS, &mut key);
Self(key)
}
}