dcrypt_symmetric/aead/chacha20poly1305/
cipher.rs

1//! ChaCha20Poly1305 authenticated encryption
2//!
3//! This module provides an implementation of the ChaCha20Poly1305 authenticated encryption
4//! algorithm as defined in RFC 8439.
5
6use super::common::{
7    ChaCha20Poly1305CiphertextPackage, ChaCha20Poly1305Key, ChaCha20Poly1305Nonce,
8};
9use crate::cipher::{Aead, SymmetricCipher};
10use crate::error::{from_primitive_error, validate, Result};
11use dcrypt_algorithms::aead::chacha20poly1305::ChaCha20Poly1305;
12use dcrypt_algorithms::aead::chacha20poly1305::CHACHA20POLY1305_TAG_SIZE;
13use dcrypt_algorithms::aead::xchacha20poly1305::XChaCha20Poly1305;
14use dcrypt_algorithms::error::Error as PrimitiveError;
15use dcrypt_algorithms::types::Nonce; // Import the generic Nonce type
16use rand::RngCore;
17use std::fmt;
18
19/// ChaCha20Poly1305 authenticated encryption
20pub struct ChaCha20Poly1305Cipher {
21    cipher: ChaCha20Poly1305,
22    pub(crate) key: ChaCha20Poly1305Key,
23}
24
25impl SymmetricCipher for ChaCha20Poly1305Cipher {
26    type Key = ChaCha20Poly1305Key;
27
28    fn new(key: &Self::Key) -> Result<Self> {
29        // Validate key length (though it should already be correct by type)
30        validate::length("ChaCha20Poly1305 key", key.as_bytes().len(), 32)?;
31
32        let cipher = ChaCha20Poly1305::new(key.as_bytes());
33        Ok(Self {
34            cipher,
35            key: key.clone(),
36        })
37    }
38
39    fn name() -> &'static str {
40        "ChaCha20Poly1305"
41    }
42}
43
44impl Aead for ChaCha20Poly1305Cipher {
45    type Nonce = ChaCha20Poly1305Nonce;
46
47    fn encrypt(
48        &self,
49        nonce: &Self::Nonce,
50        plaintext: &[u8],
51        aad: Option<&[u8]>,
52    ) -> Result<Vec<u8>> {
53        // Convert our nonce type to the new Nonce<12> type
54        let primitives_nonce = Nonce::<12>::from_slice(nonce.as_bytes())?;
55
56        // Use the converted nonce
57        self.cipher
58            .encrypt(&primitives_nonce, plaintext, aad)
59            .map_err(from_primitive_error)
60    }
61
62    fn decrypt(
63        &self,
64        nonce: &Self::Nonce,
65        ciphertext: &[u8],
66        aad: Option<&[u8]>,
67    ) -> Result<Vec<u8>> {
68        // Validate minimum ciphertext length (must include tag)
69        validate::min_length(
70            "ChaCha20Poly1305 ciphertext",
71            ciphertext.len(),
72            CHACHA20POLY1305_TAG_SIZE,
73        )?;
74
75        // Convert our nonce type to the new Nonce<12> type
76        let primitives_nonce = Nonce::<12>::from_slice(nonce.as_bytes())?;
77
78        // Decrypt with proper error transformation for authentication failures
79        self.cipher
80            .decrypt(&primitives_nonce, ciphertext, aad)
81            .map_err(|e| match e {
82                PrimitiveError::Authentication { .. } => {
83                    dcrypt_api::error::Error::AuthenticationFailed {
84                        context: "ChaCha20Poly1305",
85                        #[cfg(feature = "std")]
86                        message: "authentication tag verification failed".to_string(),
87                    }
88                }
89                _ => from_primitive_error(e),
90            })
91    }
92
93    fn generate_nonce() -> Self::Nonce {
94        ChaCha20Poly1305Nonce::generate()
95    }
96}
97
98impl ChaCha20Poly1305Cipher {
99    /// Generates a new ChaCha20Poly1305 instance with a random key
100    pub fn generate() -> Result<(Self, ChaCha20Poly1305Key)> {
101        let key = ChaCha20Poly1305Key::generate();
102        let cipher = Self::new(&key)?;
103        Ok((cipher, key))
104    }
105
106    /// Convenience method for encryption with a new random nonce
107    pub fn encrypt_with_random_nonce(
108        &self,
109        plaintext: &[u8],
110        aad: Option<&[u8]>,
111    ) -> Result<(Vec<u8>, ChaCha20Poly1305Nonce)> {
112        let nonce = Self::generate_nonce();
113        let ciphertext = self.encrypt(&nonce, plaintext, aad)?;
114        Ok((ciphertext, nonce))
115    }
116
117    /// Helper method to decrypt and verify all in one step
118    pub fn decrypt_and_verify(
119        &self,
120        ciphertext: &[u8],
121        nonce: &ChaCha20Poly1305Nonce,
122        aad: Option<&[u8]>,
123    ) -> Result<Vec<u8>> {
124        self.decrypt(nonce, ciphertext, aad)
125    }
126
127    /// Returns the key used by this instance
128    pub fn key(&self) -> &ChaCha20Poly1305Key {
129        &self.key
130    }
131
132    /// Encrypts data and returns a package containing both nonce and ciphertext
133    pub fn encrypt_to_package(
134        &self,
135        plaintext: &[u8],
136        aad: Option<&[u8]>,
137    ) -> Result<ChaCha20Poly1305CiphertextPackage> {
138        let (ciphertext, nonce) = self.encrypt_with_random_nonce(plaintext, aad)?;
139        Ok(ChaCha20Poly1305CiphertextPackage::new(nonce, ciphertext))
140    }
141
142    /// Decrypts a package containing both nonce and ciphertext
143    pub fn decrypt_package(
144        &self,
145        package: &ChaCha20Poly1305CiphertextPackage,
146        aad: Option<&[u8]>,
147    ) -> Result<Vec<u8>> {
148        self.decrypt(&package.nonce, &package.ciphertext, aad)
149    }
150}
151
152/// XChaCha20Poly1305 authenticated encryption with extended 24-byte nonce
153pub struct XChaCha20Poly1305Cipher {
154    cipher: XChaCha20Poly1305,
155}
156
157impl SymmetricCipher for XChaCha20Poly1305Cipher {
158    type Key = ChaCha20Poly1305Key;
159
160    fn new(key: &Self::Key) -> Result<Self> {
161        // Validate key length
162        validate::length("XChaCha20Poly1305 key", key.as_bytes().len(), 32)?;
163
164        let cipher = XChaCha20Poly1305::new(key.as_bytes());
165        Ok(Self { cipher })
166    }
167
168    fn name() -> &'static str {
169        "XChaCha20Poly1305"
170    }
171}
172
173/// Extended 24-byte nonce for XChaCha20Poly1305
174#[derive(Clone, Debug)]
175pub struct XChaCha20Poly1305Nonce([u8; 24]);
176
177impl XChaCha20Poly1305Nonce {
178    /// Creates a new nonce from raw bytes
179    pub fn new(bytes: [u8; 24]) -> Self {
180        Self(bytes)
181    }
182
183    /// Creates a new random nonce
184    pub fn generate() -> Self {
185        let mut nonce = [0u8; 24];
186        rand::rngs::OsRng.fill_bytes(&mut nonce);
187        Self(nonce)
188    }
189
190    /// Returns a reference to the raw nonce bytes
191    pub fn as_bytes(&self) -> &[u8; 24] {
192        &self.0
193    }
194
195    /// Creates a nonce from a base64 string
196    pub fn from_string(s: &str) -> Result<Self> {
197        let bytes =
198            base64::decode(s).map_err(|_| dcrypt_api::error::Error::SerializationError {
199                context: "XChaCha20Poly1305 nonce base64 decode",
200                #[cfg(feature = "std")]
201                message: "invalid base64 encoding".to_string(),
202            })?;
203
204        validate::length("XChaCha20Poly1305 nonce", bytes.len(), 24)?;
205
206        let mut nonce = [0u8; 24];
207        nonce.copy_from_slice(&bytes);
208
209        Ok(Self(nonce))
210    }
211}
212
213impl fmt::Display for XChaCha20Poly1305Nonce {
214    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215        write!(f, "{}", base64::encode(self.0))
216    }
217}
218
219impl Aead for XChaCha20Poly1305Cipher {
220    type Nonce = XChaCha20Poly1305Nonce;
221
222    fn encrypt(
223        &self,
224        nonce: &Self::Nonce,
225        plaintext: &[u8],
226        aad: Option<&[u8]>,
227    ) -> Result<Vec<u8>> {
228        // Convert our nonce type to the new Nonce<24> type
229        let primitives_nonce = Nonce::<24>::from_slice(nonce.as_bytes())?;
230
231        // Use the converted nonce
232        self.cipher
233            .encrypt(&primitives_nonce, plaintext, aad)
234            .map_err(from_primitive_error)
235    }
236
237    fn decrypt(
238        &self,
239        nonce: &Self::Nonce,
240        ciphertext: &[u8],
241        aad: Option<&[u8]>,
242    ) -> Result<Vec<u8>> {
243        // Validate minimum ciphertext length
244        validate::min_length(
245            "XChaCha20Poly1305 ciphertext",
246            ciphertext.len(),
247            CHACHA20POLY1305_TAG_SIZE,
248        )?;
249
250        // Convert our nonce type to the new Nonce<24> type
251        let primitives_nonce = Nonce::<24>::from_slice(nonce.as_bytes())?;
252
253        // Decrypt with proper error transformation
254        self.cipher
255            .decrypt(&primitives_nonce, ciphertext, aad)
256            .map_err(|e| match e {
257                PrimitiveError::Authentication { .. } => {
258                    dcrypt_api::error::Error::AuthenticationFailed {
259                        context: "XChaCha20Poly1305",
260                        #[cfg(feature = "std")]
261                        message: "authentication tag verification failed".to_string(),
262                    }
263                }
264                _ => from_primitive_error(e),
265            })
266    }
267
268    fn generate_nonce() -> Self::Nonce {
269        XChaCha20Poly1305Nonce::generate()
270    }
271}
272
273impl XChaCha20Poly1305Cipher {
274    /// Generates a new XChaCha20Poly1305 instance with a random key
275    pub fn generate() -> Result<(Self, ChaCha20Poly1305Key)> {
276        let key = ChaCha20Poly1305Key::generate();
277        let cipher = Self::new(&key)?;
278        Ok((cipher, key))
279    }
280
281    /// Convenience method for encryption with a new random nonce
282    pub fn encrypt_with_random_nonce(
283        &self,
284        plaintext: &[u8],
285        aad: Option<&[u8]>,
286    ) -> Result<(Vec<u8>, XChaCha20Poly1305Nonce)> {
287        let nonce = Self::generate_nonce();
288        let ciphertext = self.encrypt(&nonce, plaintext, aad)?;
289        Ok((ciphertext, nonce))
290    }
291
292    /// Helper method to decrypt and verify all in one step
293    pub fn decrypt_and_verify(
294        &self,
295        ciphertext: &[u8],
296        nonce: &XChaCha20Poly1305Nonce,
297        aad: Option<&[u8]>,
298    ) -> Result<Vec<u8>> {
299        self.decrypt(nonce, ciphertext, aad)
300    }
301}