From 07f9cdcf9a9674017d59d26eb01b11e403922b05 Mon Sep 17 00:00:00 2001 From: Michele Esposito <34438276+mikesposito@users.noreply.github.com> Date: Mon, 4 Dec 2023 23:23:53 +0100 Subject: [PATCH] feat: extension traits (#21) * feat: extension traits * chore: run rustfmt --- README.md | 32 +-- cipher/LICENSE | 21 ++ cipher/key/src/lib.rs | 110 +++++++- cipher/src/lib.rs | 4 +- cipher/src/permutation/chacha20/mod.rs | 10 +- enclave/Cargo.toml | 2 +- enclave/LICENSE | 21 ++ enclave/README.md | 114 +++++++++ enclave/src/errors.rs | 20 ++ enclave/src/lib.rs | 341 +++++++++++++++++++++++-- enclave/src/traits.rs | 43 ++++ src/lib.rs | 18 +- src/main.rs | 21 +- 13 files changed, 682 insertions(+), 75 deletions(-) create mode 100644 cipher/LICENSE create mode 100644 enclave/LICENSE create mode 100644 enclave/README.md create mode 100644 enclave/src/traits.rs diff --git a/README.md b/README.md index 954415d..3482d43 100644 --- a/README.md +++ b/README.md @@ -50,33 +50,27 @@ Secured is straightforward to use from the command line. Here are the basic comm To use Secured as a library in your Rust application, simply import the package and utilize its encryption and decryption functions as per your requirements. -```rust -use secured::enclave::Enclave; -use secured::cipher::Key; +#### Encrypting Data -fn main() { - // Key generation (32bytes for the key, 16 bytes for salt) - let key = Key::<32, 16>::new(b"my password", 900_000); // 900K rounds +```rust +use secured_enclave::{Enclave, Encryptable, KeyDerivationStrategy}; - // Leave some readable metadata (but signed!) - let metadata = b"some metadata".to_vec(); +let password = "strong_password"; +let encrypted_string = "Hello, world!".encrypt(password.to_string(), KeyDerivationStrategy::default()); +``` - // Using Enclave for data encapsulation (&str metadata, 8-byte nonce) - let enclave = - Enclave::from_plain_bytes(metadata, key.pubk, b"Some bytes to encrypt".to_vec()) - .unwrap(); +#### Decrypting Data - // Get encrypted bytes (ciphertext) - println!("Encrypted bytes: {:?}", enclave.encrypted_bytes); +```rust +use secured_enclave::{Decryptable, EnclaveError}; - // Decrypt Enclave - let decrypted_bytes = enclave.decrypt(key.pubk).unwrap(); +let password = "strong_password"; +let decrypted_result = encrypted_data.decrypt(password.to_string()); - assert_eq!(decrypted_bytes, b"Some bytes to encrypt"); -} +println!("Decrypted data: {:?}", String::from_utf8(decrypted_data).unwrap()) ``` -See [package documentation](https://docs.rs/secured/0.3.0/) for more information +See [Enclave documentation](enclave/README.md) for more advanced usage ## Contributing diff --git a/cipher/LICENSE b/cipher/LICENSE new file mode 100644 index 0000000..b0affab --- /dev/null +++ b/cipher/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Michele Esposito + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/cipher/key/src/lib.rs b/cipher/key/src/lib.rs index 1761a3e..5cfdd91 100644 --- a/cipher/key/src/lib.rs +++ b/cipher/key/src/lib.rs @@ -27,14 +27,18 @@ impl Key { /// /// # Panics /// Panics if the key derivation fails. - pub fn new(password: &[u8], rounds: u32) -> Self { + pub fn new(password: &[u8], strategy: KeyDerivationStrategy) -> Self { // Generate a random salt value let salt = random_bytes::(); // Derive the public key using PBKDF2 algorithm let mut pubk = [0; P]; - if pbkdf2::>(password, &salt, rounds, &mut pubk).is_err() { - panic!("Key derivation failed") + match strategy { + KeyDerivationStrategy::PBKDF2(rounds) => { + if pbkdf2::>(password, &salt, rounds as u32, &mut pubk).is_err() { + panic!("Key derivation failed") + } + } } Self { pubk, salt } @@ -52,11 +56,16 @@ impl Key { /// /// # Panics /// Panics if the key derivation fails. - pub fn with_salt(password: &[u8], salt: [u8; S], rounds: u32) -> Self { + pub fn with_salt(password: &[u8], salt: [u8; S], strategy: KeyDerivationStrategy) -> Self { // Derive the public key using PBKDF2 algorithm with the provided salt let mut pubk = [0; P]; - if pbkdf2::>(password, &salt, rounds, &mut pubk).is_err() { - panic!("Key derivation failed") + + match strategy { + KeyDerivationStrategy::PBKDF2(rounds) => { + if pbkdf2::>(password, &salt, rounds as u32, &mut pubk).is_err() { + panic!("Key derivation failed") + } + } } Self { pubk, salt } @@ -69,3 +78,92 @@ pub fn random_bytes() -> [u8; S] { OsRng.fill_bytes(&mut bytes); bytes } + +#[derive(Clone, Debug)] +pub enum KeyDerivationStrategy { + PBKDF2(usize), +} + +impl Default for KeyDerivationStrategy { + fn default() -> Self { + KeyDerivationStrategy::PBKDF2(900_000) + } +} + +impl TryFrom> for KeyDerivationStrategy { + type Error = String; + + fn try_from(bytes: Vec) -> Result { + match bytes[0] { + 0 => { + let rounds_bytes = &bytes[1..]; + let rounds = usize::from_be_bytes(rounds_bytes.try_into().or(Err("Invalid rounds bytes"))?); + Ok(KeyDerivationStrategy::PBKDF2(rounds)) + } + _ => Err("Invalid key derivation strategy".to_string()), + } + } +} + +impl From for Vec { + fn from(strategy: KeyDerivationStrategy) -> Self { + match strategy { + KeyDerivationStrategy::PBKDF2(rounds) => [vec![0u8], rounds.to_be_bytes().to_vec()].concat(), + } + } +} + +impl PartialEq for KeyDerivationStrategy { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (KeyDerivationStrategy::PBKDF2(rounds), KeyDerivationStrategy::PBKDF2(rounds2)) => { + rounds == rounds2 + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_key_derivation() { + let password = "password".as_bytes(); + + let key = Key::<32, 32>::new(password, KeyDerivationStrategy::PBKDF2(10_000)); + let key2 = Key::<32, 32>::with_salt(password, key.salt, KeyDerivationStrategy::PBKDF2(10_000)); + + assert_eq!(key.pubk, key2.pubk); + } + + #[test] + fn test_key_derivation_with_different_salt() { + let password = "password".as_bytes(); + + let key = Key::<32, 32>::new(password, KeyDerivationStrategy::PBKDF2(10_000)); + let key2 = Key::<32, 32>::new(password, KeyDerivationStrategy::PBKDF2(10_000)); + + assert_ne!(key.pubk, key2.pubk); + } + + #[test] + fn test_key_derivation_with_different_rounds() { + let password = "password".as_bytes(); + + let key = Key::<32, 32>::new(password, KeyDerivationStrategy::PBKDF2(10_000)); + let key2 = Key::<32, 32>::new(password, KeyDerivationStrategy::PBKDF2(11_000)); + + assert_ne!(key.pubk, key2.pubk); + } + + #[test] + fn test_key_strategy_serialization_deserialization() { + let strategy = KeyDerivationStrategy::PBKDF2(10_000); + + let serialized: Vec = strategy.clone().into(); + let deserialized = KeyDerivationStrategy::try_from(serialized).unwrap(); + + assert_eq!(strategy, deserialized); + } +} diff --git a/cipher/src/lib.rs b/cipher/src/lib.rs index 1f187e9..06ea034 100644 --- a/cipher/src/lib.rs +++ b/cipher/src/lib.rs @@ -48,7 +48,7 @@ //! let is_decryption_ok = verified_decrypted_data.is_ok(); //! //! println!("Decrypted and verified data: {:?}", verified_decrypted_data.unwrap()); -//! +//! //! ``` //! //! ## Modules @@ -58,7 +58,7 @@ pub mod permutation; -pub use secured_cipher_key::{random_bytes, Key}; +pub use secured_cipher_key::{random_bytes, Key, KeyDerivationStrategy}; pub use permutation::{ChaCha20, Permutation, Poly1305, SignedEnvelope}; diff --git a/cipher/src/permutation/chacha20/mod.rs b/cipher/src/permutation/chacha20/mod.rs index 3f57729..2705b94 100644 --- a/cipher/src/permutation/chacha20/mod.rs +++ b/cipher/src/permutation/chacha20/mod.rs @@ -1,5 +1,5 @@ mod core; -pub use core::{CHACHA20_NONCE_SIZE, permute, xor_bytes, Block, CONSTANTS, STATE_WORDS}; +pub use core::{permute, xor_bytes, Block, CHACHA20_NONCE_SIZE, CONSTANTS, STATE_WORDS}; use super::Permutation; @@ -37,10 +37,10 @@ impl ChaCha20 { /// # Example /// ``` /// use secured_cipher::{ChaCha20, Permutation}; - /// + /// /// let mut chacha20 = ChaCha20::new(); /// chacha20.init(&[0_u8; 32], &[0_u8; 12]); - /// + /// /// let keystream_block = chacha20.next_keystream(); /// // `keystream_block` now contains the next 64 bytes of the keystream /// ``` @@ -135,10 +135,10 @@ impl Permutation for ChaCha20 { /// # Example /// ``` /// use secured_cipher::{ChaCha20, Permutation}; - /// + /// /// let mut chacha20 = ChaCha20::new(); /// chacha20.init(&[0_u8; 32], &[0_u8; 12]); - /// + /// /// let data = b"some plaintext data"; // Data to be encrypted or decrypted /// let processed_data = chacha20.process(data); /// // `processed_data` now contains the encrypted or decrypted output diff --git a/enclave/Cargo.toml b/enclave/Cargo.toml index 9497996..50c0125 100644 --- a/enclave/Cargo.toml +++ b/enclave/Cargo.toml @@ -5,7 +5,7 @@ authors = ["mikesposito"] edition = "2021" license = "MIT" repository = "https://github.com/mikesposito/secured/" -description = "Part of secured: a lightweight, easy-to-use Rust package for file encryption and decryption, suitable for both CLI and library integration in Rust applications." +description = "Ergonomic library for secure encryption and decryption of data in Rust." [dependencies.secured-cipher] version = "0.3.0" diff --git a/enclave/LICENSE b/enclave/LICENSE new file mode 100644 index 0000000..b0affab --- /dev/null +++ b/enclave/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Michele Esposito + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/enclave/README.md b/enclave/README.md new file mode 100644 index 0000000..75ed5b1 --- /dev/null +++ b/enclave/README.md @@ -0,0 +1,114 @@ +# secured-enclave + +## Overview + +`secured-enclave` is a Rust crate designed for secure and dev-ergonomic encryption and decryption of data. It provides a robust way to encrypt data using various cipher algorithms, key derivation strategies, and support for metadata. + +## Features + +- **Secure Encryption and Decryption**: Uses ChaCha20Poly1305 for encryption and authentication, ensuring strong security. +- **Metadata Support**: Allows associating metadata with encrypted data. +- **Flexible Key Management**: Supports different key derivation strategies for enhanced security. +- **Serialization and Deserialization**: Easily serialize and deserialize encrypted data for storage or transmission. + +## Installation + +Add the following line to your `Cargo.toml` file: + +```toml +[dependencies] +enclave = "0.5.0" +``` + +## Usage + +### Basic Encryption and Decryption + +#### Encrypting Data + +```rust +use secured_enclave::{Enclave, Encryptable, KeyDerivationStrategy}; + +let password = "strong_password"; +let encrypted_string = "Hello, world!".encrypt(password.to_string(), KeyDerivationStrategy::default()); +``` + +#### Decrypting Data + +```rust +use secured_enclave::{Decryptable, EnclaveError}; + +let password = "strong_password"; +let decrypted_result = encrypted_data.decrypt(password.to_string()); + +println!("Decrypted data: {:?}", String::from_utf8(decrypted_data).unwrap()) +``` + +### Advanced Usage + +#### Encrypting with Custom Strategies + +It is possible to have more control as well when needed: + +```rust +use secured_enclave::{Enclave, KeyDerivationStrategy}; + +fn main() { + // Key generation (32bytes for the key, 16 bytes for salt) + let key = Key::<32, 16>::new(b"my password", KeyDerivationStrategy::PBKDF2(900_000)); // 900K iterations + + // Leave some readable metadata (but signed!) + let metadata = b"some metadata".to_vec(); + + // Using Enclave for data encapsulation (&str metadata, 8-byte nonce) + let enclave = + Enclave::from_plain_bytes(metadata, key.pubk, b"Some bytes to encrypt".to_vec()) + .unwrap(); + + // Get encrypted bytes (ciphertext) + println!("Encrypted bytes: {:?}", enclave.encrypted_bytes); + + // Serialize everything to bytes + let bytes: Vec = enclave.into(); + + // Decrypt Enclave + let decrypted_bytes = enclave.decrypt(key.pubk).unwrap(); + + assert_eq!(decrypted_bytes, b"Some bytes to encrypt"); +} +``` + +#### Decrypting with Metadata + +```rust +use enclave::{Decryptable, EnclaveError}; + +let decryption_result = encrypted_data_with_metadata.decrypt_with_metadata::>(key); +match decryption_result { + Ok((decrypted_data, metadata)) => { + println!("Decrypted data: {:?}", String::from_utf8(decrypted_data).unwrap()); + println!("Metadata: {:?}", String::from_utf8(metadata).unwrap()); + }, + Err(e) => println!("Error during decryption: {:?}", e), +} +``` + +## Testing + +The crate includes a comprehensive set of unit tests. Run the tests with the following command: + +```shell +cargo test +``` + +## Contributing + +Contributions are welcome! Please read our contributing guidelines before submitting pull requests. + +## License + +This project is licensed under the [MIT license](LICENSE). + +--- + +This README provides a basic guide to getting started with the Enclave crate. For more detailed documentation, please refer to the [API documentation](https://docs.rs/secured-enclave/). diff --git a/enclave/src/errors.rs b/enclave/src/errors.rs index ead8d33..4c64250 100644 --- a/enclave/src/errors.rs +++ b/enclave/src/errors.rs @@ -1,9 +1,12 @@ use std::fmt::{Display, Formatter, Result}; +use secured_cipher::CipherError; + #[derive(Debug)] pub enum EnclaveError { Serialization(String), Deserialization(String), + Generic(String), } impl Display for EnclaveError { @@ -13,6 +16,23 @@ impl Display for EnclaveError { EnclaveError::Deserialization(message) => { write!(f, "Unable to deserialize safe > {}", message) } + EnclaveError::Generic(message) => write!(f, "Enclave error > {}", message), } } } + +impl From for EnclaveError { + fn from(error: CipherError) -> Self { + match error { + CipherError::AuthenticationFailed => { + EnclaveError::Deserialization("authentication failed".to_string()) + } + } + } +} + +impl From for EnclaveError { + fn from(error: String) -> Self { + EnclaveError::Generic(error) + } +} diff --git a/enclave/src/lib.rs b/enclave/src/lib.rs index 2288434..bb6afdb 100644 --- a/enclave/src/lib.rs +++ b/enclave/src/lib.rs @@ -1,8 +1,12 @@ pub mod errors; +pub mod traits; pub use errors::EnclaveError; -use secured_cipher::{ - permutation::chacha20::CHACHA20_NONCE_SIZE, random_bytes, Cipher, SignedEnvelope, +pub use traits::{Decryptable, Encryptable}; + +pub use secured_cipher::{ + permutation::chacha20::CHACHA20_NONCE_SIZE, random_bytes, Cipher, Key, KeyDerivationStrategy, + SignedEnvelope, }; const KEY_SIZE: usize = 32; @@ -68,14 +72,13 @@ where /// /// # Returns /// A `Result` containing the decrypted data as a vector of bytes, or an error string if decryption fails. - pub fn decrypt(&self, key: [u8; KEY_SIZE]) -> Result, String> { + pub fn decrypt(&self, key: [u8; KEY_SIZE]) -> Result, EnclaveError> { let envelope = SignedEnvelope::from(self.encrypted_bytes.to_vec()); Ok( Cipher::default() .init(&key, &self.nonce) - .decrypt_and_verify(&envelope) - .or(Err("decryption failed".to_string()))?, + .decrypt_and_verify(&envelope)?, ) } } @@ -153,20 +156,274 @@ where } } +impl Encryptable for Vec { + /// Encrypts a vector of bytes using a provided password. + /// + /// # Arguments + /// * `password`: The password to use for key derivation. + /// * `strategy`: The key derivation strategy to use. + /// + /// # Returns + /// A `Vec` containing the encrypted data. + fn encrypt(&self, password: String, strategy: KeyDerivationStrategy) -> Vec { + let key: Key<32, 16> = Key::new(password.as_bytes(), strategy.clone()); + let enclave = Enclave::from_plain_bytes(vec![], key.pubk, self.clone()).unwrap(); + + [enclave.into(), key.salt.to_vec(), strategy.into()].concat() + } + + /// Encrypts a vector of bytes using a provided key. + /// + /// # Arguments + /// * `key`: The 32-byte cipher key used for encryption. + /// + /// # Returns + /// A `Vec` containing the encrypted data. + fn encrypt_with_key(&self, key: [u8; KEY_SIZE]) -> Vec { + let enclave = Enclave::from_plain_bytes(vec![], key, self.clone()).unwrap(); + enclave.into() + } + + /// Encrypts a vector of bytes using a provided key and metadata. + /// + /// # Arguments + /// * `key`: The 32-byte cipher key used for encryption. + /// * `metadata`: The metadata to be associated with the encrypted data. + /// + /// # Returns + /// A `Vec` containing the encrypted data. + fn encrypt_with_metadata(&self, key: [u8; KEY_SIZE], metadata: M) -> Vec + where + M: From> + Into> + Clone, + { + let enclave = Enclave::from_plain_bytes(metadata, key, self.clone()).unwrap(); + enclave.into() + } +} + +impl Decryptable for Vec { + /// Decrypts a slice of bytes using a provided password. + /// + /// # Arguments + /// * `password`: The password to use for decryption. + /// + /// # Returns + /// A `Result` containing the decrypted data as a vector of bytes, or an error string if decryption fails. + fn decrypt(&self, password: String) -> Result, EnclaveError> { + let strategy = KeyDerivationStrategy::try_from(self[self.len() - 9..self.len()].to_vec())?; + let salt: [u8; 16] = self[self.len() - 25..self.len() - 9].try_into().unwrap(); + let key = Key::::with_salt(password.as_bytes(), salt, strategy); + + let enclave = Enclave::>::try_from(self[..self.len() - 25].to_vec())?; + + enclave.decrypt(key.pubk) + } + + /// Decrypts a slice of bytes using a provided key. + /// + /// # Arguments + /// * `key`: The 32-byte cipher key used for decryption. + /// + /// # Returns + /// A `Result` containing the decrypted data as a vector of bytes, or an error string if decryption fails. + fn decrypt_with_key(&self, key: [u8; KEY_SIZE]) -> Result, EnclaveError> { + let enclave = Enclave::>::try_from(self.clone())?; + enclave.decrypt(key) + } + + /// Decrypts a slice of bytes using a provided key. + /// + /// # Arguments + /// * `key`: The 32-byte cipher key used for decryption. + /// + /// # Returns + /// A `Result` containing the decrypted data as a vector of bytes, or an error string if decryption fails. + fn decrypt_with_metadata(&self, key: [u8; KEY_SIZE]) -> Result<(Vec, M), EnclaveError> + where + M: TryFrom> + Into> + Clone, + { + let enclave = Enclave::::try_from(self.clone())?; + let decrypted_bytes = enclave.decrypt(key)?; + Ok((decrypted_bytes, enclave.metadata)) + } +} + +impl Decryptable for &[u8] { + /// Decrypts a slice of bytes using a provided password. + /// + /// # Arguments + /// * `password`: The password to use for decryption. + /// + /// # Returns + /// A `Result` containing the decrypted data as a vector of bytes, or an error string if decryption fails. + fn decrypt(&self, password: String) -> Result, EnclaveError> { + self.to_vec().decrypt(password) + } + + /// Decrypts a slice of bytes using a provided key. + /// + /// # Arguments + /// * `key`: The 32-byte cipher key used for decryption. + /// + /// # Returns + /// A `Result` containing the decrypted data as a vector of bytes, or an error string if decryption fails. + fn decrypt_with_key(&self, key: [u8; KEY_SIZE]) -> Result, EnclaveError> { + self.to_vec().decrypt_with_key(key) + } + + /// Decrypts a slice of bytes using a provided key. + /// + /// # Arguments + /// * `key`: The 32-byte cipher key used for decryption. + /// + /// # Returns + /// A `Result` containing the decrypted data as a vector of bytes, or an error string if decryption fails. + fn decrypt_with_metadata(&self, key: [u8; KEY_SIZE]) -> Result<(Vec, M), EnclaveError> + where + M: TryFrom> + Into> + Clone, + { + self.to_vec().decrypt_with_metadata(key) + } +} + +impl Encryptable for &[u8] { + /// Encrypts a slice of bytes using a provided password. + /// + /// # Arguments + /// * `password`: The password to use for key derivation. + /// * `strategy`: The key derivation strategy to use. + /// + /// # Returns + /// A `Vec` containing the encrypted data. + fn encrypt(&self, password: String, strategy: KeyDerivationStrategy) -> Vec { + self.to_vec().encrypt(password, strategy) + } + + /// Encrypts a slice of bytes using a provided key. + /// + /// # Arguments + /// * `key`: The 32-byte cipher key used for encryption. + /// + /// # Returns + /// A `Vec` containing the encrypted data. + fn encrypt_with_key(&self, key: [u8; KEY_SIZE]) -> Vec { + self.to_vec().encrypt_with_key(key) + } + + /// Encrypts a slice of bytes using a provided key and metadata. + /// + /// # Arguments + /// * `key`: The 32-byte cipher key used for encryption. + /// * `metadata`: The metadata to be associated with the encrypted data. + /// + /// # Returns + /// A `Vec` containing the encrypted data. + fn encrypt_with_metadata(&self, key: [u8; KEY_SIZE], metadata: M) -> Vec + where + M: From> + Into> + Clone, + { + self.to_vec().encrypt_with_metadata(key, metadata) + } +} + +impl Encryptable for String { + /// Encrypts a string using a provided password. + /// + /// # Arguments + /// * `key`: The 32-byte cipher key used for encryption. + /// * `strategy`: The key derivation strategy to use. + /// + /// # Returns + /// A `Vec` containing the encrypted data. + fn encrypt(&self, password: String, strategy: KeyDerivationStrategy) -> Vec { + self.as_bytes().to_vec().encrypt(password, strategy) + } + + /// Encrypts a String using a provided key. + /// + /// # Arguments + /// * `key`: The 32-byte cipher key used for encryption. + /// + /// # Returns + /// A `Vec` containing the encrypted data. + fn encrypt_with_key(&self, key: [u8; KEY_SIZE]) -> Vec { + self.as_bytes().to_vec().encrypt_with_key(key) + } + + /// Encrypts a String using a provided key and metadata. + /// + /// # Arguments + /// * `key`: The 32-byte cipher key used for encryption. + /// * `metadata`: The metadata to be associated with the encrypted data. + /// + /// # Returns + /// A `Vec` containing the encrypted data. + fn encrypt_with_metadata(&self, key: [u8; KEY_SIZE], metadata: M) -> Vec + where + M: From> + Into> + Clone, + { + self + .as_bytes() + .to_vec() + .encrypt_with_metadata(key, metadata) + } +} + +impl Encryptable for &str { + /// Encrypts a &str using a provided password. + /// + /// # Arguments + /// * `password`: The password to use for key derivation. + /// + /// # Returns + /// A `Vec` containing the encrypted data. + fn encrypt(&self, password: String, strategy: KeyDerivationStrategy) -> Vec { + self.as_bytes().to_vec().encrypt(password, strategy) + } + + /// Encrypts a string using a provided key. + /// + /// # Arguments + /// * `key`: The 32-byte cipher key used for encryption. + /// + /// # Returns + /// A `Vec` containing the encrypted data. + fn encrypt_with_key(&self, key: [u8; KEY_SIZE]) -> Vec { + self.as_bytes().to_vec().encrypt_with_key(key) + } + + /// Encrypts an &str using a provided key and metadata. + /// + /// # Arguments + /// * `key`: The 32-byte cipher key used for encryption. + /// * `metadata`: The metadata to be associated with the encrypted data. + /// + /// # Returns + /// A `Vec` containing the encrypted data. + fn encrypt_with_metadata(&self, key: [u8; KEY_SIZE], metadata: M) -> Vec + where + M: From> + Into> + Clone, + { + self + .as_bytes() + .to_vec() + .encrypt_with_metadata(key, metadata) + } +} + #[cfg(test)] mod tests { use super::*; - use secured_cipher::Key; mod from_plain_bytes { use super::*; #[test] fn it_should_create_enclave() { - let key: Key<32, 16> = Key::new(b"my password", 10_000); + let key = [0u8; KEY_SIZE]; let bytes = [0u8, 1u8, 2u8, 3u8, 4u8].to_vec(); - let safe = Enclave::from_plain_bytes(b"metadata".to_owned(), key.pubk, bytes); + let safe = Enclave::from_plain_bytes(b"metadata".to_owned(), key, bytes); assert!(safe.is_ok()); assert_eq!(safe.unwrap().metadata, b"metadata".to_owned()); @@ -178,11 +435,11 @@ mod tests { #[test] fn it_should_decrypt_enclave() { - let key: Key<32, 16> = Key::new(b"my password", 10_000); + let key = [0u8; KEY_SIZE]; let bytes = [0u8, 1u8, 2u8, 3u8, 4u8].to_vec(); - let safe = Enclave::from_plain_bytes(b"metadata".to_vec(), key.pubk, bytes.clone()).unwrap(); + let safe = Enclave::from_plain_bytes(b"metadata".to_vec(), key, bytes.clone()).unwrap(); - let decrypted_bytes = safe.decrypt(key.pubk); + let decrypted_bytes = safe.decrypt(key); assert!(decrypted_bytes.is_ok()); assert_eq!(decrypted_bytes.unwrap(), bytes); @@ -190,26 +447,76 @@ mod tests { #[test] fn it_should_fail_with_wrong_key() { - let key: Key<32, 16> = Key::new(b"my password", 10_000); + let key = [0u8; KEY_SIZE]; let bytes = [0u8, 1u8, 2u8, 3u8, 4u8].to_vec(); - let safe = Enclave::from_plain_bytes(b"metadata".to_vec(), key.pubk, bytes.clone()).unwrap(); - let wrong_key: Key<32, 16> = Key::new(b"my wrong password", 10_000); + let safe = Enclave::from_plain_bytes(b"metadata".to_vec(), key, bytes.clone()).unwrap(); + let wrong_key = [1u8; KEY_SIZE]; - let decrypted_bytes = safe.decrypt(wrong_key.pubk); + let decrypted_bytes = safe.decrypt(wrong_key); assert!(!decrypted_bytes.is_ok()); } #[test] fn it_should_serialize_and_deserialize_to_bytes() { - let key: Key<32, 16> = Key::new(b"my password", 10_000); + let key = [0u8; KEY_SIZE]; let bytes = [0u8, 1u8, 2u8, 3u8, 4u8].to_vec(); - let enclave = Enclave::from_plain_bytes([0_u8, 1_u8], key.pubk, bytes.clone()).unwrap(); + let enclave = Enclave::from_plain_bytes([0_u8, 1_u8], key, bytes.clone()).unwrap(); let serialized: Vec = enclave.clone().into(); let deserialized = Enclave::try_from(serialized).unwrap(); assert_eq!(enclave, deserialized); } + + #[test] + fn vec_u8_should_be_encryptable_and_decryptable_with_password() { + let bytes = [0u8, 1u8, 2u8, 3u8, 4u8].to_vec(); + let password = "my password".to_string(); + + // Using a low number of iterations here because tests are slow + let encrypted_bytes = bytes.encrypt(password.clone(), KeyDerivationStrategy::PBKDF2(10_000)); + let decrypted_bytes = encrypted_bytes.decrypt(password); + + assert!(decrypted_bytes.is_ok()); + assert_eq!(decrypted_bytes.unwrap(), bytes); + } + + #[test] + fn vec_u8_should_be_encryptable_and_decryptable_with_key() { + let bytes = [0u8, 1u8, 2u8, 3u8, 4u8].to_vec(); + let key = [0u8; KEY_SIZE]; + + let encrypted_bytes = bytes.encrypt_with_key(key); + let decrypted_bytes = encrypted_bytes.decrypt_with_key(key); + + assert!(decrypted_bytes.is_ok()); + assert_eq!(decrypted_bytes.unwrap(), bytes); + } + + #[test] + fn vec_u8_should_be_encryptable_and_decryptable_with_metadata() { + let bytes = [0u8, 1u8, 2u8, 3u8, 4u8].to_vec(); + let key = [0u8; KEY_SIZE]; + + let encrypted_bytes = bytes.encrypt_with_metadata(key, b"metadata".to_vec()); + let decrypted_bytes = encrypted_bytes.decrypt_with_metadata::>(key); + + assert!(decrypted_bytes.is_ok()); + assert_eq!(decrypted_bytes.unwrap(), (bytes, b"metadata".to_vec())); + } + + #[test] + fn strings_should_be_encryptable_and_decryptable_with_password() { + let string = "my string".to_string(); + let password = "my password".to_string(); + + // Using a low number of iterations here because tests are slow + let encrypted_bytes = string.encrypt(password.clone(), KeyDerivationStrategy::PBKDF2(10_000)); + let decrypted_bytes = encrypted_bytes.decrypt(password); + + assert!(decrypted_bytes.is_ok()); + assert_eq!(decrypted_bytes.unwrap(), string.as_bytes()); + } } } diff --git a/enclave/src/traits.rs b/enclave/src/traits.rs new file mode 100644 index 0000000..5876ee7 --- /dev/null +++ b/enclave/src/traits.rs @@ -0,0 +1,43 @@ +use secured_cipher::KeyDerivationStrategy; + +use crate::EnclaveError; + +/// The `Encryptable` trait provides a common interface for encryption operations. +/// It is implemented by types that can be encrypted. +/// +/// # Type Parameters +/// * `KEY_SIZE` - The size of the key to be used for encryption. +/// +/// # Methods +/// * `encrypt` - Encrypts the type using a specified password and key derivation strategy. +/// * `encrypt_with_key` - Encrypts the type using a specified key. +/// * `encrypt_with_metadata` - Encrypts the type using a specified key and metadata. +pub trait Encryptable { + fn encrypt(&self, password: String, strategy: KeyDerivationStrategy) -> Vec; + + fn encrypt_with_key(&self, key: [u8; KEY_SIZE]) -> Vec; + + fn encrypt_with_metadata(&self, key: [u8; KEY_SIZE], metadata: T) -> Vec + where + T: From> + Into> + Clone; +} + +/// The `Decryptable` trait provides a common interface for decryption operations. +/// It is implemented by types that can be decrypted. +/// +/// # Type Parameters +/// * `KEY_SIZE` - The size of the key to be used for decryption. +/// +/// # Methods +/// * `decrypt` - Decrypts the type using a specified password. +/// * `decrypt_with_key` - Decrypts the type using a specified key. +/// * `decrypt_with_metadata` - Decrypts the type using a specified key and metadata. +pub trait Decryptable { + fn decrypt(&self, password: String) -> Result, EnclaveError>; + + fn decrypt_with_key(&self, key: [u8; KEY_SIZE]) -> Result, EnclaveError>; + + fn decrypt_with_metadata(&self, key: [u8; KEY_SIZE]) -> Result<(Vec, T), EnclaveError> + where + T: TryFrom> + Into> + Clone; +} diff --git a/src/lib.rs b/src/lib.rs index 6f29e4c..a626918 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,19 +31,17 @@ pub use cipher; /// Here's a quick example to illustrate basic usage of the `secured` library: /// /// ```rust -/// use secured::{enclave::Enclave, cipher::Key}; +/// use secured::enclave::{Encryptable, Decryptable, KeyDerivationStrategy}; /// /// fn main() { -/// // Key generation: Create a new cryptographic key using a password and iteration count. -/// // Note: In a production environment, ensure to use a higher iteration count for added security. -/// let key: Key<32, 16> = Key::new(b"my password", 1_000); -/// -/// // Encrypt data: Utilize the Enclave to securely encrypt data along with metadata. -/// let metadata = b"some metadata".to_vec(); -/// let enclave = Enclave::from_plain_bytes(metadata, key.pubk, b"some bytes to encrypt".to_vec()).unwrap(); +/// // Encrypt whatever: strings, bytes, etc. +/// let password = "my password".to_string(); +/// let encrypted_bytes = "Hello, world!".encrypt(password, KeyDerivationStrategy::default()); /// -/// // Decrypt data: Recover the original bytes from the encrypted enclave. -/// let recovered_bytes = enclave.decrypt(key.pubk); +/// // Decrypt data: Recover the original bytes from the encrypted bytes. +/// let recovered_data = encrypted_bytes.decrypt("my password".to_string()); +/// +/// assert_eq!(recovered_data.unwrap(), "Hello, world!".as_bytes().to_vec()); /// } /// ``` /// diff --git a/src/main.rs b/src/main.rs index 94c99bb..25aaef7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,8 +3,8 @@ use rpassword::prompt_password; use std::fs::{metadata, File}; use std::io::{Read, Write}; -use cipher::Key; -use enclave::Enclave; +use cipher::KeyDerivationStrategy; +use enclave::{Decryptable, Encryptable}; /// Defines command line subcommands for the application. #[derive(Debug, Subcommand)] @@ -56,14 +56,8 @@ fn main() { /// * `password` - The password used for encryption. /// * `filename` - The name of the file to be encrypted. fn encrypt_file(password: &String, filename: &String) { - let encryption_key: Key<32, 16> = Key::new(password.as_bytes(), 900_000); - let enclave = Enclave::from_plain_bytes( - encryption_key.salt, - encryption_key.pubk, - get_file_as_byte_vec(filename), - ) - .unwrap(); - let encrypted_bytes: Vec = enclave.into(); + let encrypted_bytes = + get_file_as_byte_vec(filename).encrypt(password.clone(), KeyDerivationStrategy::default()); File::create(format!("{}.secured", filename)) .expect("Unable to create file") @@ -79,11 +73,8 @@ fn encrypt_file(password: &String, filename: &String) { /// * `password` - The password used for decryption. /// * `filename` - The name of the file to be decrypted. fn decrypt_file(password: &String, filename: &String) { - let encrypted_bytes = get_file_as_byte_vec(filename); - let enclave = Enclave::try_from(encrypted_bytes).expect("Unable to deserialize enclave"); - let encryption_key: Key<32, 16> = Key::with_salt(password.as_bytes(), enclave.metadata, 900_000); - let recovered_bytes = enclave - .decrypt(encryption_key.pubk) + let recovered_bytes = get_file_as_byte_vec(filename) + .decrypt(password.clone()) .expect("Wrong password or corrupted enclave"); File::create(filename.replace(".secured", ""))