Secure types for handling sensitive data in Rust
Provides SecretString and SecretBytes for safely managing passwords, API keys, cryptographic keys, and other sensitive information with automatic memory cleanup and protection against accidental exposure.
- 🔒 Automatic memory cleanup: Uses
zeroizeto clear sensitive data from memory on Drop - 🎭 Masked display: Debug and Display implementations show
***instead of actual values - 🔐 Controlled access: Actual values accessible only through explicit
expose()method - ⏱️ Timing-attack resistant: Optional constant-time comparison to prevent timing attacks
- 🪶 Lightweight: Minimal dependencies with optional features
use saferet::{SecretString, SecretBytes};
// SecretString for text-based secrets
let api_key = SecretString::new("sk_live_abc123");
println!("{:?}", api_key); // Output: SecretString(***)
// Access the actual value when needed
let header = format!("Bearer {}", api_key.expose());
// SecretBytes for binary secrets
let crypto_key = SecretBytes::new(vec![0x01, 0x02, 0x03, 0x04]);
println!("{:?}", crypto_key); // Output: SecretBytes(***)
// Constant-time comparison (enabled by default)
let password1 = SecretString::new("secret");
let password2 = SecretString::new("secret");
assert_eq!(password1, password2); // Uses constant-time comparisonAdd to your Cargo.toml:
[dependencies]
saferet = "0.1"| Feature | Description | Default |
|---|---|---|
constant-time-eq |
Timing-attack resistant comparison using subtle |
✅ Enabled |
serde |
Serialize/Deserialize support | ✅ Enabled |
Minimal configuration (zeroize only):
[dependencies]
saferet = { version = "0.1", default-features = false }Custom features:
[dependencies]
# Only constant-time comparison, no serde
saferet = { version = "0.1", default-features = false, features = ["constant-time-eq"] }
# Only serde support, no constant-time comparison
saferet = { version = "0.1", default-features = false, features = ["serde"] }For text-based sensitive data like passwords, API keys, and authentication tokens.
use saferet::SecretString;
let password = SecretString::new("my-secret-password");
// Masked in logs and error messages
println!("{}", password); // Output: ***
println!("{:?}", password); // Output: SecretString(***)
// Explicit access when needed
if password.expose() == "my-secret-password" {
// Use password.expose() carefully
}
// Works with various string types
let from_string: SecretString = String::from("key").into();
let from_str: SecretString = "key".into();
let parsed: SecretString = "key".parse().unwrap();For binary sensitive data like cryptographic keys, hashes, and binary tokens.
use saferet::SecretBytes;
let key = SecretBytes::new(vec![0x01, 0x02, 0x03, 0x04]);
// Masked in logs and error messages
println!("{}", key); // Output: ***
println!("{:?}", key); // Output: SecretBytes(***)
// Explicit access when needed
let key_slice: &[u8] = key.expose();
// or using AsRef
let key_slice: &[u8] = key.as_ref();
// Works with various byte types
let from_vec: SecretBytes = vec![0x01, 0x02].into();
let from_slice: SecretBytes = [0x03, 0x04].as_ref().into();- Memory cleanup: Sensitive data is zeroized when dropped
- Accidental exposure prevention: Debug/Display never show actual values
- Timing-attack resistance: Comparison operations run in constant time (with
constant-time-eqfeature) - Explicit access control: Values only accessible through
expose()method
- Intentional exposure: If you call
expose()and log/print the result, the value will be exposed - Memory copies: String/Vec reallocation may leave copies at old memory locations
- Swap memory: OS may swap memory to disk before zeroization
- Core dumps: Process crashes may dump memory to disk
- Side-channel attacks: Does not protect against advanced attacks like speculative execution
Best practices:
- Minimize calls to
expose() - Never include
expose()results in logs, error messages, or debug output - Use
constant-time-eqfeature for cryptographic comparisons - Consider platform-specific secure memory allocation for highly sensitive data
With the serde feature (enabled by default), both types support serialization:
use saferet::SecretString;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct Config {
#[serde(default)]
api_key: SecretString,
}
// Serialization includes the actual value
// ⚠️ Be careful when serializing to logs or untrusted destinations
let config = Config {
api_key: SecretString::new("secret-key"),
};
let json = serde_json::to_string(&config).unwrap();
// json contains the actual "secret-key" valueSecurity note: Serialization bypasses the masking protection. Only serialize when absolutely necessary and ensure the destination is secure.
Uses constant-time comparison via the subtle crate to prevent timing attacks:
let secret1 = SecretString::new("password");
let secret2 = SecretString::new("password");
let secret3 = SecretString::new("different");
assert_eq!(secret1, secret2); // Constant-time comparison
assert_ne!(secret1, secret3); // Constant-time comparisonUses standard PartialEq implementation (faster but potentially vulnerable to timing attacks):
[dependencies]
saferet = { version = "0.1", default-features = false }See the examples/ directory:
cargo run --example basic_usageRun all tests with all feature combinations:
# All features (default)
cargo test
# No features
cargo test --no-default-features
# Individual features
cargo test --no-default-features --features serde
cargo test --no-default-features --features constant-time-eq- API Keys: Store API keys securely without risking exposure in logs
- Passwords: Handle user passwords with automatic cleanup
- OAuth Tokens: Manage authentication tokens safely
- Database Credentials: Store connection strings and passwords
- Cryptographic Keys: Handle encryption keys with timing-attack protection
- JWT Secrets: Manage signing keys for JSON Web Tokens
| Crate | Zeroize | Masked Display | Constant-time Eq | Type Safety |
|---|---|---|---|---|
saferet |
✅ | ✅ | ✅ (optional) | String + Bytes |
secrecy |
✅ | ✅ | ❌ | Generic |
zeroize |
✅ | ❌ | ❌ | Generic |
subtle |
❌ | ❌ | ✅ | Primitives |
saferet combines the best features in an easy-to-use package specifically designed for string and byte secrets.
Licensed under MIT license (LICENSE or http://opensource.org/licenses/MIT).
Contributions are welcome! Please feel free to submit a Pull Request.