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}