obfuse 1.0.0

Compile-time string obfuscation with runtime decryption and secure memory wiping
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
# obfuse-rs

Compile-time string encryption for Rust with runtime decryption and secure memory wiping.

**🔒 Now with polymorphic decryption by default** - Each string gets unique inline decryption code combining AES-256-GCM with random transformations for maximum anti-reversing protection.

## Demo

### Control Flow Obfuscation in IDA Pro

Obfuscated binaries produce complex control flow graphs that resist static analysis:

![IDA Pro Control Flow Graph](docs/images/ida.png)

### Macro Expansion

The `obfuse!` macro generates unique inline decryption code for each string at compile time:

![Macro Expansion](docs/images/expand.gif)

## Quick Start

```toml
[dependencies]
obfuse = "0.1"
```

```rust
use obfuse::obfuse;

fn main() {
    // String encrypted at compile time with AES-256-GCM + unique polymorphic layers
    let api_key = obfuse!("sk_live_abc123_secret");
    
    // Decrypted only when accessed
    println!("API Key: {}", api_key.as_str());
    
    // Memory securely wiped on drop
}
```

> **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
- **Polymorphic decryption** (default, **recommended**): Each string gets unique inline decryption code with combined encryption
- **Combined encryption layers** (polymorphic mode):
  - **Layer 1**: Strong AEAD encryption (AES-256-GCM by default)
  - **Layer 2**: Unique polymorphic transformations per string (XOR, ADD/SUB, bit rotations)
  - **Layer 3**: Runtime key derivation (keys computed from constants, not stored statically)
- **Multiple encryption algorithms**: Choose via Cargo features
  - `aes-256-gcm` (default) - AES-256 in GCM mode with polymorphic layers
  - `aes-128-gcm` - AES-128 in GCM mode with polymorphic layers
  - `chacha20-poly1305` - ChaCha20-Poly1305 AEAD with polymorphic layers
  - `xor` - Simple XOR with MBA obfuscation (fast, less secure)
