Skip to content

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

  1. Password Store: Manages user credentials
  2. PasswordStore trait: Interface for credential storage
  3. InMemoryPasswordStore: In-memory implementation
  4. SharedPasswordStore: Thread-safe wrapper
  5. Custom implementations supported

  6. SCRAM Credentials: Stored authentication data

  7. stored_key: H(ClientKey) - used to verify client
  8. server_key: HMAC(SaltedPassword, "Server Key") - used to authenticate server
  9. salt: Random 16-byte salt
  10. iterations: PBKDF2 iteration count (default: 4096)

  11. SCRAM Auth State: Per-connection authentication state

  12. Manages nonces
  13. Constructs authentication messages
  14. Verifies client proof
  15. Generates server signature

  16. Auth Manager: High-level authentication coordinator

  17. Supports multiple auth methods
  18. Integrates with password store
  19. 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_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

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_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:

cargo test postgres_scram_auth_tests

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

  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_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

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

Support

For issues, questions, or contributions: - GitHub Issues: heliosdb/heliosdb-lite/issues - Documentation: docs/ - Examples: examples/postgres_server_ssl.rs