Skip to main content

const_secret/
lib.rs

1//! A `no_std` crate for compile-time encrypted secrets.
2//!
3//! This crate provides encrypted storage for sensitive data that is encrypted at compile time
4//! and only decrypted at runtime when accessed. This prevents secrets from appearing in
5//! plaintext in the final binary.
6//!
7//! # Features
8//!
9//! - **Compile-time encryption**: Secrets are encrypted during compilation
10//! - **Multiple algorithms**: XOR (simple, fast) and RC4 (stream cipher)
11//! - **Drop strategies**: Control what happens to decrypted data on drop:
12//!   - `Zeroize`: Overwrites memory with zeros
13//!   - `ReEncrypt`: Re-encrypts the data
14//!   - `NoOp`: Leaves data unchanged
15//! - **Thread-safe**: `Sync` implementation allows concurrent access
16//! - `no_std` compatible: Works in embedded environments
17//!
18//! # Examples
19//!
20//! ## XOR Algorithm
21//!
22//! XOR is the simplest and fastest algorithm. It uses a single-byte key:
23//!
24//! ```rust
25//! use const_secret::{
26//!     Encrypted, StringLiteral,
27//!     drop_strategy::Zeroize,
28//!     xor::{ReEncrypt, Xor},
29//! };
30//!
31//! // Zeroize on drop (safest - clears memory)
32//! const SECRET_ZEROIZE: Encrypted<Xor<0xAA, Zeroize>, StringLiteral, 5> =
33//!     Encrypted::<Xor<0xAA, Zeroize>, StringLiteral, 5>::new(*b"hello");
34//!
35//! // Re-encrypt on drop (good for frequently accessed secrets)
36//! const SECRET_REENCRYPT: Encrypted<Xor<0xBB, ReEncrypt<0xBB>>, StringLiteral, 6> =
37//!     Encrypted::<Xor<0xBB, ReEncrypt<0xBB>>, StringLiteral, 6>::new(*b"secret");
38//!
39//! // No-op on drop (fastest, but leaves data in memory)
40//! const SECRET_NOOP: Encrypted<Xor<0xCC, Zeroize>, StringLiteral, 4> =
41//!     Encrypted::<Xor<0xCC, Zeroize>, StringLiteral, 4>::new(*b"test");
42//! ```
43//!
44//! ## RC4 Algorithm
45//!
46//! RC4 is a stream cipher with variable-length keys (1-256 bytes).
47//! **Note:** RC4 is cryptographically broken; use only for basic obfuscation:
48//!
49//! ```rust
50//! use const_secret::{
51//!     Encrypted, StringLiteral, ByteArray,
52//!     drop_strategy::Zeroize,
53//!     rc4::{ReEncrypt, Rc4},
54//! };
55//!
56//! const KEY: [u8; 16] = *b"my-secret-key-16";
57//!
58//! // RC4 with zeroize drop strategy
59//! const RC4_SECRET: Encrypted<Rc4<16, Zeroize<[u8; 16]>>, StringLiteral, 6> =
60//!     Encrypted::<Rc4<16, Zeroize<[u8; 16]>>, StringLiteral, 6>::new(*b"rc4sec", KEY);
61//!
62//! // RC4 with re-encrypt drop strategy
63//! const RC4_REENCRYPT: Encrypted<Rc4<16, ReEncrypt<16>>, StringLiteral, 8> =
64//!     Encrypted::<Rc4<16, ReEncrypt<16>>, StringLiteral, 8>::new(*b"rc4data!", KEY);
65//! ```
66//!
67//! ## Usage Modes
68//!
69//! ### `StringLiteral` Mode
70//!
71//! For UTF-8 string data. Returns `&str` on dereference:
72//!
73//! ```rust
74//! use const_secret::{
75//!     Encrypted, StringLiteral,
76//!     drop_strategy::Zeroize,
77//!     xor::Xor,
78//! };
79//!
80//! const API_KEY: Encrypted<Xor<0xAA, Zeroize>, StringLiteral, 34> =
81//!     Encrypted::<Xor<0xAA, Zeroize>, StringLiteral, 34>::new(
82//!         *b"sk-live-1234567890abcdefghijklmnop"
83//!     );
84//!
85//! fn main() {
86//!     let key: &str = &*API_KEY;
87//!     assert_eq!(key, "sk-live-1234567890abcdefghijklmnop");
88//! }
89//! ```
90//!
91//! ### `ByteArray` Mode
92//!
93//! For binary data. Returns `&[u8; N]` on dereference:
94//!
95//! ```rust
96//! use const_secret::{
97//!     Encrypted, ByteArray,
98//!     drop_strategy::Zeroize,
99//!     xor::Xor,
100//! };
101//!
102//! const BINARY_SECRET: Encrypted<Xor<0xBB, Zeroize>, ByteArray, 16> =
103//!     Encrypted::<Xor<0xBB, Zeroize>, ByteArray, 16>::new([
104//!         0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
105//!         0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
106//!     ]);
107//!
108//! fn main() {
109//!     let data: &[u8; 16] = &*BINARY_SECRET;
110//!     assert_eq!(data[0], 0x01);
111//! }
112//! ```
113//!
114//! ## Choosing an Algorithm
115//!
116//! | Algorithm | Speed | Security | Use Case |
117//! |-----------|-------|----------|----------|
118//! | XOR       | Fast  | Basic    | Simple obfuscation, speed critical |
119//! | RC4       | Medium| Broken   | Variable key length, slightly better obfuscation |
120//!
121//! ## Drop Strategies
122//!
123//! | Strategy   | Behavior on Drop | Best For |
124//! |------------|------------------|----------|
125//! | `Zeroize`  | Overwrites with zeros | Maximum security |
126//! | `ReEncrypt`| Re-encrypts data | If you prefer the residue to remain encrypted after using |
127//! | `NoOp`     | Leaves unchanged | Performance critical, non-sensitive |
128//!
129//! # Architecture
130//!
131//! The crate uses a type-level architecture:
132//! - [`Algorithm`]: Trait defining encryption algorithm and associated data
133//! - [`Encrypted<A, M, N>`]: Main struct holding encrypted data
134//! - [`DropStrategy`]: Trait for handling drop behavior
135//! - Mode markers: [`StringLiteral`] and [`ByteArray`]
136
137#![no_std]
138#![cfg_attr(not(debug_assertions), deny(warnings))]
139#![allow(unknown_lints)]
140#![warn(
141    clippy::all,
142    clippy::await_holding_lock,
143    clippy::char_lit_as_u8,
144    clippy::checked_conversions,
145    clippy::dbg_macro,
146    clippy::debug_assert_with_mut_call,
147    clippy::doc_markdown,
148    clippy::empty_enums,
149    clippy::enum_glob_use,
150    clippy::exit,
151    clippy::expl_impl_clone_on_copy,
152    clippy::explicit_deref_methods,
153    clippy::explicit_into_iter_loop,
154    clippy::fallible_impl_from,
155    clippy::filter_map_next,
156    clippy::float_cmp_const,
157    clippy::fn_params_excessive_bools,
158    clippy::if_let_mutex,
159    clippy::imprecise_flops,
160    clippy::inefficient_to_string,
161    clippy::invalid_upcast_comparisons,
162    clippy::large_types_passed_by_value,
163    clippy::let_unit_value,
164    clippy::linkedlist,
165    clippy::lossy_float_literal,
166    clippy::macro_use_imports,
167    clippy::manual_ok_or,
168    clippy::map_flatten,
169    clippy::match_same_arms,
170    clippy::match_wildcard_for_single_variants,
171    clippy::mem_forget,
172    unexpected_cfgs,
173    clippy::missing_errors_doc,
174    clippy::missing_safety_doc,
175    clippy::mut_mut,
176    clippy::mutex_integer,
177    clippy::needless_borrow,
178    clippy::needless_continue,
179    clippy::needless_pass_by_value,
180    clippy::option_option,
181    clippy::path_buf_push_overwrite,
182    clippy::ptr_as_ptr,
183    clippy::ref_option_ref,
184    clippy::rest_pat_in_fully_bound_structs,
185    clippy::same_functions_in_if_condition,
186    clippy::string_add_assign,
187    clippy::string_add,
188    clippy::string_lit_as_bytes,
189    clippy::todo,
190    clippy::trait_duplication_in_bounds,
191    clippy::unimplemented,
192    clippy::unnested_or_patterns,
193    clippy::unused_self,
194    clippy::useless_transmute,
195    clippy::verbose_file_reads,
196    clippy::zero_sized_map_values,
197    future_incompatible,
198    nonstandard_style,
199    rust_2018_idioms
200)]
201
202#[cfg(test)]
203extern crate std;
204
205#[cfg(test)]
206extern crate alloc;
207
208pub mod align;
209pub mod drop_strategy;
210pub mod rc4;
211pub mod xor;
212
213use crate::drop_strategy::DropStrategy;
214use core::{cell::UnsafeCell, fmt, marker::PhantomData, sync::atomic::AtomicU8};
215
216/// Decryption state constants for thread-safe lazy decryption
217pub(crate) const STATE_UNENCRYPTED: u8 = 0;
218pub(crate) const STATE_DECRYPTING: u8 = 1;
219pub(crate) const STATE_DECRYPTED: u8 = 2;
220
221/// A trait that defines an encryption algorithm and its associated types.
222///
223/// This trait is implemented by algorithm types (like [`xor::Xor`]
224/// and [`rc4::Rc4`]) to specify:
225/// - The drop strategy to use when the encrypted data is dropped
226/// - The extra data type that the algorithm needs to store alongside the buffer
227///
228/// The `Extra` associated type allows algorithms to store additional data
229/// (like encryption keys for RC4) within the [`Encrypted`] struct.
230pub trait Algorithm {
231    /// The drop strategy to use when the encrypted data is dropped.
232    type Drop: DropStrategy<Extra = Self::Extra>;
233    /// Additional data stored alongside the encrypted buffer.
234    ///
235    /// For XOR this is `()` (no extra data needed), for RC4 this is the key array.
236    type Extra;
237}
238
239/// Mode marker type indicating the encrypted data should be treated as a UTF-8 string literal.
240///
241/// When used as the `M` type parameter of [`Encrypted<A, M, N>`], dereferencing
242/// returns `&str` instead of `&[u8; N]`.
243///
244/// # Safety
245///
246/// The original plaintext must be valid UTF-8. The encryption algorithm must
247/// preserve the byte values such that decryption produces valid UTF-8.
248pub struct StringLiteral;
249
250/// Mode marker type indicating the encrypted data should be treated as a byte array.
251///
252/// When used as the `M` type parameter of [`Encrypted<A, M, N>`], dereferencing
253/// returns `&[u8; N]` (a reference to the raw byte array).
254pub struct ByteArray;
255
256/// An encrypted container that holds data encrypted at compile time.
257///
258/// This struct stores encrypted data that is decrypted on first access via
259/// the [`Deref`](core::ops::Deref) implementation. The decryption happens
260/// exactly once, after which the plaintext is cached for subsequent accesses.
261///
262/// # Type Parameters
263///
264/// - `A`: The encryption algorithm type implementing [`Algorithm`]
265/// - `M`: The mode marker type ([`StringLiteral`] or [`ByteArray`])
266/// - `N`: The size of the encrypted buffer in bytes
267///
268/// # Thread Safety
269///
270/// The struct is `Sync`, allowing concurrent access from multiple threads.
271/// The first thread to access the data performs the decryption; subsequent
272/// accesses read the already-decrypted buffer.
273///
274/// # Drop Behavior
275///
276/// When dropped, the data is handled according to the algorithm's
277/// [`DropStrategy`]:
278/// - [`Zeroize`](crate::drop_strategy::Zeroize): Overwrites with zeros
279/// - [`ReEncrypt`](crate::xor::ReEncrypt) / [`ReEncrypt`](crate::rc4::ReEncrypt): Re-encrypts
280/// - [`NoOp`](crate::drop_strategy::NoOp): Leaves data unchanged
281///
282/// # Example
283///
284/// ```rust
285/// use const_secret::{
286///     Encrypted, StringLiteral,
287///     drop_strategy::Zeroize,
288///     xor::Xor,
289/// };
290///
291/// const SECRET: Encrypted<Xor<0xAA, Zeroize>, StringLiteral, 5> =
292///     Encrypted::<Xor<0xAA, Zeroize>, StringLiteral, 5>::new(*b"hello");
293///
294/// fn main() {
295///     // Decrypts on first access
296///     let decrypted: &str = &*SECRET;
297///     assert_eq!(decrypted, "hello");
298/// }
299/// ```
300pub struct Encrypted<A: Algorithm, M, const N: usize> {
301    /// The encrypted/decrypted data buffer.
302    ///
303    /// Uses [`UnsafeCell`] for interior mutability to allow decryption on first access.
304    buffer: UnsafeCell<[u8; N]>,
305    /// State of decryption (0=unencrypted, 1=decrypting, 2=decrypted).
306    ///
307    /// Uses atomic operations to ensure thread-safe lazy decryption.
308    /// - `STATE_UNENCRYPTED` (0): Initial state, needs decryption
309    /// - `STATE_DECRYPTING` (1): A thread is currently decrypting
310    /// - `STATE_DECRYPTED` (2): Decryption complete, safe to read
311    decryption_state: AtomicU8,
312    /// Algorithm-specific extra data (e.g., the encryption key for RC4).
313    extra: A::Extra,
314    /// Phantom marker to carry the algorithm and mode type information.
315    _phantom: PhantomData<(A, M)>,
316}
317
318impl<A: Algorithm, M, const N: usize> fmt::Debug for Encrypted<A, M, N> {
319    /// Formats the `Encrypted` struct for debugging.
320    ///
321    /// Note that the actual buffer contents are not displayed for security reasons.
322    /// Only the `decryption_state` is shown. The output uses `finish_non_exhaustive()`
323    /// to indicate there are additional fields not shown.
324    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325        f.debug_struct("Encrypted")
326            .field("decryption_state", &self.decryption_state)
327            .finish_non_exhaustive()
328    }
329}
330
331impl<A: Algorithm, M, const N: usize> Drop for Encrypted<A, M, N> {
332    /// Handles the encrypted data when the struct is dropped.
333    ///
334    /// Applies the algorithm's [`DropStrategy`]
335    /// to the buffer. This may zeroize, re-encrypt, or leave the data unchanged
336    /// depending on the configured strategy.
337    fn drop(&mut self) {
338        // SAFETY: `buffer` is initialized and exclusively borrowed through `&mut self`.
339        let data_ref = unsafe { &mut *self.buffer.get() };
340        A::Drop::drop(data_ref, &self.extra);
341    }
342}
343
344// SAFETY: `Encrypted` is `Sync` because:
345// 1. The 3-state `decryption_state` (AtomicU8) ensures proper synchronization:
346//    - Only one thread can transition from UNENCRYPTED to DECRYPTING
347//    - Other threads spin-wait until state becomes DECRYPTED
348// 2. The thread that wins the race gets exclusive mutable access during decryption
349// 3. After decryption completes (state = DECRYPTED), the buffer is immutable
350// 4. Multiple threads can safely read the stable, decrypted buffer concurrently
351unsafe impl<A: Algorithm, M, const N: usize> Sync for Encrypted<A, M, N>
352where
353    A: Sync,
354    A::Extra: Sync,
355    M: Sync,
356{
357}