Skip to main content

ever_crypto/
xchacha.rs

1//! XChaCha20Poly1305 simple API wrapper
2//! 
3//! Provides easy-to-use functions for authenticated encryption with XChaCha20Poly1305
4
5use chacha20poly1305::{
6    aead::{Aead, AeadCore, KeyInit, OsRng},
7    XChaCha20Poly1305, XNonce as InternalXNonce,
8};
9use zeroize::Zeroize;
10use crate::{CryptoError, Result};
11
12/// XChaCha20Poly1305 key (256 bits / 32 bytes)
13#[derive(Clone, Zeroize)]
14#[zeroize(drop)]
15pub struct XChaChaKey([u8; 32]);
16
17impl XChaChaKey {
18    /// Generate a new random key
19    pub fn generate() -> Self {
20        let key = XChaCha20Poly1305::generate_key(&mut OsRng);
21        Self(key.into())
22    }
23    
24    /// Create a key from raw bytes
25    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
26        if bytes.len() != 32 {
27            return Err(CryptoError::InvalidKeyLength {
28                expected: 32,
29                actual: bytes.len(),
30            });
31        }
32        let mut key = [0u8; 32];
33        key.copy_from_slice(bytes);
34        Ok(Self(key))
35    }
36    
37    /// Get the raw key bytes
38    pub fn as_bytes(&self) -> &[u8] {
39        &self.0
40    }
41}
42
43/// XChaCha20Poly1305 nonce (192 bits / 24 bytes)
44#[derive(Clone, Copy)]
45pub struct XChaChaNonce([u8; 24]);
46
47impl XChaChaNonce {
48    /// Generate a new random nonce
49    pub fn generate() -> Self {
50        let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
51        Self(nonce.into())
52    }
53    
54    /// Create a nonce from raw bytes
55    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
56        if bytes.len() != 24 {
57            return Err(CryptoError::InvalidNonceLength {
58                expected: 24,
59                actual: bytes.len(),
60            });
61        }
62        let mut nonce = [0u8; 24];
63        nonce.copy_from_slice(bytes);
64        Ok(Self(nonce))
65    }
66    
67    /// Get the raw nonce bytes
68    pub fn as_bytes(&self) -> &[u8] {
69        &self.0
70    }
71}
72
73/// Main XChaCha20Poly1305 cryptographic operations
74pub struct XChaChaCrypto;
75
76impl XChaChaCrypto {
77    /// Encrypt a message with XChaCha20Poly1305
78    /// 
79    /// # Arguments
80    /// * `key` - The encryption key
81    /// * `nonce` - A unique nonce for this message
82    /// * `plaintext` - The message to encrypt
83    /// * `associated_data` - Optional additional authenticated data (AAD)
84    /// 
85    /// # Returns
86    /// The ciphertext with appended authentication tag
87    pub fn encrypt(
88        key: &XChaChaKey,
89        nonce: &XChaChaNonce,
90        plaintext: &[u8],
91        associated_data: Option<&[u8]>,
92    ) -> Result<Vec<u8>> {
93        let cipher = XChaCha20Poly1305::new(key.as_bytes().into());
94        let nonce = InternalXNonce::from_slice(nonce.as_bytes());
95        let aad = associated_data.unwrap_or(&[]);
96        
97        cipher
98            .encrypt(nonce, aead::Payload { msg: plaintext, aad })
99            .map_err(|e| CryptoError::EncryptionError(e.to_string()))
100    }
101    
102    /// Decrypt a message with XChaCha20Poly1305
103    /// 
104    /// # Arguments
105    /// * `key` - The decryption key
106    /// * `nonce` - The nonce used for encryption
107    /// * `ciphertext` - The ciphertext with authentication tag
108    /// * `associated_data` - Optional additional authenticated data (AAD)
109    /// 
110    /// # Returns
111    /// The decrypted plaintext
112    pub fn decrypt(
113        key: &XChaChaKey,
114        nonce: &XChaChaNonce,
115        ciphertext: &[u8],
116        associated_data: Option<&[u8]>,
117    ) -> Result<Vec<u8>> {
118        let cipher = XChaCha20Poly1305::new(key.as_bytes().into());
119        let nonce = InternalXNonce::from_slice(nonce.as_bytes());
120        let aad = associated_data.unwrap_or(&[]);
121        
122        cipher
123            .decrypt(nonce, aead::Payload { msg: ciphertext, aad })
124            .map_err(|e| CryptoError::DecryptionError(e.to_string()))
125    }
126    
127    /// Encrypt with a randomly generated nonce
128    /// 
129    /// # Arguments
130    /// * `key` - The encryption key
131    /// * `plaintext` - The message to encrypt
132    /// * `associated_data` - Optional additional authenticated data
133    /// 
134    /// # Returns
135    /// A tuple of (nonce, ciphertext)
136    pub fn encrypt_with_random_nonce(
137        key: &XChaChaKey,
138        plaintext: &[u8],
139        associated_data: Option<&[u8]>,
140    ) -> Result<(XChaChaNonce, Vec<u8>)> {
141        let nonce = XChaChaNonce::generate();
142        let ciphertext = Self::encrypt(key, &nonce, plaintext, associated_data)?;
143        Ok((nonce, ciphertext))
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150    
151    #[test]
152    fn test_key_generation() {
153        let key = XChaChaKey::generate();
154        assert_eq!(key.as_bytes().len(), 32);
155    }
156    
157    #[test]
158    fn test_nonce_generation() {
159        let nonce = XChaChaNonce::generate();
160        assert_eq!(nonce.as_bytes().len(), 24);
161    }
162    
163    #[test]
164    fn test_encrypt_decrypt() {
165        let key = XChaChaKey::generate();
166        let plaintext = b"Hello, World!";
167        let aad = b"Additional data";
168        
169        // Test with generated nonce
170        let (nonce, ciphertext) = XChaChaCrypto::encrypt_with_random_nonce(
171            &key,
172            plaintext,
173            Some(aad),
174        ).unwrap();
175        
176        let decrypted = XChaChaCrypto::decrypt(
177            &key,
178            &nonce,
179            &ciphertext,
180            Some(aad),
181        ).unwrap();
182        
183        assert_eq!(plaintext, &decrypted[..]);
184    }
185    
186    #[test]
187    fn test_encrypt_decrypt_no_aad() {
188        let key = XChaChaKey::generate();
189        let nonce = XChaChaNonce::generate();
190        let plaintext = b"Secret message";
191        
192        let ciphertext = XChaChaCrypto::encrypt(&key, &nonce, plaintext, None).unwrap();
193        let decrypted = XChaChaCrypto::decrypt(&key, &nonce, &ciphertext, None).unwrap();
194        
195        assert_eq!(plaintext, &decrypted[..]);
196    }
197    
198    #[test]
199    fn test_wrong_key_fails() {
200        let key1 = XChaChaKey::generate();
201        let key2 = XChaChaKey::generate();
202        let nonce = XChaChaNonce::generate();
203        let plaintext = b"Secret";
204        
205        let ciphertext = XChaChaCrypto::encrypt(&key1, &nonce, plaintext, None).unwrap();
206        let result = XChaChaCrypto::decrypt(&key2, &nonce, &ciphertext, None);
207        
208        assert!(result.is_err());
209    }
210}