Expand description
§Security Hardening Guide
§Overview
The byte-array-ops library provides two distinct operational modes for handling byte arrays, designed to balance security and convenience based on your use case. These modes are mutually exclusive and determined at compile time through feature flags.
§Hardened Mode vs Insecure Mode
§Hardened Mode
Activation: Any sec_harden_* feature flag enabled
Philosophy: Security-first approach that restricts API surface to prevent accidental data leakage and improper sanitization of sensitive data.
What it restricts:
- No
DereftoVec<u8>: Prevents accidental exposure of Vec methods that could transfer ownership or leak data - No
to_vec()method: Prevents ownership transfer that bypasses sanitization - No
Into<Vec<u8>>: Prevents conversion that could leave sensitive data unsanitized truncate()panics: Blocks operations that cause reallocation and potential data remnance in deallocated memory- Display/Debug restricted: In release builds, only shows
ByteArray length: Ninstead of actual byte data
What it provides:
- Explicit methods:
len(),is_empty(),get()- controlled access without Vec exposure - Controlled iteration:
iter()anditer_mut()methods for safe byte access - Debug-build visibility: Full data display in debug builds for development convenience
- Type safety: Strong guarantees that sensitive data won’t leak through unintended pathways
Use cases:
- Cryptographic keys and material
- Passwords, PINs, and secrets
- Authentication tokens and session IDs
- Any data where leakage has security implications
§Insecure Mode
Activation: Default (no hardening features enabled)
Philosophy: Convenience-first approach that provides full Vec<u8> compatibility for general-purpose byte manipulation.
What it provides:
- Full
DereftoVec<u8>: All Vec methods available (push, pop, reserve, capacity, etc.) - Ownership transfer:
to_vec()andInto<Vec<u8>>for seamless conversion - Working
truncate(): Standard Vec::truncate behavior - Full Display/Debug: Complete byte data exposure in all builds
- Maximum compatibility: Drop-in replacement for
Vec<u8>in most scenarios
Why “insecure”:
- User is responsible for proper data sanitization
- Easy to accidentally leak data through logs, Debug output, or error messages
- Reallocation operations can leave sensitive data in freed memory
- No compile-time enforcement of security practices
Use cases:
- General byte manipulation and processing
- Network protocol implementation
- File I/O and serialization
- Performance-critical code where data is not sensitive
- Prototyping and development
§Feature Flag Reference
§Individual Hardening Flags
These flags enable specific security features:
-
sec_harden_zeroize: Memory wiping on drop (planned v0.3.0)- Ensures sensitive data is zeroed when ByteArray goes out of scope
- Prevents data from persisting in memory after use
-
sec_harden_const_time_ops: Constant-time comparisons (planned v0.3.0)- Prevents timing attacks on sensitive comparisons
- Uses the
subtlecrate for constant-time operations
-
sec_harden_memlock: Prevent swapping to disk (planned v0.3.0)- Locks memory pages to prevent swap file leakage
- Platform-specific implementation
-
sec_harden_memencrypt: In-memory encryption (planned v0.3.0)- Encrypts data while in memory
- Combines zeroize and memlock features
§Convenience Tier Flags
These flags activate multiple hardening features at once:
-
sec_basic_hardening:- Activates:
sec_harden_zeroize - Recommended for: Most sensitive data handling
- Activates:
-
sec_enhanced_hardening:- Activates:
sec_basic_hardening+sec_harden_const_time_ops+sec_harden_memlock - Recommended for: Cryptographic material and high-security applications
- Activates:
-
sec_maximum_hardening:- Activates: All hardening features
- Recommended for: Maximum security requirements
§API Differences Between Modes
§Available in Both Modes
These methods work identically in both modes:
use byte_array_ops::ByteArray;
// Construction
let _arr1 = ByteArray::new();
let _arr2 = ByteArray::with_capacity(10);
let _arr3 = ByteArray::from_hex("deadbeef").unwrap();
let _arr4 = ByteArray::from_bin("1010101").unwrap();
let _arr5 = ByteArray::init_zeros(5);
let arr = ByteArray::init_value(0xff, 3);
// Basic access
let _bytes = arr.as_bytes(); // Returns &[u8]
let _byte = arr[0]; // Indexing
let mut arr_mut = arr.clone();
let _mut_bytes = arr_mut.as_mut(); // Mutable slice access
// Type conversions
// From<Vec<u8>>, From<&[u8]>, From<[u8; N]>
let _from_vec: ByteArray = vec![0x01, 0x02].into();
let _from_slice: ByteArray = [0x01, 0x02].as_slice().into();
let _from_array: ByteArray = [0x01, 0x02, 0x03].into();§Hardened Mode Only
Methods that exist only in hardened mode:
arr.len(); // Explicit length access
arr.is_empty(); // Explicit emptiness check
let _byte = arr.get(0); // Checked element access
let _iter = arr.iter(); // Immutable iterator
// let _iter_mut = _arr_mut.iter_mut(); // Mutable iterator (requires hardening features)
// This panics in hardened mode:
// _arr_mut.truncate(2); // unimplemented!()§Insecure Mode Only
Methods and traits that exist only in insecure mode:
use byte_array_ops::ByteArray;
use byte_array_ops::try_hex;
let arr = ByteArray::default();
let _arr2 = arr.to_vec(); // Ownership transfer
let arr = ByteArray::default();
let _v: Vec<u8> = arr.into(); // Into conversion
let mut arr = try_hex!("deadbeef").expect("failed to convert");
arr.truncate(2); // Works normally
// Through Deref to Vec<u8>:
let _len = arr.len(); // Via Deref
let _empty = arr.is_empty(); // Via Deref
let _cap = arr.capacity(); // Via Deref
let _byte = arr.get(0); // Via Deref
let _first = arr.first(); // Via Deref
let _last = arr.last(); // Via Deref
let _iter = arr.iter(); // Via Deref to Vec
// Note: iter_mut() requires DerefMut which is not implemented
// ... all other Vec methods§Migration Between Modes
§Switching from Insecure to Hardened
Breaking changes:
- No more
to_vec()orInto<Vec<u8>>:
use byte_array_ops::ByteArray;
let arr = ByteArray::from_hex("deadbeef").unwrap();
// OLD (insecure):
// let v: Vec<u8> = arr.to_vec();
// let v: Vec<u8> = arr.into();
// NEW (hardened):
// Use as_bytes() for borrowing:
let slice: &[u8] = arr.as_bytes();
// Or iterate and collect:
let v: Vec<u8> = arr.iter().copied().collect();- No more
Derefto Vec:
use byte_array_ops::ByteArray;
// OLD (insecure):
// let cap = arr.capacity(); // Via Deref
// arr.reserve(10); // Via Deref
// NEW (hardened):
// Capacity/reserve not available - plan memory upfront:
let arr = ByteArray::with_capacity(100);- No more
truncate():
use byte_array_ops::ByteArray;
let arr = ByteArray::from_hex("deadbeefcafe").unwrap();
// OLD (insecure):
// arr.truncate(10);
// NEW (hardened):
// Reconstruct or use different approach:
let new_arr: ByteArray = arr.iter()
.take(4)
.copied()
.collect::<Vec<u8>>()
.into();- Display/Debug changes:
use byte_array_ops::ByteArray;
let arr = ByteArray::from_hex("deadbeef").unwrap();
// Both modes support Debug/Display
println!("Array: {:?}", arr);
// In hardened release builds: shows "ByteArray length: 4"
// In hardened debug builds: shows full data
// In insecure mode: always shows full data§Switching from Hardened to Insecure
Generally straightforward - just remove hardening feature flags. Your code will gain access to more methods.
Considerations:
- Review all uses of
truncate()- ensure proper cleanup if sensitive - Audit Display/Debug usage - data will now leak in logs
- Consider if Deref access introduces security risks
- Verify no implicit conversions to Vec expose sensitive data
§Security Considerations
§Data Remnance
Problem: When memory is reallocated (grow, shrink, truncate), old data may remain in freed memory pages.
Hardened approach:
- Blocks operations that cause reallocation (
truncate()panics) - Future: zeroize feature will clear memory on drop
Insecure approach:
- Allows reallocation operations
- User responsible for manual cleanup if needed
§Logging and Debug Output
Problem: Debug output and logging can expose sensitive data to log files, terminal history, or monitoring systems.
Hardened approach:
- Release builds show only
ByteArray length: N - Debug builds show data for development convenience
- Requires explicit action to expose data
Insecure approach:
- Full data always visible in Debug/Display
- Easy to accidentally log sensitive information
§Ownership Transfer
Problem: Converting to Vec<u8> and passing ownership means you lose control over how data is sanitized.
Hardened approach:
- No
to_vec()orInto<Vec<u8>>conversion - Forces borrowing via
as_bytes()or explicit iteration - Maintains control over data lifecycle
Insecure approach:
- Easy conversion to
Vec<u8> - Once converted, no security guarantees
- User must track and sanitize manually
§Iterator Safety
Problem: Mutable iterators can modify data in place without proper cleanup tracking.
Hardened approach:
- Provides
iter()anditer_mut()explicitly - Future: may add Drop impl to zero unconsumed bytes
Insecure approach:
- Access to Vec’s iterators through Deref
- No special safety guarantees
§Implementation Details
§File Structure
src/core/trust/
├── hardened/
│ ├── mod.rs # Module orchestration
│ ├── vec.rs # len, is_empty, get, truncate (panics)
│ ├── iter.rs # iter, iter_mut implementations
│ └── display.rs # Display, Debug (length-only in release)
└── insecure/
├── mod.rs # Module orchestration
├── vec.rs # to_vec, truncate, From, Deref implementations
└── display.rs # Display, Debug (full data always)§Conditional Compilation
The trust module uses mutual exclusion to ensure only one mode compiles:
// Simplified view - see src/lib.rs for actual implementationGuarantees:
- Only one mode compiles at a time
- No runtime overhead from conditional checks
- Clear compile-time errors if methods not available
§Iterator Types
Iterator types (ByteArrayIter, ByteArrayIterMut) are shared between modes:
- Defined in:
core/iter.rs - Used by: Both hardened and insecure modes
- Hardened mode:
iter()methods inhardened/iter.rs - Insecure mode: Access through
Derefto Vec
§Examples
§Hardened Mode Example
use byte_array_ops::ByteArray;
// Enable hardening in Cargo.toml:
// byte-array-ops = { version = "*", features = ["sec_basic_hardening"] }
fn process_key(key_hex: &str) -> Result<(), Box<dyn std::error::Error>> {
let key = ByteArray::from_hex(key_hex)?;
// ✓ Allowed: Explicit access
println!("Key length: {}", key.len());
// ✓ Allowed: Iteration
for byte in key.iter() {
let _ = byte; // process byte
}
// ✓ Allowed: Borrowing
let slice: &[u8] = key.as_bytes();
let _ = slice;
// ✗ Not allowed in hardened mode (would panic):
// let mut key = key;
// key.truncate(16);
// ✗ Not allowed: No such method in hardened mode
// let v = key.to_vec();
// ✗ Not allowed: No Deref to Vec in hardened mode
// let cap = key.capacity();
Ok(())
}
// Test it works§Insecure Mode Example
use byte_array_ops::ByteArray;
// Default mode (no features needed)
fn process_data(data_hex: &str) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut arr = ByteArray::from_hex(data_hex)?;
// ✓ All operations available
if arr.len() > 4 {
arr.truncate(4);
}
// ✓ Deref access
println!("Capacity: {}", arr.capacity());
// ✓ Ownership transfer
let vec: Vec<u8> = arr.to_vec();
Ok(vec)
}
// Test it works§Future Enhancements
§Version 0.3.0 (Planned)
- Zeroize on drop: Automatic memory clearing when ByteArray is dropped
- Constant-time operations: Timing-attack resistant comparisons
- Memory locking: Prevent swapping sensitive data to disk
- Memory encryption: Encrypt data while in memory
§Under Consideration
- Drop impl for iterators: Zero unconsumed bytes when iterator is dropped
- SecureReallocationProvider trait: Safe reallocation with guaranteed cleanup
- Audit logging: Optional compile-time tracking of sensitive data access
- Type-level security markers: Compile-time distinction between sensitive and non-sensitive ByteArrays
§Best Practices
-
Choose the right mode for your use case:
- Sensitive data → Hardened mode
- General processing → Insecure mode
-
Plan capacity upfront in hardened mode:
- Use
ByteArray::with_capacity()to avoid reallocation
- Use
-
Avoid Debug/Display with sensitive data:
- Even in hardened mode, debug builds show data
- Never log sensitive ByteArrays in production
-
Audit conversion points:
- Review all
as_bytes()calls for potential leakage - Check iterator usage for proper cleanup
- Review all
-
Test both modes if supporting both:
- CI should test with and without hardening features
- Document which mode your library expects
-
Document security requirements:
- Tell users which mode to use
- Explain implications of choosing insecure mode