- **MBA (Mixed Boolean-Arithmetic) transformations**: XOR decryption uses mathematically equivalent but complex expressions to resist decompiler simplification (e.g., IDA's Hex-Rays)
- **Proper error handling**: No panics unless you use `.as_str()` - use `try_as_str()` for Result-based error handling
- **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

## Encryption Modes

### Default Mode: Polymorphic + AES-256-GCM (Recommended)

The default configuration provides **maximum obfuscation** through combined encryption:

```toml
[dependencies]
obfuse = "0.1"  # Uses aes-256-gcm + polymorphic by default
```

**Returns:** `ObfuseStrInline` - each string has unique inline decryption code

**Security layers:**
1. **AES-256-GCM encryption**: Industry-standard authenticated encryption
2. **Polymorphic transformations**: 2-4 random layers per string (XOR, ADD/SUB, rotations)
3. **Runtime key derivation**: Keys computed from constants, not stored statically
4. **No central decrypt function**: Each string has unique inline decryption code

**Benefits:**
- ✅ Strongest anti-reversing protection
- ✅ Defense in depth (two independent encryption layers)
- ✅ Unique code per string prevents pattern analysis
- ✅ Even if one layer is broken, the other provides protection
- ✅ Proper error propagation (no unwrap/expect in generated code)

**Trade-offs:**
- Slightly larger binary (~150 bytes per string vs ~68 bytes traditional)
- Minimal runtime overhead (key computation is fast)

### Traditional Mode: AES-256-GCM Only

For projects that prioritize smaller binary size:

```toml
[dependencies]
obfuse = { version = "0.1", default-features = false, features = ["aes-256-gcm"] }
```

**Returns:** `ObfuseStr` - traditional centralized decryption

**When to use:**
- Binary size is critical
- Basic obfuscation is sufficient
- Not targeting experienced reverse engineers

### Other Encryption Options

```toml
# AES-128-GCM with polymorphic (smaller key, still very secure)
obfuse = { version = "0.1", default-features = false, features = ["aes-128-gcm", "polymorphic"] }

# ChaCha20-Poly1305 with polymorphic (best for ARM/mobile)
obfuse = { version = "0.1", default-features = false, features = ["chacha20-poly1305", "polymorphic"] }

# XOR with MBA (fastest, suitable for obfuscation only)
obfuse = { version = "0.1", default-features = false, features = ["xor"] }
```

## Recommendations by Use Case

| Use Case | Recommended Configuration | Return Type | Why |
|----------|---------------------------|-------------|-----|
| **Production software** | Default (`aes-256-gcm` + `polymorphic`) | `ObfuseStrInline` | Maximum protection, worth the small size increase |
| **Mobile/embedded** | `chacha20-poly1305` + `polymorphic` | `ObfuseStrInline` | ChaCha20 is faster on ARM processors |
| **Size-critical** | `aes-256-gcm` only (no polymorphic) | `ObfuseStr` | Smallest per-string overhead |
| **High-performance** | `xor` with MBA | `ObfuseStr` | Fastest encryption/decryption |
| **Maximum security** | Default + deterministic seed for CI | `ObfuseStrInline` | Reproducible builds with strong protection |

## Binary Size Impact

Adding `obfuse` to your project has minimal overhead:

| Configuration | Library Overhead | Per-String Overhead | Notes |
|---------------|------------------|---------------------|-------|
| Default (AES-256 + polymorphic) | ~27 KB | ~150 bytes | **Recommended** - Maximum security |
| Traditional (AES-256 only) | ~27 KB | ~68 bytes | Smaller, but less secure |
| XOR with MBA | ~5 KB | ~40 bytes | Fastest, obfuscation only |

**Breakdown (default mode):**
- **Library overhead**: ~27 KB (one-time cost for crypto + zeroize)
- **Per-string overhead**: ~150 bytes (inline decryption code + encrypted data)

**For 100 strings:**
- Traditional: 27 KB + (100 × 68 bytes) = ~34 KB
- Polymorphic (default): 27 KB + (100 × 150 bytes) = ~42 KB
- **Extra cost**: 8 KB for significantly stronger protection

### Performance

| Operation | Time |
|-----------|------|
| First access (decryption) | ~500 ns |
| Cached access | ~10 ns |
| Plain string access | ~1 ns |

Decryption is lazy and cached - subsequent accesses are nearly free.


## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
obfuse = "0.1"  # Default: aes-256-gcm + polymorphic (recommended)
```

This gives you **maximum security** with:
- AES-256-GCM authenticated encryption
- Unique polymorphic transformations per string
- Runtime key derivation
- No central decryption point
- Proper error propagation

### Customizing Encryption Options

If you need different configurations:

```toml
# Traditional mode (smaller binary, less secure)
[dependencies]
obfuse = { version = "0.1", default-features = false, features = ["aes-256-gcm"] }

# AES-128 with polymorphic (good balance)
[dependencies]
obfuse = { version = "0.1", default-features = false, features = ["aes-128-gcm", "polymorphic"] }

# ChaCha20 with polymorphic (best for ARM/mobile)
[dependencies]
obfuse = { version = "0.1", default-features = false, features = ["chacha20-poly1305", "polymorphic"] }

# XOR only (fastest, obfuscation only)
[dependencies]
obfuse = { version = "0.1", default-features = false, features = ["xor"] }
```

### Understanding Polymorphic Mode (Enabled by Default)

Polymorphic mode is **now enabled by default** because it provides significantly stronger anti-reversing protection with minimal overhead.

**What it does:**
1. **Layer 1**: Encrypts with AES-256-GCM (industry-standard AEAD)
2. **Layer 2**: Adds 2-4 random transformation layers per string
3. **Layer 3**: Derives keys at runtime from constants

**Why it's better:**
- Each string has **unique decryption code** (not a shared function)
- Reverse engineers must analyze **each string individually**
- **Defense in depth**: Even if one layer is broken, the other protects
- Combines strong encryption (AES) with unique transformations
- **No panics in generated code**: Errors propagate properly via `Result`

**To disable polymorphic and use traditional mode only:**
```toml
[dependencies]
obfuse = { version = "0.1", default-features = false, features = ["aes-256-gcm"] }
```

## Usage

### Basic Usage (Polymorphic Mode - Default)

```rust
use obfuse::obfuse;

fn main() {
    // Returns ObfuseStrInline with unique inline decryption code
    let secret = obfuse!("my secret API key");

    // Decrypted only when accessed (may panic on error)
    println!("Secret: {}", secret.as_str());

    // Memory is securely wiped when `secret` goes out of scope
}
```

### Error Handling (Recommended)

```rust
use obfuse::{obfuse, ObfuseError};

fn main() -> Result<(), ObfuseError> {
    let secret = obfuse!("sensitive data");

    // Use try_as_str() for proper error handling - no panics!
    match secret.try_as_str() {
        Ok(s) => println!("Secret: {s}"),
        Err(ObfuseError::InvalidUtf8(e)) => {
            eprintln!("Invalid UTF-8: {e}");
        }
        Err(e) => {
            eprintln!("Decryption error: {e}");
        }
    }

    Ok(())
}
```

Or with `?` operator:

```rust
use obfuse::{obfuse, ObfuseError};

fn get_secret() -> Result<String, ObfuseError> {
    let secret = obfuse!("my secret");
    Ok(secret.try_as_str()?.to_string())
}
```

### Traditional Mode Usage

```rust
use obfuse::{obfuse, ObfuseStr};

fn main() {
    // Returns ObfuseStr when polymorphic is disabled
    let secret: ObfuseStr = obfuse!("database password");

    // Use the decrypted string
    connect_to_database(secret.as_str());

    // `secret` is automatically zeroed on drop
}
```

### With Explicit Type Annotation

```rust
use obfuse::obfuse;

fn main() {
    // Type inference works for both modes
    let secret = obfuse!("my secret");
    
    // Explicit type if needed (default mode returns ObfuseStrInline)
    let another: _ = obfuse!("another secret");
    
    println!("{}", secret.as_str());
    println!("{}", another.as_str());
}
```

### Lazy Decryption

Both `ObfuseStrInline` and `ObfuseStr` decrypt lazily:

```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
}
```

## 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

## MBA (Mixed Boolean-Arithmetic) Transformations

When using the `xor` feature, decryption logic is obfuscated using MBA transformations to resist decompiler simplification.

### What are MBA Transformations?

MBA transformations replace simple operations with mathematically equivalent but complex expressions. For example:

```text
Simple XOR:        a ^ b
MBA equivalent:    (a | b) - (a & b)
With noise:        ((a | b) + D1 - D1) - ((a & b) + D2 - D2) + (D3 ^ D3)
```

### Why Use MBA?

Decompilers like IDA's Hex-Rays are excellent at recognizing and simplifying straightforward operations. MBA transformations:

- **Resist pattern matching**: The complex expressions don't match known simplification patterns
- **Expand simple operations**: A single XOR becomes many lines of arithmetic/logic
- **Include noise operations**: Dummy constants that cancel out add visual complexity
- **Combine Boolean and arithmetic**: Mixing `AND`, `OR`, `XOR` with `+`, `-`, `*` prevents easy reduction

### Example Decompiler Output

Without MBA, a simple decryption loop might decompile as:
```c
for (i = 0; i < len; i++)
    plaintext[i] = ciphertext[i] ^ key[i % 32];
