# obfuse-rs
Compile-time string encryption for Rust with runtime decryption and secure memory wiping.
> **Security Notice**: This library provides **string obfuscation**, not military-grade encryption. The encryption key is embedded in the binary alongside the ciphertext. A determined attacker with access to your binary can extract both.
>
> **Appropriate uses:**
> - Preventing casual inspection of binaries (`strings` command, hex editors)
> - Stopping automated string extraction tools
> - Basic protection against unsophisticated reverse engineering
>
> **NOT appropriate for:**
> - Protecting highly sensitive secrets (use proper secrets management)
> - Compliance requirements (PCI-DSS, HIPAA, SOC2, etc.)
> - Scenarios where key extraction would be catastrophic
## Features
- **Compile-time encryption**: Strings are encrypted during compilation, never stored in plaintext in binaries
- **Multiple encryption algorithms**: Choose via Cargo features
- `aes-256-gcm` (default) - AES-256 in GCM mode
- `aes-128-gcm` - AES-128 in GCM mode
- `chacha20-poly1305` - ChaCha20-Poly1305 AEAD
- `xor` - Simple XOR (fast, less secure, good for obfuscation)
- **Secure memory handling**: Volatile zeroing of sensitive data on drop
- **Zero-copy decryption**: Decrypt only when accessed
- **No runtime dependencies**: Encryption happens at compile time
## Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
obfuse = "0.1"
```
### Selecting Encryption Algorithm
By default, `aes-256-gcm` is used. To use a different algorithm:
```toml
# Use AES-128
[dependencies]
obfuse = { version = "0.1", default-features = false, features = ["aes-128-gcm"] }
# Use ChaCha20-Poly1305
[dependencies]
obfuse = { version = "0.1", default-features = false, features = ["chacha20-poly1305"] }
# Use XOR (fast obfuscation, not cryptographically secure)
[dependencies]
obfuse = { version = "0.1", default-features = false, features = ["xor"] }
```
## Usage
### Basic Usage
```rust
use obfuse::obfuse;
fn main() {
// String is encrypted at compile time
let secret = obfuse!("my secret API key");
// Decrypted only when accessed
println!("Secret: {}", secret.as_str());
// Memory is securely wiped when `secret` goes out of scope
}
```
### With Explicit Type
```rust
use obfuse::{obfuse, ObfuseStr};
fn main() {
let secret: ObfuseStr = obfuse!("database password");
// Use the decrypted string
connect_to_database(secret.as_str());
// `secret` is automatically zeroed on drop
}
```
### Lazy Decryption
```rust
use obfuse::obfuse;
fn main() {
let secret = obfuse!("sensitive data");
// String remains encrypted until first access
if should_use_secret() {
// Decryption happens here
use_secret(secret.as_str());
}
// If condition is false, string is never decrypted
}
```
### Error Handling
For defensive programming, use the fallible API:
```rust
use obfuse::{obfuse, ObfuseStrError};
fn main() {
let secret = obfuse!("sensitive data");
// Fallible decryption - recommended for critical code paths
match secret.try_as_str() {
Ok(s) => println!("Secret: {s}"),
Err(ObfuseStrError::AllocationFailed) => {
eprintln!("Out of memory during decryption");
}
Err(ObfuseStrError::AuthenticationFailed) => {
eprintln!("Decryption failed - binary may be corrupted");
}
Err(ObfuseStrError::InvalidUtf8(e)) => {
eprintln!("Invalid UTF-8: {e}");
}
}
}
```
Or with `?` operator:
```rust
use obfuse::{obfuse, ObfuseStrError};
fn get_secret() -> Result<String, ObfuseStrError> {
let secret = obfuse!("my secret");
Ok(secret.try_as_str()?.to_string())
}
```
## How It Works
1. **Compile Time**: The `obfuse!` macro:
- Generates a random encryption key and nonce
- Encrypts the string literal using the selected algorithm
- Embeds encrypted bytes, key, and nonce in the binary
2. **Runtime**: The `ObfuseStr` type:
- Stores encrypted data until accessed
- Decrypts on first call to `as_str()` or `Deref`
- Caches decrypted value for subsequent accesses
3. **Drop**: When `ObfuseStr` is dropped:
- Uses `std::ptr::write_volatile` to zero all sensitive memory
- Zeros: encryption key, nonce, and decrypted plaintext
- Prevents compiler from optimizing away the zeroing
## Build Modes: Random vs Deterministic
This library supports two build modes for different use cases:
### Default: Random Key (Recommended for Production)
```rust
// Random key generated each compile - different binary every build
let secret = obfuse!("my secret");
println!("{}", secret.as_str()); // Auto-decrypts
```
```
Build 1: key = [0xab, 0xcd, ...] (random)
Build 2: key = [0x12, 0x34, ...] (different random)
Build 3: key = [0x9f, 0xe2, ...] (different random)
```
**Benefits**:
- Each build produces unique encryption
- Harder for attackers to create universal decryption tools
- Best obfuscation for production binaries
### With Seed: Deterministic Key (For Testing/CI)
```rust
// Same seed = same key = reproducible output
let secret = obfuse!("my secret", seed = "test_seed_123");
println!("{}", secret.as_str()); // Auto-decrypts (same as random mode)
```
```
Build 1 (seed="test"): key = [0xaa, 0xbb, ...] (deterministic)
Build 2 (seed="test"): key = [0xaa, 0xbb, ...] (same!)
Build 3 (seed="prod"): key = [0xcc, 0xdd, ...] (different seed = different key)
```
**Benefits**:
- Reproducible builds for CI/CD pipelines
- Testable encrypted output
- Debugging with known encryption state
### Which Mode Should You Use?
| Production builds | `obfuse!("...")` (random) |
| Unit tests | `obfuse!("...", seed = "test")` |
| CI/CD pipelines | `obfuse!("...", seed = "ci")` |
| Debugging encryption issues | `obfuse!("...", seed = "debug")` |
### Important: Both Modes Are Obfuscation
```
┌─────────────────────────────────────────────────────┐
│ Your Binary (Both Modes) │
├─────────────────────────────────────────────────────┤
│ Encrypted Data: [0x4a, 0x7f, 0x2c, ...] │
│ Encryption Key: [0xab, 0xcd, 0xef, ...] ← HERE │
│ Nonce: [0x11, 0x22, 0x33, ...] │
└─────────────────────────────────────────────────────┘
Key is ALWAYS embedded in binary
This is OBFUSCATION, not real encryption
```
For real secret protection, use runtime secrets management (environment variables, Vault, AWS Secrets Manager).
## Security Considerations
### What This Protects Against
- Static binary analysis (strings command, hex editors)
- Simple memory dumps of unaccessed secrets
- Accidental logging of encrypted values
### What This Does NOT Protect Against
- Runtime memory inspection while string is in use
- Sophisticated reverse engineering
- Side-channel attacks
- Compromised systems with debugging access
### Best Practices
1. **Minimize lifetime**: Keep `ObfuseStr` in scope only while needed
2. **Avoid cloning**: Don't clone decrypted strings unnecessarily
3. **Use strong algorithms**: Prefer `aes-256-gcm` or `chacha20-poly1305` for real security
4. **Defense in depth**: Use as one layer of protection, not the only one
## API Reference
### `obfuse!` Macro
```rust
// Random key (production)
obfuse!("string literal") -> ObfuseStr
// Deterministic key (testing/CI)
obfuse!("string literal", seed = "your_seed") -> ObfuseStr
```
Encrypts a string literal at compile time.
- **Without seed**: Random key each compile (non-reproducible)
- **With seed**: Deterministic key derived from seed (reproducible)
### `ObfuseStr` Type
```rust
impl ObfuseStr {
/// Returns the decrypted string, decrypting on first access.
/// Panics with detailed message on error.
pub fn as_str(&self) -> &str;
/// Fallible version - returns Result instead of panicking.
/// Recommended for critical code paths.
pub fn try_as_str(&self) -> Result<&str, ObfuseStrError>;
/// Returns the decrypted string as bytes.
pub fn as_bytes(&self) -> &[u8];
/// Fallible version of as_bytes().
pub fn try_as_bytes(&self) -> Result<&[u8], ObfuseStrError>;
/// Returns true if the string has been decrypted.
pub fn is_decrypted(&self) -> bool;
/// Pre-decrypt without returning the value.
pub fn try_decrypt(&self) -> Result<(), ObfuseStrError>;
/// Manually zero memory (also happens automatically on drop).
pub fn zeroize(&mut self);
}
impl Deref for ObfuseStr {
type Target = str;
fn deref(&self) -> &str; // Triggers decryption, panics on error
}
impl Drop for ObfuseStr {
fn drop(&mut self); // Volatile zeroing of all sensitive data
}
```
### `ObfuseStrError` Type
```rust
/// Errors that can occur during ObfuseStr decryption
#[derive(Debug)]
pub enum ObfuseStrError {
/// Memory allocation failed during decryption (OOM)
AllocationFailed,
/// AEAD authentication tag verification failed.
/// Indicates ciphertext tampering or algorithm mismatch.
AuthenticationFailed,
/// Decrypted bytes are not valid UTF-8
InvalidUtf8(std::str::Utf8Error),
}
impl std::fmt::Display for ObfuseStrError { /* ... */ }
impl std::error::Error for ObfuseStrError { /* ... */ }
```
## Project Structure
```
obfuse-rs/
├── Cargo.toml # Workspace configuration
├── README.md
├── obfuse/ # Main library crate (re-exports)
│ ├── Cargo.toml
│ └── src/lib.rs
├── obfuse-macros/ # Procedural macro crate
│ ├── Cargo.toml
│ └── src/lib.rs
└── obfuse-core/ # Core encryption/decryption logic
├── Cargo.toml
└── src/
├── lib.rs
├── obfuse_str.rs # ObfuseStr type implementation
├── aes.rs # AES encryption
├── chacha.rs # ChaCha20 encryption
└── xor.rs # XOR encryption
```
## Building
```bash
# Build with default features (AES-256-GCM)
cargo build
# Build with specific algorithm
cargo build --no-default-features --features chacha20-poly1305
# Run tests
cargo test
# Run tests for specific algorithm
cargo test --no-default-features --features aes-128-gcm
```
## License
MIT License - see [LICENSE](LICENSE) for details.
## Contributing
Contributions welcome! Please read the contributing guidelines first.
## Acknowledgments
- [aes-gcm](https://crates.io/crates/aes-gcm) - AES-GCM implementation
- [chacha20poly1305](https://crates.io/crates/chacha20poly1305) - ChaCha20-Poly1305 implementation
- [zeroize](https://crates.io/crates/zeroize) - Secure memory zeroing patterns