Skip to main content

rskit_encryption/
lib.rs

1#![warn(missing_docs)]
2//! Symmetric encryption utilities with AES-256-GCM and ChaCha20-Poly1305 support.
3//!
4//! This crate provides a unified interface for symmetric encryption and decryption
5//! of sensitive data using either AES-256-GCM (default, with hardware acceleration)
6//! or ChaCha20-Poly1305 (modern, performant without AES-NI).
7//!
8//! Keys are derived from passphrases using PBKDF2-SHA256 with 600,000 iterations
9//! and a random 16-byte salt per encryption operation.
10//!
11//! Ciphertext format:
12//! `base64(version[1] || algorithm[1] || salt[16] || nonce[12] || ciphertext)`.
13//!
14//! # Examples
15//!
16//! ```no_run
17//! use rskit_encryption::{Encryptor, Algorithm};
18//! use rskit_encryption::new_encryptor;
19//!
20//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
21//! let encryptor = new_encryptor(b"my-secret-key", Algorithm::AesGcm);
22//!
23//! let plaintext = b"sensitive data";
24//! let ciphertext = encryptor.encrypt(plaintext)?;
25//! let decrypted = encryptor.decrypt(&ciphertext)?;
26//!
27//! assert_eq!(decrypted, plaintext);
28//! # Ok(())
29//! # }
30//! ```
31
32pub mod aes_gcm;
33pub mod chacha20;
34pub mod traits;
35
36mod envelope;
37
38pub use aes_gcm::AesGcmEncryptor;
39pub use chacha20::ChaCha20Encryptor;
40pub use traits::{Algorithm, Encryptor};
41
42/// Creates a new encryptor for the specified algorithm.
43///
44/// # Arguments
45///
46/// * `key` - The encryption passphrase (used with PBKDF2-SHA256 to derive keys)
47/// * `algorithm` - The encryption algorithm to use
48///
49/// # Returns
50///
51/// A boxed trait object implementing [`Encryptor`].
52///
53/// # Examples
54///
55/// ```no_run
56/// use rskit_encryption::{new_encryptor, Algorithm};
57///
58/// let encryptor = new_encryptor(b"secret-key", Algorithm::ChaCha20Poly1305);
59/// ```
60pub fn new_encryptor(key: &[u8], algorithm: Algorithm) -> Box<dyn Encryptor> {
61    match algorithm {
62        Algorithm::AesGcm => Box::new(AesGcmEncryptor::new(key)),
63        Algorithm::ChaCha20Poly1305 => Box::new(ChaCha20Encryptor::new(key)),
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn test_factory_aes_gcm() {
73        let encryptor = new_encryptor(b"secret-key", Algorithm::AesGcm);
74        assert_eq!(encryptor.algorithm(), Algorithm::AesGcm);
75
76        let plaintext = b"test";
77        let ciphertext = encryptor.encrypt(plaintext).unwrap();
78        let decrypted = encryptor.decrypt(&ciphertext).unwrap();
79        assert_eq!(decrypted, plaintext);
80    }
81
82    #[test]
83    fn test_factory_chacha20() {
84        let encryptor = new_encryptor(b"secret-key", Algorithm::ChaCha20Poly1305);
85        assert_eq!(encryptor.algorithm(), Algorithm::ChaCha20Poly1305);
86
87        let plaintext = b"test";
88        let ciphertext = encryptor.encrypt(plaintext).unwrap();
89        let decrypted = encryptor.decrypt(&ciphertext).unwrap();
90        assert_eq!(decrypted, plaintext);
91    }
92
93    #[test]
94    fn malformed_versioned_envelopes_are_rejected() {
95        use base64::{Engine, engine::general_purpose::STANDARD};
96
97        let encryptor = new_encryptor(b"secret-key", Algorithm::AesGcm);
98        let too_short = STANDARD.encode([1_u8, 1, 2, 3]);
99        let err = encryptor.decrypt(&too_short).unwrap_err();
100        assert_eq!(err.code(), rskit_errors::ErrorCode::InvalidFormat);
101
102        let mut bad_version = vec![0_u8, 1];
103        bad_version.extend_from_slice(&[0_u8; 44]);
104        let err = encryptor
105            .decrypt(&STANDARD.encode(bad_version))
106            .unwrap_err();
107        assert_eq!(err.code(), rskit_errors::ErrorCode::InvalidFormat);
108
109        let mut bad_algorithm = vec![1_u8, 99];
110        bad_algorithm.extend_from_slice(&[0_u8; 44]);
111        let err = encryptor
112            .decrypt(&STANDARD.encode(bad_algorithm))
113            .unwrap_err();
114        assert_eq!(err.code(), rskit_errors::ErrorCode::InvalidFormat);
115    }
116}