Skip to main content

const_secret/
rc4.rs

1//! RC4 stream cipher algorithm implementation.
2//!
3//! This module provides the RC4 (Rivest Cipher 4) stream cipher implementation.
4//! RC4 is a widely-used stream cipher that uses a variable-length key (1-256 bytes)
5//! to generate a pseudorandom keystream which is XOR'd with the plaintext.
6//!
7//! # Security Note
8//!
9//! RC4 is considered cryptographically broken and should not be used for
10//! security-sensitive applications. It is provided here for obfuscation purposes
11//! only. For production use, consider using a modern authenticated encryption
12//! algorithm.
13//!
14//! # Algorithm
15//!
16//! RC4 consists of two main phases:
17//! 1. **KSA (Key Scheduling Algorithm)**: Initializes a 256-byte permutation
18//!    table (S-box) based on the key
19//! 2. **PRGA (Pseudo-Random Generation Algorithm)**: Generates keystream bytes
20//!    by permuting the S-box
21//!
22//! # Types
23//!
24//! - [`Rc4<KEY_LEN, D>`](Rc4): The main algorithm type with const generic key length
25//! - [`ReEncrypt<KEY_LEN>`](ReEncrypt): A drop strategy that re-encrypts data on drop
26//!
27//! # Example
28//!
29//! ```rust
30//! use const_secret::{
31//!     Encrypted, StringLiteral,
32//!     drop_strategy::Zeroize,
33//!     rc4::{ReEncrypt, Rc4},
34//! };
35//!
36//! const KEY: [u8; 5] = *b"mykey";
37//!
38//! // Zeroize on drop (default)
39//! const SECRET: Encrypted<Rc4<5, Zeroize<[u8; 5]>>, StringLiteral, 5> =
40//!     Encrypted::<Rc4<5, Zeroize<[u8; 5]>>, StringLiteral, 5>::new(*b"hello", KEY);
41//!
42//! // Re-encrypt on drop
43//! const SECRET2: Encrypted<Rc4<5, ReEncrypt<5>>, StringLiteral, 6> =
44//!     Encrypted::<Rc4<5, ReEncrypt<5>>, StringLiteral, 6>::new(*b"secret", KEY);
45//!
46//! fn main() {
47//!     let s1: &str = &*SECRET;
48//!     assert_eq!(s1, "hello");
49//!
50//!     let s2: &str = &*SECRET2;
51//!     assert_eq!(s2, "secret");
52//! }
53//! ```
54
55use core::{
56    cell::UnsafeCell,
57    marker::PhantomData,
58    ops::Deref,
59    sync::atomic::{AtomicU8, Ordering},
60};
61
62use crate::{
63    Algorithm, ByteArray, Encrypted, STATE_DECRYPTED, STATE_DECRYPTING, STATE_UNENCRYPTED,
64    StringLiteral,
65    drop_strategy::{DropStrategy, Zeroize},
66};
67
68/// Re-encrypts the buffer using RC4 on drop.
69/// This ensures the plaintext never remains in memory after the value is dropped.
70pub struct ReEncrypt<const KEY_LEN: usize>;
71
72impl<const KEY_LEN: usize> DropStrategy for ReEncrypt<KEY_LEN> {
73    type Extra = [u8; KEY_LEN];
74
75    fn drop(data: &mut [u8], key: &[u8; KEY_LEN]) {
76        // Re-run RC4 to re-encrypt the buffer
77        let mut s = [0u8; 256];
78        let mut j: u8 = 0;
79
80        // Initialize S-box
81        let mut i = 0usize;
82        while i < 256 {
83            s[i] = i as u8;
84            i += 1;
85        }
86
87        // KSA
88        let mut i = 0usize;
89        while i < 256 {
90            j = j.wrapping_add(s[i]).wrapping_add(key[i % KEY_LEN]);
91            s.swap(i, j as usize);
92            i += 1;
93        }
94
95        // PRGA: Re-encrypt
96        let mut i: u8 = 0;
97        j = 0;
98        let mut idx = 0usize;
99        let n = data.len();
100        while idx < n {
101            i = i.wrapping_add(1);
102            j = j.wrapping_add(s[i as usize]);
103            s.swap(i as usize, j as usize);
104            let k = s[(s[i as usize].wrapping_add(s[j as usize])) as usize];
105            data[idx] ^= k;
106            idx += 1;
107        }
108    }
109}
110
111/// An algorithm that performs RC4 encryption and decryption.
112/// This algorithm is generic over drop strategy.
113///
114/// RC4 is a stream cipher that uses a variable-length key (1-256 bytes).
115/// The key is stored alongside the encrypted data and is used to reproduce
116/// the keystream for decryption at runtime.
117pub struct Rc4<const KEY_LEN: usize, D: DropStrategy = Zeroize>(PhantomData<D>);
118
119impl<const KEY_LEN: usize, D: DropStrategy<Extra = [u8; KEY_LEN]>> Algorithm for Rc4<KEY_LEN, D> {
120    type Drop = D;
121    type Extra = [u8; KEY_LEN];
122}
123
124impl<const KEY_LEN: usize, D: DropStrategy<Extra = [u8; KEY_LEN]>, M, const N: usize>
125    Encrypted<Rc4<KEY_LEN, D>, M, N>
126{
127    /// Creates a new encrypted buffer using RC4.
128    ///
129    /// # Arguments
130    /// * `buffer` - The plaintext data to encrypt (must be an array of length N)
131    /// * `key` - The RC4 key (must be an array of length `KEY_LEN`)
132    ///
133    /// This function performs RC4 encryption at compile time:
134    /// 1. Runs the Key Scheduling Algorithm (KSA) to initialize the S-box
135    /// 2. Runs the Pseudo-Random Generation Algorithm (PRGA) to generate keystream
136    /// 3. XORs the keystream with the plaintext
137    pub const fn new(mut buffer: [u8; N], key: [u8; KEY_LEN]) -> Self {
138        // RC4 Key Scheduling Algorithm (KSA) and PRGA combined
139        // We use a fixed 256-byte S-box for simplicity
140        let mut s = [0u8; 256];
141        let mut j: u8 = 0;
142
143        // Initialize S-box
144        let mut i = 0usize;
145        while i < 256 {
146            s[i] = i as u8;
147            i += 1;
148        }
149
150        // KSA: Permute S-box based on key
151        let mut i = 0usize;
152        while i < 256 {
153            let key_byte = key[i % KEY_LEN];
154            j = j.wrapping_add(s[i]).wrapping_add(key_byte);
155            // Swap s[i] and s[j]
156            let temp = s[i];
157            s[i] = s[j as usize];
158            s[j as usize] = temp;
159            i += 1;
160        }
161
162        // PRGA: Generate keystream and encrypt buffer in place
163        let mut i: u8 = 0;
164        j = 0;
165        let mut idx = 0usize;
166        while idx < N {
167            i = i.wrapping_add(1);
168            j = j.wrapping_add(s[i as usize]);
169            // Swap s[i] and s[j]
170            let temp = s[i as usize];
171            s[i as usize] = s[j as usize];
172            s[j as usize] = temp;
173            // Generate keystream byte and XOR with buffer
174            let k = s[(s[i as usize].wrapping_add(s[j as usize])) as usize];
175            buffer[idx] ^= k;
176            idx += 1;
177        }
178
179        Encrypted {
180            buffer: UnsafeCell::new(buffer),
181            decryption_state: AtomicU8::new(STATE_UNENCRYPTED),
182            extra: key,
183            _phantom: PhantomData,
184        }
185    }
186}
187
188impl<const KEY_LEN: usize, D: DropStrategy<Extra = [u8; KEY_LEN]>, const N: usize> Deref
189    for Encrypted<Rc4<KEY_LEN, D>, ByteArray, N>
190{
191    type Target = [u8; N];
192
193    fn deref(&self) -> &Self::Target {
194        // Fast path: already decrypted
195        if self.decryption_state.load(Ordering::Acquire) == STATE_DECRYPTED {
196            // SAFETY: `buffer` is initialized and lives as long as `self`.
197            return unsafe { &*self.buffer.get() };
198        }
199
200        // Try to acquire the decryption lock by transitioning from UNENCRYPTED to DECRYPTING
201        match self.decryption_state.compare_exchange(
202            STATE_UNENCRYPTED,
203            STATE_DECRYPTING,
204            Ordering::AcqRel,
205            Ordering::Acquire,
206        ) {
207            Ok(_) => {
208                // SAFETY: `buffer` is always initialized and points to valid `[u8; N]`.
209                // We won the race, perform decryption with exclusive mutable access.
210                let data = unsafe { &mut *self.buffer.get() };
211                // Reconstruct RC4 state from stored key and decrypt
212                let key = &self.extra;
213                let mut s = [0u8; 256];
214                let mut j: u8 = 0;
215
216                // Initialize S-box
217                let mut i = 0usize;
218                while i < 256 {
219                    s[i] = i as u8;
220                    i += 1;
221                }
222
223                // KSA
224                let mut i = 0usize;
225                while i < 256 {
226                    j = j.wrapping_add(s[i]).wrapping_add(key[i % KEY_LEN]);
227                    s.swap(i, j as usize);
228                    i += 1;
229                }
230
231                // PRGA: Decrypt
232                let mut i: u8 = 0;
233                j = 0;
234                let mut idx = 0usize;
235                while idx < N {
236                    i = i.wrapping_add(1);
237                    j = j.wrapping_add(s[i as usize]);
238                    s.swap(i as usize, j as usize);
239                    let k = s[(s[i as usize].wrapping_add(s[j as usize])) as usize];
240                    data[idx] ^= k;
241                    idx += 1;
242                }
243
244                // Decryption complete - release lock by transitioning to DECRYPTED
245                // Use Release ordering to ensure all decryption writes are visible to other threads
246                self.decryption_state.store(STATE_DECRYPTED, Ordering::Release);
247            }
248            Err(_) => {
249                // Lost the race - another thread is decrypting
250                // Spin-wait until decryption completes
251                while self.decryption_state.load(Ordering::Acquire) != STATE_DECRYPTED {
252                    core::hint::spin_loop();
253                }
254            }
255        }
256
257        // SAFETY: `buffer` is initialized and lives as long as `self`.
258        // Decryption is complete (either by us or another thread), so it's safe
259        // to return a shared reference.
260        unsafe { &*self.buffer.get() }
261    }
262}
263
264impl<const KEY_LEN: usize, D: DropStrategy<Extra = [u8; KEY_LEN]>, const N: usize> Deref
265    for Encrypted<Rc4<KEY_LEN, D>, StringLiteral, N>
266{
267    type Target = str;
268
269    fn deref(&self) -> &Self::Target {
270        // Fast path: already decrypted
271        if self.decryption_state.load(Ordering::Acquire) == STATE_DECRYPTED {
272            // SAFETY: `buffer` is initialized and lives as long as `self`.
273            let bytes = unsafe { &*self.buffer.get() };
274            // SAFETY: Since the original input was a valid UTF-8 string literal, XOR
275            // with RC4 keystream preserves the length, and RC4 is a bijection,
276            // so the resulting bytes will still form a valid UTF-8 string.
277            return unsafe { core::str::from_utf8_unchecked(bytes) };
278        }
279
280        // Try to acquire the decryption lock by transitioning from UNENCRYPTED to DECRYPTING
281        match self.decryption_state.compare_exchange(
282            STATE_UNENCRYPTED,
283            STATE_DECRYPTING,
284            Ordering::AcqRel,
285            Ordering::Acquire,
286        ) {
287            Ok(_) => {
288                // SAFETY: `buffer` is always initialized and points to valid `[u8; N]`.
289                // We won the race, perform decryption with exclusive mutable access.
290                let data = unsafe { &mut *self.buffer.get() };
291                // Reconstruct RC4 state from stored key and decrypt
292                let key = &self.extra;
293                let mut s = [0u8; 256];
294                let mut j: u8 = 0;
295
296                // Initialize S-box
297                let mut i = 0usize;
298                while i < 256 {
299                    s[i] = i as u8;
300                    i += 1;
301                }
302
303                // KSA
304                let mut i = 0usize;
305                while i < 256 {
306                    j = j.wrapping_add(s[i]).wrapping_add(key[i % KEY_LEN]);
307                    s.swap(i, j as usize);
308                    i += 1;
309                }
310
311                // PRGA: Decrypt
312                let mut i: u8 = 0;
313                j = 0;
314                let mut idx = 0usize;
315                while idx < N {
316                    i = i.wrapping_add(1);
317                    j = j.wrapping_add(s[i as usize]);
318                    s.swap(i as usize, j as usize);
319                    let k = s[(s[i as usize].wrapping_add(s[j as usize])) as usize];
320                    data[idx] ^= k;
321                    idx += 1;
322                }
323
324                // Decryption complete - release lock by transitioning to DECRYPTED
325                // Use Release ordering to ensure all decryption writes are visible to other threads
326                self.decryption_state.store(STATE_DECRYPTED, Ordering::Release);
327            }
328            Err(_) => {
329                // Lost the race - another thread is decrypting
330                // Spin-wait until decryption completes
331                while self.decryption_state.load(Ordering::Acquire) != STATE_DECRYPTED {
332                    core::hint::spin_loop();
333                }
334            }
335        }
336
337        // SAFETY: `buffer` is initialized and lives as long as `self`.
338        // Decryption is complete (either by us or another thread), so it's safe
339        // to return a shared reference.
340        let bytes = unsafe { &*self.buffer.get() };
341
342        // SAFETY: Since the original input was a valid UTF-8 string literal, XOR
343        // with RC4 keystream preserves the length, and RC4 is a bijection,
344        // so the resulting bytes will still form a valid UTF-8 string.
345        unsafe { core::str::from_utf8_unchecked(bytes) }
346    }
347}
348
349#[cfg(test)]
350mod tests {
351    use super::*;
352    use crate::{
353        ByteArray, StringLiteral,
354        drop_strategy::{NoOp, Zeroize},
355        rc4::Rc4,
356    };
357
358    use alloc::vec;
359    use alloc::vec::Vec;
360    use core::sync::atomic::AtomicUsize;
361    use std::sync::Arc;
362    use std::thread;
363
364    // 5-byte key
365    const RC4_KEY: [u8; 5] = *b"mykey";
366    const RC4_KEY2: [u8; 16] = *b"sixteen-byte-key";
367
368    const CONST_ENCRYPTED: Encrypted<Rc4<5, Zeroize<[u8; 5]>>, ByteArray, 5> =
369        Encrypted::<Rc4<5, Zeroize<[u8; 5]>>, ByteArray, 5>::new(*b"hello", RC4_KEY);
370
371    const CONST_ENCRYPTED_STR: Encrypted<Rc4<5, Zeroize<[u8; 5]>>, StringLiteral, 5> =
372        Encrypted::<Rc4<5, Zeroize<[u8; 5]>>, StringLiteral, 5>::new(*b"hello", RC4_KEY);
373
374    const CONST_ENCRYPTED_16: Encrypted<Rc4<16, Zeroize<[u8; 16]>>, ByteArray, 8> =
375        Encrypted::<Rc4<16, Zeroize<[u8; 16]>>, ByteArray, 8>::new(*b"longdata", RC4_KEY2);
376
377    #[test]
378    fn test_rc4_buffer_is_encrypted_before_deref() {
379        let encrypted = CONST_ENCRYPTED;
380
381        // Before deref, the raw buffer should hold the RC4-encrypted data
382        let raw = unsafe { &*encrypted.buffer.get() };
383        // RC4 encryption produces different output than plaintext
384        assert_ne!(raw, b"hello", "buffer must NOT be plaintext before deref");
385        // The key should be stored in the extra field
386        assert_eq!(encrypted.extra, RC4_KEY, "key should be stored in extra");
387    }
388
389    #[test]
390    fn test_rc4_bytearray_deref_decrypts() {
391        let encrypted = CONST_ENCRYPTED;
392
393        // Deref should decrypt and return the original plaintext
394        let plain: &[u8; 5] = &*encrypted;
395        assert_eq!(plain, b"hello");
396    }
397
398    #[test]
399    fn test_rc4_string_deref_decrypts() {
400        let encrypted = CONST_ENCRYPTED_STR;
401
402        // Deref should decrypt and return the original plaintext
403        let plain: &str = &*encrypted;
404        assert_eq!(plain, "hello");
405    }
406
407    #[test]
408    fn test_rc4_multiple_derefs_are_idempotent() {
409        let encrypted = CONST_ENCRYPTED;
410
411        let first: &[u8; 5] = &*encrypted;
412        let second: &[u8; 5] = &*encrypted;
413        assert_eq!(first, b"hello");
414        assert_eq!(second, b"hello");
415    }
416
417    #[test]
418    fn test_rc4_different_key_length() {
419        let encrypted = CONST_ENCRYPTED_16;
420
421        let plain: &[u8; 8] = &*encrypted;
422        assert_eq!(plain, b"longdata");
423    }
424
425    #[test]
426    fn test_rc4_encrypted_is_sync() {
427        const fn assert_sync<T: Sync>() {}
428        const fn check() {
429            assert_sync::<Encrypted<Rc4<5, Zeroize<[u8; 5]>>, ByteArray, 8>>();
430            assert_sync::<Encrypted<Rc4<16, Zeroize<[u8; 16]>>, StringLiteral, 10>>();
431            assert_sync::<Encrypted<Rc4<32, NoOp<[u8; 32]>>, ByteArray, 16>>();
432        }
433        check();
434    }
435
436    #[test]
437    fn test_rc4_concurrent_deref_same_value() {
438        const SHARED: Encrypted<Rc4<5, Zeroize<[u8; 5]>>, StringLiteral, 5> =
439            Encrypted::<Rc4<5, Zeroize<[u8; 5]>>, StringLiteral, 5>::new(*b"hello", RC4_KEY);
440
441        let shared = Arc::new(SHARED);
442        let mut handles: Vec<thread::JoinHandle<()>> = vec![];
443
444        for _ in 0..10 {
445            let shared_clone = Arc::clone(&shared);
446            let handle = thread::spawn(move || {
447                let decrypted: &str = &*shared_clone;
448                assert_eq!(decrypted, "hello");
449            });
450            handles.push(handle);
451        }
452
453        for handle in handles {
454            handle.join().unwrap();
455        }
456    }
457
458    #[test]
459    fn test_rc4_concurrent_deref_bytearray() {
460        const SHARED: Encrypted<Rc4<16, Zeroize<[u8; 16]>>, ByteArray, 4> =
461            Encrypted::<Rc4<16, Zeroize<[u8; 16]>>, ByteArray, 4>::new([1, 2, 3, 4], RC4_KEY2);
462
463        let shared = Arc::new(SHARED);
464        let mut handles: Vec<thread::JoinHandle<()>> = vec![];
465
466        for _ in 0..20 {
467            let shared_clone = Arc::clone(&shared);
468            let handle = thread::spawn(move || {
469                let decrypted: &[u8; 4] = &*shared_clone;
470                assert_eq!(decrypted, &[1, 2, 3, 4]);
471            });
472            handles.push(handle);
473        }
474
475        for handle in handles {
476            handle.join().unwrap();
477        }
478    }
479
480    #[test]
481    fn test_rc4_concurrent_deref_race_condition() {
482        const SHARED: Encrypted<Rc4<5, Zeroize<[u8; 5]>>, StringLiteral, 8> =
483            Encrypted::<Rc4<5, Zeroize<[u8; 5]>>, StringLiteral, 8>::new(*b"racetest", RC4_KEY);
484
485        let shared = Arc::new(SHARED);
486        let results = Arc::new(AtomicUsize::new(0));
487        let mut handles: Vec<thread::JoinHandle<()>> = vec![];
488
489        for _ in 0..50 {
490            let shared_clone = Arc::clone(&shared);
491            let results_clone = Arc::clone(&results);
492            let handle = thread::spawn(move || {
493                let decrypted: &str = &*shared_clone;
494                if decrypted == "racetest" {
495                    results_clone.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
496                }
497            });
498            handles.push(handle);
499        }
500
501        for handle in handles {
502            handle.join().unwrap();
503        }
504
505        let success_count = results.load(core::sync::atomic::Ordering::Relaxed);
506        assert_eq!(success_count, 50, "all threads should see correct plaintext");
507    }
508
509    #[test]
510    fn test_rc4_single_byte() {
511        const ENCRYPTED: Encrypted<Rc4<5, Zeroize<[u8; 5]>>, ByteArray, 1> =
512            Encrypted::<Rc4<5, Zeroize<[u8; 5]>>, ByteArray, 1>::new([42], RC4_KEY);
513
514        let plain: &[u8; 1] = &*ENCRYPTED;
515        assert_eq!(plain, &[42]);
516    }
517
518    #[test]
519    fn test_rc4_all_zeros() {
520        const ENCRYPTED: Encrypted<Rc4<5, Zeroize<[u8; 5]>>, ByteArray, 4> =
521            Encrypted::<Rc4<5, Zeroize<[u8; 5]>>, ByteArray, 4>::new([0, 0, 0, 0], RC4_KEY);
522
523        let plain: &[u8; 4] = &*ENCRYPTED;
524        assert_eq!(plain, &[0, 0, 0, 0]);
525    }
526
527    #[test]
528    fn test_rc4_reencrypt_drop() {
529        use crate::rc4::ReEncrypt;
530
531        const SHARED: Encrypted<Rc4<5, ReEncrypt<5>>, StringLiteral, 5> =
532            Encrypted::<Rc4<5, ReEncrypt<5>>, StringLiteral, 5>::new(*b"hello", RC4_KEY);
533
534        let shared = Arc::new(SHARED);
535        let mut handles: Vec<thread::JoinHandle<()>> = vec![];
536
537        for _ in 0..10 {
538            let shared_clone = Arc::clone(&shared);
539            let handle = thread::spawn(move || {
540                let decrypted: &str = &*shared_clone;
541                assert_eq!(decrypted, "hello");
542            });
543            handles.push(handle);
544        }
545
546        for handle in handles {
547            handle.join().unwrap();
548        }
549
550        // After all threads finish and the Arc is dropped, the data should be re-encrypted
551        // (We can't easily test the re-encryption result here, but the test verifies
552        // that ReEncrypt compiles and works with the type system)
553    }
554}