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}