SCRAM-SHA-256 Authentication Guide¶
Complete guide to SCRAM-SHA-256 authentication in HeliosDB-Lite PostgreSQL protocol implementation.
Overview¶
SCRAM-SHA-256 (Salted Challenge Response Authentication Mechanism) is a modern, secure authentication protocol that provides:
- No plaintext passwords: Passwords are never transmitted or stored in plaintext
- Mutual authentication: Both client and server prove knowledge of the password
- Replay attack protection: Each authentication uses unique nonces
- Timing attack resistance: Constant-time comparison prevents timing-based attacks
- RFC compliance: Fully compliant with RFC 5802 (SCRAM) and RFC 7677 (SCRAM-SHA-256)
Quick Start¶
Basic Setup¶
use heliosdb_lite::{EmbeddedDatabase, Result};
use heliosdb_lite::protocol::postgres::{
PgServerBuilder, AuthMethod, AuthManager,
InMemoryPasswordStore, SharedPasswordStore
};
use std::sync::Arc;
#[tokio::main]
async fn main() -> Result<()> {
// Create database
let db = Arc::new(EmbeddedDatabase::new_in_memory()?);
// Create password store with users
let mut password_store = InMemoryPasswordStore::new();
password_store.add_user("postgres", "secure_password")?;
password_store.add_user("alice", "alice_secret")?;
// Create shared password store
let shared_store = SharedPasswordStore::new(password_store);
// Create authentication manager
let auth_manager = Arc::new(
AuthManager::with_password_store(AuthMethod::ScramSha256, shared_store)
);
// Build server
let server = PgServerBuilder::new()
.address("127.0.0.1:5432".parse()?)
.auth_manager(auth_manager)
.build(db)?;
// Start server
server.serve().await
}
Connecting with psql¶
# Direct password in connection string
psql "host=127.0.0.1 port=5432 user=postgres password=secure_password dbname=heliosdb"
# Interactive password prompt
psql "host=127.0.0.1 port=5432 user=postgres dbname=heliosdb"
# With SSL/TLS
psql "sslmode=require host=127.0.0.1 port=5432 user=postgres password=secure_password"
Architecture¶
Components¶
- Password Store: Manages user credentials
PasswordStoretrait: Interface for credential storageInMemoryPasswordStore: In-memory implementationSharedPasswordStore: Thread-safe wrapper-
Custom implementations supported
-
SCRAM Credentials: Stored authentication data
stored_key: H(ClientKey) - used to verify clientserver_key: HMAC(SaltedPassword, "Server Key") - used to authenticate serversalt: Random 16-byte salt-
iterations: PBKDF2 iteration count (default: 4096) -
SCRAM Auth State: Per-connection authentication state
- Manages nonces
- Constructs authentication messages
- Verifies client proof
-
Generates server signature
-
Auth Manager: High-level authentication coordinator
- Supports multiple auth methods
- Integrates with password store
- Handles authentication flow
Authentication Flow¶
Client Server
| |
|--- Startup (user=alice) --------------->|
| |
|<-- AuthenticationSASL (SCRAM-SHA-256)---|
| |
|--- client-first-message ---------------->|
| (n=alice,r=client_nonce) |
| |
|<-- server-first-message -----------------|
| (r=client_nonce+server_nonce, |
| s=salt_b64,i=4096) |
| |
|--- client-final-message ---------------->|
| (c=channel_binding, |
| r=combined_nonce, |
| p=client_proof) |
| | [Verify proof]
| |
|<-- server-final-message -----------------|
| (v=server_signature) |
| |
|<-- AuthenticationOk --------------------|
| |
|<-- ReadyForQuery -----------------------|
Security Features¶
Password Derivation¶
SCRAM-SHA-256 uses PBKDF2-HMAC-SHA-256 for key derivation:
// SaltedPassword = Hi(password, salt, iterations)
// ClientKey = HMAC(SaltedPassword, "Client Key")
// StoredKey = H(ClientKey)
// ServerKey = HMAC(SaltedPassword, "Server Key")
let salted_password = scram_hi(password, salt, 4096);
let client_key = scram_hmac_sha256(&salted_password, b"Client Key");
let stored_key = scram_h(&client_key);
let server_key = scram_hmac_sha256(&salted_password, b"Server Key");
Stored Credentials¶
Only stored_key and server_key are stored on the server:
pub struct ScramCredentials {
pub username: String,
pub salt: Vec<u8>, // Random 16-byte salt
pub iterations: u32, // PBKDF2 iterations (4096)
pub stored_key: Vec<u8>, // H(ClientKey)
pub server_key: Vec<u8>, // HMAC(SaltedPassword, "Server Key")
}
Timing Attack Resistance¶
All secret comparisons use constant-time algorithms:
fn constant_time_compare(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
let mut result = 0u8;
for i in 0..a.len() {
result |= a[i] ^ b[i];
}
result == 0
}
Configuration Options¶
Iteration Count¶
Customize PBKDF2 iterations (higher = more secure but slower):
Recommended values: - Development: 4096 (default) - Production: 8192-16384 - High security: 32768+
Custom Password Store¶
Implement PasswordStore trait for custom storage:
use heliosdb_lite::protocol::postgres::{PasswordStore, ScramCredentials};
use heliosdb_lite::Result;
struct DatabasePasswordStore {
// Your database connection
}
impl PasswordStore for DatabasePasswordStore {
fn get_credentials(&self, username: &str) -> Option<ScramCredentials> {
// Query database
todo!()
}
fn add_user(&mut self, username: &str, password: &str) -> Result<()> {
// Insert into database
todo!()
}
fn remove_user(&mut self, username: &str) -> Result<bool> {
// Delete from database
todo!()
}
fn update_password(&mut self, username: &str, new_password: &str) -> Result<()> {
// Update in database
todo!()
}
fn user_exists(&self, username: &str) -> bool {
// Check existence
todo!()
}
fn list_users(&self) -> Vec<String> {
// List all users
todo!()
}
}
User Management¶
Add Users¶
let mut password_store = InMemoryPasswordStore::new();
password_store.add_user("alice", "secure_password")?;
password_store.add_user("bob", "another_password")?;
Update Passwords¶
Remove Users¶
List Users¶
Check User Existence¶
Testing¶
Unit Tests¶
Test SCRAM cryptographic functions:
#[test]
fn test_scram_authentication() {
use heliosdb_lite::protocol::postgres::auth::prepare_scram_credentials;
let password = "secret";
let salt = b"randomsalt123456";
let iterations = 4096;
let (stored_key, server_key) = prepare_scram_credentials(password, salt, iterations);
assert_eq!(stored_key.len(), 32);
assert_eq!(server_key.len(), 32);
assert_ne!(stored_key, server_key);
}
Integration Tests¶
Test full authentication flow:
Manual Testing¶
# Start server
cargo run --example postgres_server_ssl
# Connect with psql
psql "host=127.0.0.1 port=5432 user=postgres password=postgres"
# Run queries
SELECT * FROM users;
Best Practices¶
Production Deployment¶
- Use strong passwords: Minimum 12 characters, mixed case, numbers, symbols
- High iteration count: Use 8192+ iterations
- Combine with SSL/TLS: Always use encrypted connections
- Rotate passwords: Implement password rotation policies
- Monitor failed attempts: Log and alert on failed authentication
- Use custom password store: Implement persistent storage (database, file)
Password Policy¶
fn validate_password(password: &str) -> Result<()> {
if password.len() < 12 {
return Err(Error::validation("Password must be at least 12 characters"));
}
let has_upper = password.chars().any(|c| c.is_uppercase());
let has_lower = password.chars().any(|c| c.is_lowercase());
let has_digit = password.chars().any(|c| c.is_numeric());
let has_special = password.chars().any(|c| !c.is_alphanumeric());
if !(has_upper && has_lower && has_digit && has_special) {
return Err(Error::validation(
"Password must contain uppercase, lowercase, digit, and special character"
));
}
Ok(())
}
Audit Logging¶
tracing::info!(
username = %username,
ip = %client_ip,
"SCRAM authentication successful"
);
tracing::warn!(
username = %username,
ip = %client_ip,
error = %error,
"SCRAM authentication failed"
);
Troubleshooting¶
Common Issues¶
Authentication fails with "User not found"¶
- Cause: User doesn't exist in password store
- Solution: Add user with
password_store.add_user(username, password)
Authentication fails with "Invalid password"¶
- Cause: Incorrect password or corrupted credentials
- Solution: Update password with
password_store.update_password(username, new_password)
Client shows "SCRAM not supported"¶
- Cause: Client doesn't support SCRAM-SHA-256
- Solution: Use a modern PostgreSQL client (psql 10+, libpq 10+)
Slow authentication¶
- Cause: High iteration count
- Solution: Balance security and performance (recommended: 4096-8192)
Debug Logging¶
Enable debug logging to troubleshoot authentication issues:
tracing_subscriber::fmt()
.with_env_filter("debug,heliosdb_lite::protocol::postgres=trace")
.init();
Performance Considerations¶
Benchmarks¶
Default configuration (4096 iterations): - Credential creation: ~5ms - Password verification: ~5ms - Full authentication: ~10-15ms
High security (16384 iterations): - Credential creation: ~20ms - Password verification: ~20ms - Full authentication: ~40-50ms
Optimization Tips¶
- Cache credentials: Use SharedPasswordStore for multiple connections
- Connection pooling: Authenticate once, reuse connections
- Iteration tuning: Balance security needs vs. performance
- Async operations: Authentication is fully async, no blocking
RFC Compliance¶
HeliosDB-Lite SCRAM implementation is fully compliant with:
- RFC 5802: Salted Challenge Response Authentication Mechanism (SCRAM)
- RFC 7677: SCRAM-SHA-256 and SCRAM-SHA-256-PLUS
- PostgreSQL Protocol: SASL authentication messages
Supported Features¶
- ✅ SCRAM-SHA-256
- ✅ PBKDF2-HMAC-SHA-256 key derivation
- ✅ Salted password hashing
- ✅ Client and server nonce
- ✅ Channel binding placeholder (n,,)
- ✅ Server signature verification
- ✅ Constant-time comparisons
Future Enhancements¶
- ⏳ Channel binding (SCRAM-SHA-256-PLUS)
- ⏳ Password change protocol
- ⏳ Additional SASL mechanisms
Migration Guide¶
From Trust to SCRAM¶
// Before (Trust mode)
let auth_manager = Arc::new(AuthManager::new(AuthMethod::Trust));
// After (SCRAM mode)
let mut password_store = InMemoryPasswordStore::new();
password_store.add_user("postgres", "secure_password")?;
let shared_store = SharedPasswordStore::new(password_store);
let auth_manager = Arc::new(
AuthManager::with_password_store(AuthMethod::ScramSha256, shared_store)
);
From Cleartext to SCRAM¶
// Before (Cleartext)
let mut auth_manager = AuthManager::new(AuthMethod::CleartextPassword);
auth_manager.add_user("alice".to_string(), "password".to_string());
// After (SCRAM)
let mut password_store = InMemoryPasswordStore::new();
password_store.add_user("alice", "password")?;
let shared_store = SharedPasswordStore::new(password_store);
let auth_manager = AuthManager::with_password_store(
AuthMethod::ScramSha256,
shared_store
);
References¶
- RFC 5802 - SCRAM SASL Mechanism
- RFC 7677 - SCRAM-SHA-256
- PostgreSQL SCRAM Documentation
- PBKDF2 Specification
Support¶
For issues, questions, or contributions: - GitHub Issues: heliosdb/heliosdb-lite/issues - Documentation: docs/ - Examples: examples/postgres_server_ssl.rs