| Crates.io | api-keys-simplified |
| lib.rs | api-keys-simplified |
| version | 0.4.0 |
| created_at | 2025-12-06 20:14:13.100331+00 |
| updated_at | 2025-12-20 08:14:09.357553+00 |
| description | Secure API key generation and validation library |
| homepage | |
| repository | https://github.com/gpmcp/api-keys-simplified |
| max_upload_size | |
| id | 1970687 |
| size | 184,619 |
A secure, Rust library for generating and validating API keys with built-in security best practices.
use api_keys_simplified::{ApiKeyManagerV0, Environment, ExposeSecret, KeyStatus, SecureString};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Initialize with checksum (out of the box DoS protection)
let manager = ApiKeyManagerV0::init_default_config("gpmcp_sk")?;
// 2. Generate a new API key
let api_key = manager.generate(Environment::production())?;
// 3. Show key to user ONCE (they must save it)
println!("API Key: {}", api_key.key().expose_secret());
// 4. Store both hash AND salt in database (required for hash verification)
let hash_data = api_key.expose_hash();
database::save_user_key(user_id, hash_data.hash(), hash_data.salt())?;
// 5. Later: verify an incoming key (checksum validated first!)
let provided_key_str = request.headers().get("Authorization")?.replace("Bearer ", "");
let provided_key = SecureString::from(provided_key_str);
let stored_hash = database::get_user_key_hash(user_id)?;
match manager.verify(&provided_key, &stored_hash)? {
KeyStatus::Valid => {
// Key is valid - grant access
handle_request(request)
}
KeyStatus::Invalid => {
// Key is invalid, expired, or revoked
Err("Invalid API key")
}
}
}
prefix[-version]-environment-random_data[.expiry][.checksum]
│ │ │ │ │ │
│ │ │ │ │ └─ BLAKE3 (recommended, 16 hex chars)
│ │ │ │ └─────────── Optional: 11-char base64url timestamp
│ │ │ └─────────────────── Base64URL (192 bits default)
│ │ └──────────────────────────────── dev/test/staging/live
│ └──────────────────────────────────────────── Optional: vN (v1, v2, etc.)
└────────────────────────────────────────────────── User-defined (e.g., acme_sk, stripe_pk)
Examples:
gpmcp_sk-live-Xf8kP2qW9zLmN4vC8aH5tJw1bQmK3rN9.a1b2c3d4e5f6g7h8gpmcp_sk-v1-live-Xf8kP2qW9zLmN4vC8aH5tJw1bQmK3rN9.a1b2c3d4e5f6g7h8acme_api-dev-Rt7jK3pV8wNmQ2uD4fG6hLk8nPqS2uW5.AAAAAGldxGE.9f8e7d6c5b4a3210api-v2-live-Rt7jK3pV8wNmQ2uD4fG6hLk8nPqS2uW5.AAAAAGldxGE.9f8e7d6c5b4a3210Checksum provides:
Expiration provides:
Versioning provides:
Common API key security mistakes:
❌ Weak random number generators → Predictable keys
❌ Plaintext storage → Database breach = total compromise
❌ Vulnerable hashing (MD5, SHA1) → Easy to crack
❌ Timing-vulnerable comparisons → Leaks key information
❌ Keys lingering in memory → Core dumps expose secrets
This library solves all of these with secure defaults and minimal code.
getrandom cratesubtle crate (timing-attack resistant)SecureString clears memory on drop via zeroize crateDebug impl redacts keysDeref trait (prevents silent leaks)Performance Comparison (10 invalid keys):
Protected Against: ✅ Brute force • ✅ Timing attacks • ✅ Rainbow tables • ✅ Memory disclosure • ✅ Database breaches • ✅ GPU/ASIC attacks
NOT Protected Against: ❌ Compromised app server • ❌ User negligence • ❌ Network interception (use HTTPS) • ❌ Quantum computers
use api_keys_simplified::{
ApiKeyManagerV0, Environment, ExposeSecret,
KeyStatus, SecureString, ApiKey, Hash
};
use chrono::{Duration, Utc};
// ✅ Checksums enabled by default (DoS protection - use .disable_checksum() to turn off)
let manager = ApiKeyManagerV0::init_default_config("myapp_sk")?;
// ✅ Never log keys (auto-redacted)
let key = manager.generate(Environment::production())?;
println!("{:?}", key); // Prints: ApiKey { key: "[REDACTED]", ... }
// ✅ Show keys only once
display_to_user_once(key.key().expose_secret());
// Store both hash and salt (salt is needed for hash regeneration)
let hash_data = key.expose_hash();
db.save(hash_data.hash(), hash_data.salt());
// ✅ Always use HTTPS
let response = client.get("https://api.example.com")
.header("Authorization", format!("Bearer {}", key.key().expose_secret()))
.send()?;
// ✅ Implement key rotation
fn rotate_key(manager: &ApiKeyManagerV0, user_id: u64) -> Result<ApiKey<Hash>, Box<dyn std::error::Error>> {
let new_key = manager.generate(Environment::production())?;
db.revoke_old_keys(user_id)?;
let hash_data = new_key.expose_hash();
db.save_new_key(user_id, hash_data.hash(), hash_data.salt())?;
Ok(new_key)
}
// ✅ Use expiration for temporary access (trials, partners)
let trial_expiry = Utc::now() + Duration::days(7);
let trial_key = manager.generate_with_expiry(Environment::production(), trial_expiry)?;
let hash_data = trial_key.expose_hash();
db.save(user_id, hash_data.hash(), hash_data.salt())?;
// ✅ Implement key revocation for compromised keys
fn revoke_key(user_id: u64, key_hash: &str) -> Result<(), Box<dyn std::error::Error>> {
// Mark hash as revoked in database
db.mark_revoked(user_id, key_hash)?;
Ok(())
}
// ✅ Check revocation status during verification
fn verify_with_revocation(
manager: &ApiKeyManagerV0,
key: &SecureString,
user_id: u64
) -> Result<bool, Box<dyn std::error::Error>> {
let stored_hash = db.get_user_key_hash(user_id)?;
// Check if key is revoked first (fast database check)
if db.is_revoked(user_id, &stored_hash)? {
return Ok(false);
}
// Then verify key status
match manager.verify(key, &stored_hash)? {
KeyStatus::Valid => Ok(true),
KeyStatus::Invalid => Ok(false),
}
}
// ✅ Rate limit verification (still important with checksums)
if rate_limiter.check(ip_address).is_err() {
return Err("Too many failed attempts");
}
// Convert incoming string to SecureString
let incoming_key = SecureString::from(request_key_string.to_string());
manager.verify(&incoming_key, &stored_hash)?;
| Preset | Memory | Time | Verification |
|---|---|---|---|
| Balanced (default) | 19 MB | 2 iter | ~50ms |
| High Security | 64 MB | 3 iter | ~150ms |
Note: Slow verification is intentional—it prevents brute force attacks.
cargo test # All tests
cargo test --features expensive_tests # Include timing analysis
use api_keys_simplified::{ApiKeyManagerV0, Environment, Error, ExposeSecret};
match ApiKeyManagerV0::init_default_config("sk") {
Ok(manager) => {
match manager.generate(Environment::production()) {
Ok(key) => println!("Success: {}", key.key().expose_secret()),
Err(Error::OperationFailed(op_err)) => {
// Operation errors contain details (use {:?} in logs for debugging)
eprintln!("Operation error: {}", op_err);
}
Err(e) => eprintln!("Generation error: {}", e),
}
}
Err(e) => eprintln!("Init error: {}", e),
}
Error messages are intentionally generic to prevent information leakage.
| Feature | api-keys-simplified | uuid | nanoid |
|---|---|---|---|
| Cryptographic security | ✅ Argon2id | ❌ | ⚠️ Basic |
| Hashed storage | ✅ Built-in | ❌ | ❌ |
| Constant-time verify | ✅ Yes | ❌ | ❌ |
| Memory protection | ✅ Auto-zeroing | ❌ | ❌ |
| Structured format | ✅ prefix.env.data | ❌ | ❌ |
Licensed under the Apache License, Version 2.0.
All cryptographic implementations use well-audited crates:
argon2 - Official Argon2 implementationsubtle - Constant-time primitiveszeroize - Secure memory zeroinggetrandom - OS-level CSPRNGEmail security issues to: sandip@ssdd.dev
Contributions welcome!