```

With MBA transformations, the same logic becomes dozens of lines of convoluted operations, making reverse engineering significantly more time-consuming.

## 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?

| Use Case | Recommended |
|----------|-------------|
| 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. **Use error handling**: Prefer `try_as_str()` over `as_str()` to avoid panics
2. **Minimize lifetime**: Keep obfuscated strings in scope only while needed
3. **Avoid cloning**: Don't clone decrypted strings unnecessarily
4. **Use strong algorithms**: Default (`aes-256-gcm` + `polymorphic`) is recommended
5. **Defense in depth**: Use as one layer of protection, not the only one

## API Reference

### `obfuse!` Macro

```rust
// Random key (production) - Default returns ObfuseStrInline
obfuse!("string literal") -> ObfuseStrInline  // with polymorphic (default)
obfuse!("string literal") -> ObfuseStr        // without polymorphic

// Deterministic key (testing/CI) - Same return types
obfuse!("string literal", seed = "your_seed") -> ObfuseStrInline  // or ObfuseStr
```

Encrypts a string literal at compile time.

- **Without seed**: Random key each compile (non-reproducible)
- **With seed**: Deterministic key derived from seed (reproducible)
- **Return type**: `ObfuseStrInline` (default with polymorphic) or `ObfuseStr` (traditional mode)

### `ObfuseStrInline` Type (Polymorphic Mode - Default)

```rust
impl ObfuseStrInline {
    /// Returns the decrypted string, decrypting on first access.
    /// Panics on error - use try_as_str() for error handling.
    pub fn as_str(&self) -> &str;

    /// Fallible version - returns Result instead of panicking.
    /// RECOMMENDED for all production code.
    pub fn try_as_str(&self) -> Result<&str, ObfuseError>;

    /// Returns the decrypted string as bytes.
    /// Panics on error - use try_as_bytes() for error handling.
    pub fn as_bytes(&self) -> &[u8];

    /// Fallible version of as_bytes().
    /// RECOMMENDED for all production code.
    pub fn try_as_bytes(&self) -> Result<&[u8], ObfuseError>;

    /// 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<(), ObfuseError>;
}

impl Deref for ObfuseStrInline {
    type Target = str;
    fn deref(&self) -> &str; // Triggers decryption, panics on error
}

// Note: ObfuseStrInline does NOT implement Drop with zeroing
// because it contains a Result-returning closure
```

### `ObfuseStr` Type (Traditional Mode)

```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, ObfuseError>;

    /// 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], ObfuseError>;

    /// 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<(), ObfuseError>;

    /// 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
}
```

### `ObfuseError` Type

```rust
/// Errors that can occur during decryption
#[derive(Debug)]
pub enum ObfuseError {
    /// 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 ObfuseError { /* ... */ }
impl std::error::Error for ObfuseError { /* ... */ }
```

## 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