dcrypt_algorithms/aead/xchacha20poly1305/
mod.rs

1//! XChaCha20Poly1305 authenticated encryption with proper error handling
2//!
3//! This module implements the XChaCha20Poly1305 Authenticated Encryption with
4//! Associated Data (AEAD) algorithm, which extends ChaCha20Poly1305 with a
5//! 24-byte nonce.
6
7use crate::aead::chacha20poly1305::{
8    ChaCha20Poly1305, CHACHA20POLY1305_KEY_SIZE, CHACHA20POLY1305_TAG_SIZE,
9};
10use crate::error::{validate, Result};
11use crate::stream::chacha::chacha20::{ChaCha20, CHACHA20_NONCE_SIZE};
12use crate::types::nonce::XChaCha20Compatible;
13use crate::types::Nonce;
14#[cfg(not(feature = "std"))]
15use alloc::vec::Vec;
16use dcrypt_api::traits::AuthenticatedCipher;
17use dcrypt_common::security::{SecretBuffer, SecureZeroingType};
18use zeroize::{Zeroize, ZeroizeOnDrop};
19
20/// Size of the XChaCha20Poly1305 nonce in bytes
21pub const XCHACHA20POLY1305_NONCE_SIZE: usize = 24;
22
23/// XChaCha20Poly1305 variant with extended 24-byte nonce
24#[derive(Clone, Zeroize, ZeroizeOnDrop)]
25pub struct XChaCha20Poly1305 {
26    key: SecretBuffer<CHACHA20POLY1305_KEY_SIZE>,
27}
28
29impl XChaCha20Poly1305 {
30    /// Create a new XChaCha20Poly1305 instance
31    pub fn new(key: &[u8; CHACHA20POLY1305_KEY_SIZE]) -> Self {
32        Self {
33            key: SecretBuffer::new(*key),
34        }
35    }
36
37    /// Creates an instance from raw key bytes
38    pub fn from_key(key: &[u8]) -> Result<Self> {
39        validate::length(
40            "XChaCha20Poly1305 key",
41            key.len(),
42            CHACHA20POLY1305_KEY_SIZE,
43        )?;
44
45        let mut key_bytes = [0u8; CHACHA20POLY1305_KEY_SIZE];
46        key_bytes.copy_from_slice(&key[..CHACHA20POLY1305_KEY_SIZE]);
47        Ok(Self {
48            key: SecretBuffer::new(key_bytes),
49        })
50    }
51
52    /// Encrypt plaintext using XChaCha20Poly1305
53    pub fn encrypt<const N: usize>(
54        &self,
55        nonce: &Nonce<N>,
56        plaintext: &[u8],
57        aad: Option<&[u8]>,
58    ) -> Result<Vec<u8>>
59    where
60        Nonce<N>: XChaCha20Compatible,
61    {
62        // Derive a subkey using HChaCha20 (simplified via ChaCha20)
63        let mut subkey = [0u8; CHACHA20POLY1305_KEY_SIZE];
64        let mut nonce_prefix = [0u8; CHACHA20_NONCE_SIZE];
65
66        // Get the nonce bytes from the generic Nonce type
67        let nonce_bytes = nonce.as_ref();
68        validate::length(
69            "XChaCha20Poly1305 nonce",
70            nonce_bytes.len(),
71            XCHACHA20POLY1305_NONCE_SIZE,
72        )?;
73
74        nonce_prefix.copy_from_slice(&nonce_bytes[..CHACHA20_NONCE_SIZE]);
75
76        // Create a Nonce<12> object from the raw nonce bytes
77        let nonce_obj = Nonce::<CHACHA20_NONCE_SIZE>::new(nonce_prefix);
78
79        // Convert SecretBuffer reference to array reference
80        let key_array: &[u8; CHACHA20POLY1305_KEY_SIZE] = self
81            .key
82            .as_ref()
83            .try_into()
84            .expect("SecretBuffer has correct size");
85
86        // Pass the key array and nonce object to ChaCha20
87        let mut chacha = ChaCha20::new(key_array, &nonce_obj);
88        chacha.keystream(&mut subkey);
89
90        // Use derived subkey with ChaCha20Poly1305
91        let chacha_poly = ChaCha20Poly1305::new(&subkey);
92
93        // Truncate nonce to 12 bytes
94        let mut truncated_nonce = [0u8; CHACHA20_NONCE_SIZE];
95        truncated_nonce.copy_from_slice(&nonce_bytes[12..24]);
96
97        let result = chacha_poly.encrypt_with_nonce(&truncated_nonce, plaintext, aad)?;
98
99        // Zeroize the subkey
100        subkey.zeroize();
101
102        Ok(result)
103    }
104
105    /// Decrypt ciphertext using XChaCha20Poly1305
106    pub fn decrypt<const N: usize>(
107        &self,
108        nonce: &Nonce<N>,
109        ciphertext: &[u8],
110        aad: Option<&[u8]>,
111    ) -> Result<Vec<u8>>
112    where
113        Nonce<N>: XChaCha20Compatible,
114    {
115        // Derive subkey as above
116        let mut subkey = [0u8; CHACHA20POLY1305_KEY_SIZE];
117        let mut nonce_prefix = [0u8; CHACHA20_NONCE_SIZE];
118
119        // Get the nonce bytes from the generic Nonce type
120        let nonce_bytes = nonce.as_ref();
121        validate::length(
122            "XChaCha20Poly1305 nonce",
123            nonce_bytes.len(),
124            XCHACHA20POLY1305_NONCE_SIZE,
125        )?;
126
127        nonce_prefix.copy_from_slice(&nonce_bytes[..CHACHA20_NONCE_SIZE]);
128
129        // Create a Nonce<12> object from the raw nonce bytes
130        let nonce_obj = Nonce::<CHACHA20_NONCE_SIZE>::new(nonce_prefix);
131
132        // Convert SecretBuffer reference to array reference
133        let key_array: &[u8; CHACHA20POLY1305_KEY_SIZE] = self
134            .key
135            .as_ref()
136            .try_into()
137            .expect("SecretBuffer has correct size");
138
139        // Pass the key array and nonce object to ChaCha20
140        let mut chacha = ChaCha20::new(key_array, &nonce_obj);
141        chacha.keystream(&mut subkey);
142
143        let chacha_poly = ChaCha20Poly1305::new(&subkey);
144
145        let mut truncated_nonce = [0u8; CHACHA20_NONCE_SIZE];
146        truncated_nonce.copy_from_slice(&nonce_bytes[12..24]);
147
148        let result = chacha_poly.decrypt_with_nonce(&truncated_nonce, ciphertext, aad)?;
149
150        // Zeroize the subkey
151        subkey.zeroize();
152
153        Ok(result)
154    }
155
156    /// Encrypt with a zero nonce (not recommended for general use)
157    pub fn encrypt_with_zero_nonce(
158        &self,
159        plaintext: &[u8],
160        associated_data: Option<&[u8]>,
161    ) -> Result<Vec<u8>> {
162        let zero_nonce = Nonce::<XCHACHA20POLY1305_NONCE_SIZE>::zeroed();
163        self.encrypt(&zero_nonce, plaintext, associated_data)
164    }
165
166    /// Decrypt with a zero nonce (not recommended for general use)
167    pub fn decrypt_with_zero_nonce(
168        &self,
169        ciphertext: &[u8],
170        associated_data: Option<&[u8]>,
171    ) -> Result<Vec<u8>> {
172        let zero_nonce = Nonce::<XCHACHA20POLY1305_NONCE_SIZE>::zeroed();
173        self.decrypt(&zero_nonce, ciphertext, associated_data)
174    }
175}
176
177// Implement SecureZeroingType for XChaCha20Poly1305
178impl SecureZeroingType for XChaCha20Poly1305 {
179    fn zeroed() -> Self {
180        Self {
181            key: SecretBuffer::zeroed(),
182        }
183    }
184
185    fn secure_clone(&self) -> Self {
186        Self {
187            key: self.key.secure_clone(),
188        }
189    }
190}
191
192// Implement the marker trait AuthenticatedCipher correctly
193impl AuthenticatedCipher for XChaCha20Poly1305 {
194    const TAG_SIZE: usize = CHACHA20POLY1305_TAG_SIZE;
195    const ALGORITHM_ID: &'static str = "XChaCha20Poly1305";
196}
197
198#[cfg(test)]
199mod tests;