Skip to main content

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//!
7//! dcrypt v1.2.3 is confirmed to have used a nonstandard construction under
8//! this name; the exact earlier introduced-version range is under investigation.
9//! This standard implementation intentionally does not decrypt
10//! those bytes. Migrate legacy data only through a separately isolated,
11//! decrypt-only compatibility tool; never relabel it as XChaCha20-Poly1305.
12
13use crate::aead::chacha20poly1305::{CHACHA20POLY1305_KEY_SIZE, CHACHA20POLY1305_TAG_SIZE};
14use crate::error::{validate, Result};
15use crate::types::nonce::XChaCha20Compatible;
16use crate::types::Nonce;
17#[cfg(not(feature = "std"))]
18use alloc::vec::Vec;
19use chacha20poly1305::aead::{Aead, KeyInit, Payload};
20use chacha20poly1305::{XChaCha20Poly1305 as BackendXChaCha20Poly1305, XNonce};
21use dcrypt_api::traits::AuthenticatedCipher;
22use dcrypt_common::security::{SecretBuffer, SecureZeroingType};
23use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
24
25/// Size of the XChaCha20Poly1305 nonce in bytes
26pub const XCHACHA20POLY1305_NONCE_SIZE: usize = 24;
27
28/// Standard XChaCha20-Poly1305 with an extended 24-byte nonce.
29///
30/// This type does not accept the nonstandard ciphertext format emitted by
31/// the affected legacy format (confirmed in dcrypt v1.2.3).
32#[derive(Clone, Zeroize, ZeroizeOnDrop)]
33pub struct XChaCha20Poly1305 {
34    key: SecretBuffer<CHACHA20POLY1305_KEY_SIZE>,
35}
36
37impl XChaCha20Poly1305 {
38    /// Create a new XChaCha20Poly1305 instance
39    pub fn new(key: &[u8; CHACHA20POLY1305_KEY_SIZE]) -> Self {
40        Self {
41            key: SecretBuffer::new(*key),
42        }
43    }
44
45    /// Creates an instance from raw key bytes
46    pub fn from_key(key: &[u8]) -> Result<Self> {
47        validate::length(
48            "XChaCha20Poly1305 key",
49            key.len(),
50            CHACHA20POLY1305_KEY_SIZE,
51        )?;
52
53        let mut key_bytes = Zeroizing::new([0u8; CHACHA20POLY1305_KEY_SIZE]);
54        key_bytes.copy_from_slice(&key[..CHACHA20POLY1305_KEY_SIZE]);
55        Ok(Self {
56            key: SecretBuffer::new(*key_bytes),
57        })
58    }
59
60    /// Encrypt plaintext using XChaCha20Poly1305
61    pub fn encrypt<const N: usize>(
62        &self,
63        nonce: &Nonce<N>,
64        plaintext: &[u8],
65        aad: Option<&[u8]>,
66    ) -> Result<Vec<u8>>
67    where
68        Nonce<N>: XChaCha20Compatible,
69    {
70        validate::length(
71            "XChaCha20Poly1305 nonce",
72            nonce.as_ref().len(),
73            XCHACHA20POLY1305_NONCE_SIZE,
74        )?;
75        let cipher = BackendXChaCha20Poly1305::new_from_slice(self.key.as_ref())
76            .map_err(|_| crate::error::Error::param("key", "invalid XChaCha20 key"))?;
77        cipher
78            .encrypt(
79                XNonce::from_slice(nonce.as_ref()),
80                Payload {
81                    msg: plaintext,
82                    aad: aad.unwrap_or(&[]),
83                },
84            )
85            .map_err(|_| crate::error::Error::Processing {
86                operation: "XChaCha20Poly1305 encryption",
87                details: "message is too long",
88            })
89    }
90
91    /// Decrypt ciphertext using XChaCha20Poly1305
92    pub fn decrypt<const N: usize>(
93        &self,
94        nonce: &Nonce<N>,
95        ciphertext: &[u8],
96        aad: Option<&[u8]>,
97    ) -> Result<Vec<u8>>
98    where
99        Nonce<N>: XChaCha20Compatible,
100    {
101        validate::length(
102            "XChaCha20Poly1305 nonce",
103            nonce.as_ref().len(),
104            XCHACHA20POLY1305_NONCE_SIZE,
105        )?;
106        let cipher = BackendXChaCha20Poly1305::new_from_slice(self.key.as_ref())
107            .map_err(|_| crate::error::Error::param("key", "invalid XChaCha20 key"))?;
108        cipher
109            .decrypt(
110                XNonce::from_slice(nonce.as_ref()),
111                Payload {
112                    msg: ciphertext,
113                    aad: aad.unwrap_or(&[]),
114                },
115            )
116            .map_err(|_| crate::error::Error::Authentication {
117                algorithm: "XChaCha20Poly1305",
118            })
119    }
120}
121
122// Implement SecureZeroingType for XChaCha20Poly1305
123impl SecureZeroingType for XChaCha20Poly1305 {
124    fn zeroed() -> Self {
125        Self {
126            key: SecretBuffer::zeroed(),
127        }
128    }
129
130    fn secure_clone(&self) -> Self {
131        Self {
132            key: self.key.secure_clone(),
133        }
134    }
135}
136
137// Implement the marker trait AuthenticatedCipher correctly
138impl AuthenticatedCipher for XChaCha20Poly1305 {
139    const TAG_SIZE: usize = CHACHA20POLY1305_TAG_SIZE;
140    const ALGORITHM_ID: &'static str = "XChaCha20Poly1305";
141}
142
143#[cfg(test)]
144mod tests;