Skip to main content

libsodium_rs/crypto_stream/
mod.rs

1//! # Stream Ciphers
2//!
3//! This module provides access to various stream ciphers implemented in libsodium:
4//! - ChaCha20: A stream cipher developed by Daniel J. Bernstein with good diffusion properties
5//! - XChaCha20: An extended nonce variant of ChaCha20 (recommended for most applications)
6//! - Salsa20: The predecessor to ChaCha20, also developed by Daniel J. Bernstein
7//!
8//! ## Important Notes
9//!
10//! - These functions are stream ciphers and do not provide authenticated encryption.
11//! - They can be used to generate pseudo-random data from a key or as building blocks
12//!   for implementing custom constructions.
13//! - For authenticated encryption, use `crypto_secretbox` instead.
14//! - XChaCha20 is recommended for most applications due to its extended nonce size.
15//!
16//! ## Usage Example
17//!
18//! ```
19//! use libsodium_rs as sodium;
20//! use sodium::crypto_stream;
21//! use sodium::ensure_init;
22//!
23//! // Initialize libsodium
24//! ensure_init().expect("Failed to initialize libsodium");
25//!
26//! // Generate a random key
27//! let key = crypto_stream::Key::generate().unwrap();
28//!
29//! // Create a nonce (in a real application, this should be unique for each message)
30//! let nonce = crypto_stream::xchacha20::Nonce::from_bytes([0u8; crypto_stream::xchacha20::NONCEBYTES]);
31//!
32//! // Message to encrypt
33//! let message = b"This is a secret message";
34//!
35//! // Encrypt the message using XChaCha20
36//! let encrypted = crypto_stream::xchacha20::stream_xor(message, &nonce, &key).unwrap();
37//!
38//! // Decrypt the message (with stream ciphers, encryption and decryption are the same operation)
39//! let decrypted = crypto_stream::xchacha20::stream_xor(&encrypted, &nonce, &key).unwrap();
40//!
41//! assert_eq!(&decrypted, message);
42//! ```
43
44use crate::{Result, SodiumError};
45use std::convert::TryFrom;
46use std::fmt;
47
48// Re-export submodules
49pub mod chacha20;
50pub mod salsa20;
51pub mod xchacha20;
52
53/// Number of bytes in a standard stream encryption key (32 bytes)
54pub const KEYBYTES: usize = libsodium_sys::crypto_stream_KEYBYTES as usize;
55/// Number of bytes in a standard stream encryption nonce (24 bytes)
56pub const NONCEBYTES: usize = libsodium_sys::crypto_stream_NONCEBYTES as usize;
57
58/// A secret key for stream encryption
59///
60/// This key is used for symmetric encryption with various stream ciphers.
61/// All stream cipher variants in this module use the same key size (32 bytes).
62#[derive(Debug, Clone, Eq, PartialEq, zeroize::Zeroize, zeroize::ZeroizeOnDrop)]
63pub struct Key([u8; KEYBYTES]);
64
65impl Key {
66    /// Generates a new random key for stream encryption
67    ///
68    /// This function generates a cryptographically secure random key that can be used
69    /// with any of the stream cipher variants in this module (ChaCha20, XChaCha20, Salsa20).
70    ///
71    /// # Returns
72    /// * `Result<Self>` - A randomly generated key
73    ///
74    /// # Example
75    /// ```
76    /// use libsodium_rs as sodium;
77    /// use sodium::crypto_stream;
78    /// use sodium::ensure_init;
79    ///
80    /// // Initialize libsodium
81    /// ensure_init().expect("Failed to initialize libsodium");
82    ///
83    /// // Generate a random key
84    /// let key = crypto_stream::Key::generate().unwrap();
85    /// ```
86    pub fn generate() -> Result<Self> {
87        let mut key = [0u8; KEYBYTES];
88        unsafe {
89            libsodium_sys::crypto_stream_keygen(key.as_mut_ptr());
90        }
91        Ok(Key(key))
92    }
93
94    /// Creates a key from a byte slice
95    ///
96    /// This function creates a key from an existing byte slice, which must be exactly
97    /// `KEYBYTES` (32) bytes long. This is useful when you have an existing key or
98    /// when you need to derive a key from another source.
99    ///
100    /// # Arguments
101    /// * `slice` - The bytes to create the key from
102    ///
103    /// # Returns
104    /// * `Result<Self>` - The key or an error if the input is invalid
105    ///
106    /// # Errors
107    /// Returns an error if the input is not exactly `KEYBYTES` bytes long
108    ///
109    /// # Example
110    /// ```
111    /// use libsodium_rs as sodium;
112    /// use sodium::crypto_stream;
113    /// use sodium::ensure_init;
114    ///
115    /// // Initialize libsodium
116    /// ensure_init().expect("Failed to initialize libsodium");
117    ///
118    /// // Create a key from existing bytes
119    /// let key_bytes = [0x42; crypto_stream::KEYBYTES]; // In a real application, use a proper key
120    /// let key = crypto_stream::Key::from_slice(&key_bytes).unwrap();
121    /// ```
122    pub fn from_slice(slice: &[u8]) -> Result<Self> {
123        if slice.len() != KEYBYTES {
124            return Err(SodiumError::InvalidInput(format!(
125                "key must be exactly {KEYBYTES} bytes"
126            )));
127        }
128
129        let mut key = [0u8; KEYBYTES];
130        key.copy_from_slice(slice);
131        Ok(Key(key))
132    }
133
134    /// Returns a reference to the key as a byte slice
135    ///
136    /// This method provides access to the raw bytes of the key, which can be useful
137    /// when you need to pass the key to other functions or store it.
138    ///
139    /// # Returns
140    /// * `&[u8]` - A reference to the key bytes
141    ///
142    /// # Security Considerations
143    /// Be careful when handling the raw key bytes. Avoid logging or displaying them,
144    /// and ensure they are securely erased from memory when no longer needed.
145    ///
146    /// # Example
147    /// ```
148    /// use libsodium_rs as sodium;
149    /// use sodium::crypto_stream;
150    /// use sodium::ensure_init;
151    ///
152    /// // Initialize libsodium
153    /// ensure_init().expect("Failed to initialize libsodium");
154    ///
155    /// // Generate a random key
156    /// let key = crypto_stream::Key::generate().unwrap();
157    ///
158    /// // Get the raw bytes of the key
159    /// let key_bytes = key.as_bytes();
160    /// assert_eq!(key_bytes.len(), crypto_stream::KEYBYTES);
161    /// ```
162    pub fn as_bytes(&self) -> &[u8] {
163        &self.0
164    }
165}
166
167// Add implementation of Display for Key for easier debugging
168impl fmt::Display for Key {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        write!(f, "Key([...])") // Don't show the actual bytes for security
171    }
172}
173
174// Add implementation of TryFrom for Key for more idiomatic conversions
175impl TryFrom<&[u8]> for Key {
176    type Error = SodiumError;
177
178    fn try_from(bytes: &[u8]) -> std::result::Result<Self, Self::Error> {
179        Key::from_slice(bytes)
180    }
181}
182
183impl AsRef<[u8]> for Key {
184    fn as_ref(&self) -> &[u8] {
185        &self.0
186    }
187}
188
189impl From<[u8; KEYBYTES]> for Key {
190    fn from(bytes: [u8; KEYBYTES]) -> Self {
191        Self(bytes)
192    }
193}
194
195impl From<Key> for [u8; KEYBYTES] {
196    fn from(key: Key) -> [u8; KEYBYTES] {
197        key.0
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204
205    #[test]
206    fn test_key_generation() {
207        // Generate a random key
208        let key = Key::generate().unwrap();
209
210        // Verify the key size
211        assert_eq!(key.as_bytes().len(), KEYBYTES);
212
213        // Test creating a key from bytes
214        let key_bytes = key.as_bytes();
215        let key2 = Key::from_slice(key_bytes).unwrap();
216        assert_eq!(key2.as_bytes(), key_bytes);
217    }
218
219    #[test]
220    fn test_key_traits() {
221        // Test TryFrom<&[u8]>
222        let bytes = [0x42; KEYBYTES];
223        let key = Key::try_from(&bytes[..]).unwrap();
224        assert_eq!(key.as_bytes(), &bytes);
225
226        // Test invalid length
227        let invalid_bytes = [0x42; KEYBYTES - 1];
228        assert!(Key::try_from(&invalid_bytes[..]).is_err());
229
230        // Test From<[u8; KEYBYTES]>
231        let bytes = [0x43; KEYBYTES];
232        let key2 = Key::from(bytes);
233        assert_eq!(key2.as_bytes(), &bytes);
234
235        // Test From<Key> for [u8; KEYBYTES]
236        let extracted: [u8; KEYBYTES] = key2.into();
237        assert_eq!(extracted, bytes);
238
239        // Test AsRef<[u8]>
240        let key3 = Key::generate().unwrap();
241        let slice_ref: &[u8] = key3.as_ref();
242        assert_eq!(slice_ref.len(), KEYBYTES);
243    }
244
245    #[test]
246    fn test_chacha20() {
247        // Generate a random key and nonce
248        let key = Key::generate().unwrap();
249        let nonce = chacha20::Nonce::from_bytes([0u8; chacha20::NONCEBYTES]);
250        let message = b"test message";
251
252        // Test stream generation
253        let stream = chacha20::stream(32, &nonce, &key).unwrap();
254        assert_eq!(stream.len(), 32);
255
256        // Verify the stream is not all zeros
257        assert!(stream.iter().any(|&b| b != 0));
258
259        // Test encryption/decryption
260        let encrypted = chacha20::stream_xor(message, &nonce, &key).unwrap();
261
262        // Verify the encrypted data is different from the original
263        assert_ne!(&encrypted, message);
264
265        // Decrypt and verify it matches the original
266        let decrypted = chacha20::stream_xor(&encrypted, &nonce, &key).unwrap();
267        assert_eq!(&decrypted, message);
268    }
269
270    #[test]
271    fn test_xchacha20() {
272        // Generate a random key and nonce
273        let key = Key::generate().unwrap();
274        let nonce = xchacha20::Nonce::from_bytes([0u8; xchacha20::NONCEBYTES]);
275        let message = b"test message";
276
277        // Test stream generation
278        let stream = xchacha20::stream(32, &nonce, &key).unwrap();
279        assert_eq!(stream.len(), 32);
280
281        // Verify the stream is not all zeros
282        assert!(stream.iter().any(|&b| b != 0));
283
284        // Test encryption/decryption
285        let encrypted = xchacha20::stream_xor(message, &nonce, &key).unwrap();
286
287        // Verify the encrypted data is different from the original
288        assert_ne!(&encrypted, message);
289
290        // Decrypt and verify it matches the original
291        let decrypted = xchacha20::stream_xor(&encrypted, &nonce, &key).unwrap();
292        assert_eq!(&decrypted, message);
293    }
294
295    #[test]
296    fn test_salsa20() {
297        // Generate a random key and nonce
298        let key = Key::generate().unwrap();
299        let nonce = salsa20::Nonce::from_bytes([0u8; salsa20::NONCEBYTES]);
300        let message = b"test message";
301
302        // Test stream generation
303        let stream = salsa20::stream(32, &nonce, &key);
304        assert_eq!(stream.len(), 32);
305
306        // Verify the stream is not all zeros
307        assert!(stream.iter().any(|&b| b != 0));
308
309        // Test encryption/decryption
310        let encrypted = salsa20::stream_xor(message, &nonce, &key);
311
312        // Verify the encrypted data is different from the original
313        assert_ne!(&encrypted, message);
314
315        // Decrypt and verify it matches the original
316        let decrypted = salsa20::stream_xor(&encrypted, &nonce, &key);
317        assert_eq!(&decrypted, message);
318    }
319
320    #[test]
321    fn test_invalid_nonce_length() {
322        // Generate a random key (unused but kept for documentation purposes)
323        let _key = Key::generate().unwrap();
324
325        // Create invalid nonce slices
326        let invalid_nonce_long = vec![0u8; chacha20::NONCEBYTES + 1];
327        let invalid_nonce_short = vec![0u8; chacha20::NONCEBYTES - 1];
328
329        // Test chacha20 nonce validation
330        assert!(chacha20::Nonce::try_from_slice(&invalid_nonce_long).is_err());
331        assert!(chacha20::Nonce::try_from_slice(&invalid_nonce_short).is_err());
332
333        // Test xchacha20 nonce validation
334        let invalid_xchacha_nonce_long = vec![0u8; xchacha20::NONCEBYTES + 1];
335        let invalid_xchacha_nonce_short = vec![0u8; xchacha20::NONCEBYTES - 1];
336        assert!(xchacha20::Nonce::try_from_slice(&invalid_xchacha_nonce_long).is_err());
337        assert!(xchacha20::Nonce::try_from_slice(&invalid_xchacha_nonce_short).is_err());
338
339        // Test salsa20 nonce validation
340        let invalid_salsa_nonce_long = vec![0u8; salsa20::NONCEBYTES + 1];
341        let invalid_salsa_nonce_short = vec![0u8; salsa20::NONCEBYTES - 1];
342        assert!(salsa20::Nonce::try_from_slice(&invalid_salsa_nonce_long).is_err());
343        assert!(salsa20::Nonce::try_from_slice(&invalid_salsa_nonce_short).is_err());
344    }
345}