Skip to content

SCRAM-SHA-256 Authentication Guide

SCRAM-SHA-256 Authentication Guide

Complete guide to SCRAM-SHA-256 authentication in HeliosDB Nano 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_nano::{EmbeddedDatabase, Result};
use heliosdb_nano::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

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

  1. Password Store: Manages user credentials

    • PasswordStore trait: Interface for credential storage
    • InMemoryPasswordStore: In-memory implementation
    • SharedPasswordStore: Thread-safe wrapper
    • Custom implementations supported
  2. SCRAM Credentials: Stored authentication data

    • stored_key: H(ClientKey) - used to verify client
    • server_key: HMAC(SaltedPassword, “Server Key”) - used to authenticate server
    • salt: Random 16-byte salt
    • iterations: PBKDF2 iteration count (default: 4096)
  3. SCRAM Auth State: Per-connection authentication state

    • Manages nonces
    • Constructs authentication messages
    • Verifies client proof
    • Generates server signature
  4. 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):

let password_store = InMemoryPasswordStore::with_iterations(8192);

Recommended values:

  • Development: 4096 (default)
  • Production: 8192-16384
  • High security: 32768+

Custom Password Store

Implement PasswordStore trait for custom storage:

use heliosdb_nano::protocol::postgres::{PasswordStore, ScramCredentials};
use heliosdb_nano::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

password_store.update_password("alice", "new_secure_password")?;

Remove Users

let removed = password_store.remove_user("bob")?;

List Users

let users = password_store.list_users();
println!("Users: {:?}", users);

Check User Existence

if password_store.user_exists("alice") {
println!("User alice exists");
}

Testing

Unit Tests

Test SCRAM cryptographic functions:

#[test]
fn test_scram_authentication() {
use heliosdb_nano::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:

Terminal window
cargo test postgres_scram_auth_tests

Manual Testing

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

  1. Use strong passwords: Minimum 12 characters, mixed case, numbers, symbols
  2. High iteration count: Use 8192+ iterations
  3. Combine with SSL/TLS: Always use encrypted connections
  4. Rotate passwords: Implement password rotation policies
  5. Monitor failed attempts: Log and alert on failed authentication
  6. 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_nano::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

  1. Cache credentials: Use SharedPasswordStore for multiple connections
  2. Connection pooling: Authenticate once, reuse connections
  3. Iteration tuning: Balance security needs vs. performance
  4. Async operations: Authentication is fully async, no blocking

RFC Compliance

HeliosDB Nano 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

Support

For issues, questions, or contributions: