OpenSSH Hash-Based Authentication System

This post describes a secure SSH authentication system that replaces traditional authorized_keys files with SQLite database lookups using SHA256 hashes of public keys. The system provides enhanced security, performance, and manageability for SSH key authentication.

Table of Contents

Overview

The SSH Hash-Based Authentication System replaces traditional authorized_keys files with SQLite database lookups using SHA256 hashes of public keys. This provides enhanced security, performance, and manageability for SSH key authentication.

System Requirements

  • Operating System: Linux (kernel 5.14.0-570.30.1.el9_6.x86_64)
  • Linux Distribution: Rocky Linux 9.6
  • OpenSSH Server: openssh-server-8.7p1-45.el9.rocky.0.1.x86_64
  • OpenSSH Clients: openssh-clients-8.7p1-45.el9.rocky.0.1.x86_64
  • Additional Dependencies: SQLite3, bash, core utilities

Key Benefits

  • Security: No plain-text key storage, hash-based lookups
  • Performance: SQLite binary lookups (faster than file parsing)
  • Scalability: Per-user databases with indexed lookups
  • Manageability: Comprehensive management tools
  • Auditability: Complete audit trail and logging
  • Flexibility: Supports multiple key types (RSA, ED25519, ECDSA)

System Architecture

Core Components

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│   SSH Client    │    │   SSHD Server    │    │  Hash Database  │
│                 │    │                  │    │                 │
│ • SSH Key       │───▶│ • AuthorizedKeys │───▶│ • SQLite DB     │
│ • Key Data      │    │   Command        │    │ • SHA256 Hashes │
│                 │    │ • ssh_hash_auth  │    │ • Per-user      │
└─────────────────┘    └──────────────────┘    └─────────────────┘

Authentication Flow

  1. Client connects with SSH key
  2. SSHD calls ssh_hash_auth.sh with key data
  3. Script extracts key type and generates hash
  4. SQLite lookup in user's database
  5. Returns authorised key or nothing
  6. SSHD validates the returned key

Database Structure

-- Standard Database
CREATE TABLE hashes (
    hash TEXT PRIMARY KEY,
    description TEXT,
    added TEXT
);

-- Enhanced Database (with expiration)
CREATE TABLE hashes (
    hash TEXT PRIMARY KEY,
    description TEXT,
    added TEXT,
    expires TEXT,
    last_used TEXT,
    usage_count INTEGER DEFAULT 0,
    status TEXT DEFAULT 'active'
);

Installation & Setup

Step 1: Install Core System

# Copy scripts to /usr/local/bin
sudo cp ssh_hash_auth.sh /usr/local/bin/
sudo cp ssh_hash_manager.sh /usr/local/bin/
sudo chmod +x /usr/local/bin/ssh_hash_auth.sh
sudo chmod +x /usr/local/bin/ssh_hash_manager.sh

# Install SSHD configuration
sudo cp 99-hash-auth.conf /etc/ssh/sshd_config.d/99-hash-auth.conf
sudo systemctl reload sshd

Step 2: Set Up User Database

# Generate hash from user's public key
./ssh_hash_manager.sh generate <username> ~<username>/.ssh/id_rsa.pub

# Or add hash manually
./ssh_hash_manager.sh add <username> SHA256:hash... "description"

Step 3: Test Authentication

# Test connection
ssh <username>@localhost

# Check debug output if needed
./debug_auth.sh <username> <key_data>

Usage & Management

Basic Operations

# Generate hash from public key file
./ssh_hash_manager.sh generate <user> <key_file>

# Add hash manually
./ssh_hash_manager.sh add <user> <hash> [description]

# List all hashes
./ssh_hash_manager.sh list <user>

# Remove hash
./ssh_hash_manager.sh remove <user> <hash>

# Search hashes
./ssh_hash_manager.sh search <user> <term>

Enhanced Operations (with expiration)

# Generate hash with expiration (90 days)
./enhanced_hash_manager.sh generate <user> <key_file> 90

# Add hash with expiration (30 days)
./enhanced_hash_manager.sh add <user> <hash> "description" 30

# Check for expired hashes
./enhanced_hash_manager.sh check-expired <user>

# Clean up expired hashes (dry run)
./enhanced_hash_manager.sh cleanup <user> --dry-run

# Backup database
./enhanced_hash_manager.sh backup <user> /var/backup/ssh_hashes/user

# Restore database
./enhanced_hash_manager.sh restore <user> <backup_file>

# Migrate from authorized_keys file
./enhanced_hash_manager.sh migrate <user> ~user/.ssh/authorized_keys 60

Migration from Traditional authorized_keys

# Option 1: Manual migration
./enhanced_hash_manager.sh migrate <user> ~<user>/.ssh/authorized_keys 90

# Option 2: Bulk migration
for user in $(cut -d: -f1 /etc/passwd); do
    if [ -f "/home/$user/.ssh/authorized_keys" ]; then
        ./enhanced_hash_manager.sh migrate "$user" "/home/$user/.ssh/authorized_keys" 90
    fi
done

Security Features

Core Security Benefits

  • No plain-text key storage - only SHA256 hashes
  • Hash-based lookups prevent key enumeration
  • Database files have restricted permissions (600)
  • User-specific databases prevent cross-user access
  • No modification of existing authorized_keys files

Enhanced Security Features

  • Key expiration dates for automatic rotation
  • Audit logging of all authentication attempts
  • Rate limiting to prevent brute force attacks
  • Usage tracking and analytics
  • Backup/restore functionality

Audit Logging

Log Format:

timestamp|event|username|key_hash|ip_address|result

Events Logged:

  • AUTH_SUCCESS - Successful authentication
  • AUTH_FAILED - Failed authentication (with reason)
  • RATE_LIMITED - Rate limited attempts
  • DB_NOT_FOUND - Database not found
  • EXPIRED - Expired key attempt

Rate Limiting:

  • Maximum 5 failed attempts per 5 minutes per user/IP
  • Automatic reset on successful authentication
  • Configurable limits in enhanced script

Troubleshooting

Common Issues

1. Authentication Fails:

# Check if database exists
ls -la ~<user>/.ssh/authorized_keys.db

# Check database contents
./ssh_hash_manager.sh list <user>

# Test with debug script
./debug_auth.sh <user> <key_data>

2. SSHD Configuration Issues:

# Check SSHD config
sudo sshd -T | grep AuthorizedKeys

# Reload SSHD
sudo systemctl reload sshd

# Check SSHD logs
sudo journalctl -u sshd -f

3. Permission Issues:

# Fix database permissions
chmod 600 ~<user>/.ssh/authorized_keys.db
chown <user>:<user> ~<user>/.ssh/authorized_keys.db

# Fix .ssh directory permissions
chmod 700 ~<user>/.ssh
chown <user>:<user> ~<user>/.ssh

Debug Commands

# Test hash generation
echo "ssh-rsa AAAAB3NzaC1yc2E..." | ./ssh_hash_manager.sh generate test /dev/stdin

# Test database lookup
sqlite3 ~<user>/.ssh/authorized_keys.db "SELECT * FROM hashes;"

# Check audit logs
sudo tail -f /var/log/ssh_hash_auth.log

Advanced Features

Key Expiration

  • Automatic expiration dates for keys
  • Configurable expiration periods
  • Automatic cleanup of expired keys
  • Notifications for expiring keys

Usage Analytics

  • Track last used timestamp
  • Count usage frequency
  • Identify unused keys
  • Generate usage reports

Backup and Recovery

  • Automated database backups
  • Point-in-time recovery
  • Backup verification
  • Disaster recovery procedures

Best Practices

Security

  • Regular key rotation (use expiration dates)
  • Monitor audit logs for suspicious activity
  • Backup databases regularly
  • Use rate limiting to prevent brute force
  • Restrict database access to authorised users only

Management

  • Document all key additions/removals
  • Regular cleanup of expired keys
  • Monitor database performance
  • Test backup/restore procedures
  • Keep scripts updated and secure

Monitoring

  • Set up log monitoring for failed attempts
  • Monitor database size and performance
  • Track key usage patterns
  • Alert on unusual access patterns
  • Regular security audits

File Reference

Core Files

  • ssh_hash_auth.sh - Main authentication script (production)
  • debug_auth.sh - Debug version for troubleshooting
  • ssh_hash_manager.sh - Hash management tool
  • 99-hash-auth.conf - SSHD configuration

Enhanced Files

  • enhanced_hash_auth_with_expiration.sh - Enhanced auth with expiration & audit logging
  • enhanced_hash_manager.sh - Enhanced manager with advanced features

Database Locations

  • User databases: ~<user>/.ssh/authorized_keys.db
  • Audit logs: /var/log/ssh_hash_auth.log
  • Rate limit file: /tmp/ssh_hash_auth_rate_limit

Configuration Files

  • SSHD config: /etc/ssh/sshd_config.d/99-hash-auth.conf
  • Scripts: /usr/local/bin/ssh_hash_auth.sh

Appendixes

ssh_hash_auth.sh (Production Authentication Script)

#!/bin/bash
#
# Fast SSH Hash-Based Authentication Script
# Uses binary database for ultra-fast lookups
#
# Usage: ./ssh_hash_auth.sh <username> <key_data>
# Returns: "ssh-rsa <key_data>" if authorised, nothing if unauthorised

set -e

# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Check arguments
if [ $# -ne 2 ]; then
    exit 1
fi

USERNAME="$1"
KEY_DATA="$2"

# SSH passes just the key data without the type prefix
# We need to determine the key type from the data
# For now, we'll assume it's the same type as what we have in the database
# This is a simplified approach - in production you might want to detect the type

# Get user's home directory
USER_HOME=$(eval echo ~$USERNAME)
HASH_DB="$USER_HOME/.ssh/authorized_keys.db"

# Check if hash database exists
if [ ! -f "$HASH_DB" ]; then
    exit 1
fi

# Function to extract key type from base64 data
extract_key_type() {
    local key_data="$1"
    local key_type=""
    
    # Decode base64 and extract the key type
    # The format is: [4-byte length][key-type-string][4-byte length][curve-name][4-byte length][key-data]
    # For RSA: [4-byte length]["ssh-rsa"][4-byte length][exponent][4-byte length][modulus]
    # For ED25519: [4-byte length]["ssh-ed25519"][4-byte length][key-data]
    # For ECDSA: [4-byte length]["ecdsa-sha2-nistp384"][4-byte length][curve-name][4-byte length][key-data]
    
    # Read the first length (4 bytes) and then read the key type string
    local first_length=$(echo "$key_data" | base64 -d | dd bs=1 skip=0 count=4 2>/dev/null | xxd -p | tr -d '\n' | sed 's/000000//')
    local length_dec=$(printf "%d" "0x$first_length")
    
    # Extract the key type string
    key_type=$(echo "$key_data" | base64 -d | dd bs=1 skip=4 count=$length_dec 2>/dev/null | tr -d '\0')
    
    # Handle different key types
    case "$key_type" in
        "ssh-rsa")
            echo "ssh-rsa"
            ;;
        "ssh-ed25519")
            echo "ssh-ed25519"
            ;;
        "ecdsa-sha2-nistp256")
            echo "ecdsa-sha2-nistp256"
            ;;
        "ecdsa-sha2-nistp384")
            echo "ecdsa-sha2-nistp384"
            ;;
        "ecdsa-sha2-nistp521")
            echo "ecdsa-sha2-nistp521"
            ;;
        *)
            echo "unknown"
            ;;
    esac
}

# Function to generate hash from public key
generate_hash() {
    local key_data="$1"
    local key_type="$2"
    
    # Reconstruct the full public key with a generic comment
    local full_key="${key_type} ${key_data} ssh_hash_auth_key"
    
    # Extract the base64 part and hash with sha256sum
    local key_b64=$(echo "$full_key" | awk '{print $2}')
    local hash=$(echo "$key_b64" | base64 -d | sha256sum | cut -d' ' -f1)
    local hash_b64=$(echo "$hash" | xxd -r -p | base64)
    
    echo "SHA256:$hash_b64"
}

# Function to check if FTS5 is available
check_fts5() {
    # Check if SQLite3 is available
    if ! command -v sqlite3 >/dev/null 2>&1; then
        return 1
    fi
    
    # Check SQLite version (FTS5 requires SQLite 3.9.0+)
    local version=$(sqlite3 :memory: "SELECT sqlite_version();" 2>/dev/null)
    if [ $? -ne 0 ]; then
        return 1
    fi
    
    # Parse version and check if it's >= 3.9.0
    local major=$(echo "$version" | cut -d. -f1)
    local minor=$(echo "$version" | cut -d. -f2)
    
    if [ "$major" -lt 3 ] || ([ "$major" -eq 3 ] && [ "$minor" -lt 9 ]); then
        return 1
    fi
    
    # Test if FTS5 can be created
    if ! sqlite3 :memory: "CREATE VIRTUAL TABLE test_fts USING fts5(test); DROP TABLE test_fts;" >/dev/null 2>&1; then
        return 1
    fi
    
    return 0
}

# Extract the key type from the base64 data
KEY_TYPE=$(extract_key_type "$KEY_DATA")

# Reconstruct the full key with the extracted type
FULL_KEY="${KEY_TYPE} ${KEY_DATA} ssh_hash_auth_key"

# Generate hash using the correct key type
KEY_HASH=$(generate_hash "$KEY_DATA" "$KEY_TYPE")

# Fast binary lookup using sqlite3 (much faster than grep)
if command -v sqlite3 >/dev/null 2>&1; then
    # Use regular SQLite table lookup (simpler and more reliable)
    if sqlite3 "$HASH_DB" "SELECT 1 FROM hashes WHERE hash='$KEY_HASH' LIMIT 1;" 2>/dev/null | grep -q "1"; then
        echo "$FULL_KEY"
        exit 0
    fi
else
    # Fallback to optimised text search with sort and binary search
    if [ -f "$HASH_DB" ] && sort "$HASH_DB" | grep -q "^$KEY_HASH$"; then
        echo "$FULL_KEY"
        exit 0
    fi
fi

# Key is not authorised, return nothing
exit 1

debug_auth.sh (Debug Authentication Script)

#!/bin/bash

# SSH Hash-Based Authentication Script (DEBUG VERSION)
# This script is called by SSHD via AuthorizedKeysCommand
# Usage: debug_auth.sh <username> <key_data>

set -e

# Configuration
# Get user's home directory
USER_HOME=$(eval echo ~$1)
DB_FILE="$USER_HOME/.ssh/authorized_keys.db"

# Function to extract key type from base64 data
extract_key_type() {
    local key_data="$1"
    local key_type=""
    
    echo "DEBUG: Extracting key type from: $key_data" >&2
    
    # Decode base64 and extract the key type
    # The format is: [4-byte length][key-type-string][4-byte length][curve-name][4-byte length][key-data]
    # For RSA: [4-byte length]["ssh-rsa"][4-byte length][exponent][4-byte length][modulus]
    # For ED25519: [4-byte length]["ssh-ed25519"][4-byte length][key-data]
    # For ECDSA: [4-byte length]["ecdsa-sha2-nistp384"][4-byte length][curve-name][4-byte length][key-data]
    
    # Read the first length (4 bytes) and then read the key type string
    local first_length=$(echo "$key_data" | base64 -d | dd bs=1 skip=0 count=4 2>/dev/null | xxd -p | tr -d '\n' | sed 's/000000//')
    local length_dec=$(printf "%d" "0x$first_length")
    
    echo "DEBUG: First length hex: $first_length, decimal: $length_dec" >&2
    
    # Extract the key type string
    key_type=$(echo "$key_data" | base64 -d | dd bs=1 skip=4 count=$length_dec 2>/dev/null | tr -d '\0')
    
    echo "DEBUG: Extracted key type: '$key_type'" >&2
    
    # Handle different key types
    case "$key_type" in
        "ssh-rsa") echo "ssh-rsa" ;;
        "ssh-ed25519") echo "ssh-ed25519" ;;
        "ecdsa-sha2-nistp256") echo "ecdsa-sha2-nistp256" ;;
        "ecdsa-sha2-nistp384") echo "ecdsa-sha2-nistp384" ;;
        "ecdsa-sha2-nistp521") echo "ecdsa-sha2-nistp521" ;;
        *) echo "unknown" ;;
    esac
}

# Function to generate hash from public key
generate_hash() {
    local key_data="$1"
    local key_type="$2"
    
    echo "DEBUG: Generating hash for key type: $key_type" >&2
    
    # Reconstruct the full public key with a generic comment
    local full_key="${key_type} ${key_data} ssh_hash_auth_key"
    
    echo "DEBUG: Full key: $full_key" >&2
    
    # Use the same method as ssh_hash_manager.sh
    # Extract the base64 part and hash with sha256sum
    local key_b64=$(echo "$full_key" | awk '{print $2}')
    local hash=$(echo "$key_b64" | base64 -d | sha256sum | cut -d' ' -f1)
    local hash_b64=$(echo "$hash" | xxd -r -p | base64)
    
    local final_hash="SHA256:$hash_b64"
    echo "DEBUG: Generated hash: $final_hash" >&2
    
    echo "$final_hash"
}

# Check arguments
if [ $# -ne 2 ]; then
    echo "Usage: $0 <username> <key_data>" >&2
    exit 1
fi

USERNAME="$1"
KEY_DATA="$2"

echo "DEBUG: Username: $USERNAME" >&2
echo "DEBUG: Key data: $KEY_DATA" >&2

# Check if database exists
if [ ! -f "$DB_FILE" ]; then
    echo "DEBUG: Database file not found: $DB_FILE" >&2
    exit 1
fi

echo "DEBUG: Database file found: $DB_FILE" >&2

# Extract the key type from the base64 data
KEY_TYPE=$(extract_key_type "$KEY_DATA")

echo "DEBUG: Extracted key type: $KEY_TYPE" >&2

# Reconstruct the full key with the extracted type
FULL_KEY="${KEY_TYPE} ${KEY_DATA} ssh_hash_auth_key"

echo "DEBUG: Full key: $FULL_KEY" >&2

# Generate hash using the correct key type
KEY_HASH=$(generate_hash "$KEY_DATA" "$KEY_TYPE")

echo "DEBUG: Key hash: $KEY_HASH" >&2

# Fast binary lookup using sqlite3 (much faster than grep)
if command -v sqlite3 >/dev/null 2>&1; then
    echo "DEBUG: Using SQLite lookup" >&2
    # Use regular SQLite table lookup (simpler and more reliable)
    if sqlite3 "$DB_FILE" "SELECT 1 FROM hashes WHERE hash='$KEY_HASH' LIMIT 1;" 2>/dev/null | grep -q "1"; then
        echo "DEBUG: Key found in database!" >&2
        echo "$FULL_KEY"
        exit 0
    else
        echo "DEBUG: Key not found in database" >&2
    fi
else
    echo "DEBUG: Using fallback text search" >&2
    # Fallback to optimised text search with sort and binary search
    if [ -f "$DB_FILE" ] && sort "$DB_FILE" | grep -q "^$KEY_HASH$"; then
        echo "DEBUG: Key found in database!" >&2
        echo "$FULL_KEY"
        exit 0
    else
        echo "DEBUG: Key not found in database" >&2
    fi
fi

# Key is not authorised, return nothing
echo "DEBUG: Authentication failed" >&2
exit 1

99-hash-auth.conf (SSHD Configuration)

# Hash-based authentication configuration (per-user)
# This file is automatically generated by ssh_hash_auth installation
# To disable, rename this file or remove it

AuthorizedKeysCommand /usr/local/bin/ssh_hash_auth.sh %u %k
AuthorizedKeysCommandUser root

ssh_hash_manager.sh (Hash Management Tool)

#!/bin/bash
#
# Fast SSH Hash Manager using SQLite database
# Provides ultra-fast hash lookups and management
#

set -e

# Configuration
HASH_FILE_PERMS=600

# Function to generate hash from public key
generate_hash() {
    local key_data="$1"
    
    # Normalise the key (remove extra whitespace)
    normalised_key=$(echo "$key_data" | tr -s ' ')
    
    # Extract the base64 part (second field)
    key_b64=$(echo "$normalised_key" | awk '{print $2}')
    
    # Decode base64 and hash with sha256sum
    hash=$(echo "$key_b64" | base64 -d | sha256sum | cut -d' ' -f1)
    
    # Convert to base64
    hash_b64=$(echo "$hash" | xxd -r -p | base64)
    
    echo "SHA256:$hash_b64"
}

# Function to initialise SQLite database with FTS5 support check
init_database() {
    local username="$1"
    local db_file="$2"
    
    # Create .ssh directory if it doesn't exist
    local ssh_dir=$(dirname "$db_file")
    if [ ! -d "$ssh_dir" ]; then
        mkdir -p "$ssh_dir"
        chmod 700 "$ssh_dir"
        chown "$username:$username" "$ssh_dir"
    fi
    
    # Create SQLite database if it doesn't exist
    if [ ! -f "$db_file" ]; then
        # Always create the main table
        sqlite3 "$db_file" "CREATE TABLE hashes (hash TEXT PRIMARY KEY, description TEXT, added TEXT);"
        
        # Check if FTS5 is available and create FTS table if supported
        if check_fts5; then
            echo "FTS5 detected - creating optimised full-text search tables"
            sqlite3 "$db_file" "CREATE VIRTUAL TABLE hashes_fts USING fts5(hash, description, content='hashes', content_rowid='rowid');"
            sqlite3 "$db_file" "CREATE TRIGGER hashes_ai AFTER INSERT ON hashes BEGIN INSERT INTO hashes_fts(rowid, hash, description) VALUES (new.rowid, new.hash, new.description); END;"
            sqlite3 "$db_file" "CREATE TRIGGER hashes_ad AFTER DELETE ON hashes BEGIN INSERT INTO hashes_fts(hashes_fts, rowid, hash, description) VALUES('delete', old.rowid, old.hash, old.description); END;"
            sqlite3 "$db_file" "CREATE TRIGGER hashes_au AFTER UPDATE ON hashes BEGIN INSERT INTO hashes_fts(hashes_fts, rowid, hash, description) VALUES('delete', old.rowid, old.hash, old.description); INSERT INTO hashes_fts(rowid, hash, description) VALUES (new.rowid, new.hash, new.description); END;"
        else
            echo "FTS5 not available - using standard SQLite tables (slower but functional)"
        fi
        
        chmod $HASH_FILE_PERMS "$db_file"
        chown "$username:$username" "$db_file"
    fi
}

# Function to add hash to database
add_hash() {
    local username="$1"
    local hash="$2"
    local description="$3"
    
    # Get user's home directory
    user_home=$(eval echo ~$username)
    db_file="$user_home/.ssh/authorized_keys.db"
    
    # Initialise database
    init_database "$username" "$db_file"
    
    # Check if hash already exists
    if sqlite3 "$db_file" "SELECT 1 FROM hashes WHERE hash='$hash' LIMIT 1;" 2>/dev/null | grep -q "1"; then
        echo "Hash already exists: $hash"
        return
    fi
    
    # Add hash to database
    sqlite3 "$db_file" "INSERT INTO hashes (hash, description, added) VALUES ('$hash', '$description', '$(date -Iseconds)');"
    
    # Set proper permissions
    chmod $HASH_FILE_PERMS "$db_file"
    chown "$username:$username" "$db_file"
    
    echo "Added hash: $hash"
}

# Function to generate hash from public key file
generate_from_file() {
    local username="$1"
    local key_file="$2"
    
    if [ ! -f "$key_file" ]; then
        echo "Error: Key file not found: $key_file"
        exit 1
    fi
    
    # Read the public key
    key_data=$(cat "$key_file")
    
    # Extract comment from the public key (third field)
    comment=$(echo "$key_data" | awk '{print $3}')
    
    # If no comment found, use a default
    if [ -z "$comment" ]; then
        comment="No comment"
    fi
    
    # Generate hash
    hash=$(generate_hash "$key_data")
    
    # Add to user's database with extracted comment
    add_hash "$username" "$hash" "$comment"
    
    echo "Generated hash: $hash"
    echo "Public key: $key_data"
    echo "Comment: $comment"
}

# Function to list hashes for user
list_hashes() {
    local username="$1"
    
    user_home=$(eval echo ~$username)
    db_file="$user_home/.ssh/authorized_keys.db"
    
    if [ ! -f "$db_file" ]; then
        echo "No hash database found for user '$username'"
        return
    fi
    
    echo "Authorised hashes for user '$username':"
    echo "======================================"
    
    # Use FTS5 for faster search if available
    if check_fts5; then
        sqlite3 "$db_file" "SELECT hash, description FROM hashes_fts;" 2>/dev/null || echo "No hashes found"
    else
        sqlite3 "$db_file" "SELECT hash, description, added FROM hashes ORDER BY added;" 2>/dev/null || echo "No hashes found"
    fi
}

# Function to remove hash
remove_hash() {
    local username="$1"
    local hash="$2"
    
    user_home=$(eval echo ~$username)
    db_file="$user_home/.ssh/authorized_keys.db"
    
    if [ ! -f "$db_file" ]; then
        echo "No hash database found for user '$username'"
        return
    fi
    
    # Remove hash from database
    sqlite3 "$db_file" "DELETE FROM hashes WHERE hash='$hash';"
    
    echo "Removed hash: $hash"
}

# Function to search hashes using FTS
search_hashes() {
    local username="$1"
    local search_term="$2"
    
    user_home=$(eval echo ~$username)
    db_file="$user_home/.ssh/authorized_keys.db"
    
    if [ ! -f "$db_file" ]; then
        echo "No hash database found for user '$username'"
        return
    fi
    
    echo "Searching hashes for user '$username' with term: '$search_term'"
    echo "================================================================"
    
    # Use FTS5 for fast full-text search
    if check_fts5; then
        sqlite3 "$db_file" "SELECT hash, description, added FROM hashes_fts WHERE hashes_fts MATCH '$search_term' ORDER BY rank;" 2>/dev/null || echo "No matches found"
    else
        sqlite3 "$db_file" "SELECT hash, description, added FROM hashes WHERE hash LIKE '%$search_term%' OR description LIKE '%$search_term%' ORDER BY added;" 2>/dev/null || echo "No matches found"
    fi
}

# Function to check if sqlite3 is available
check_sqlite() {
    if ! command -v sqlite3 >/dev/null 2>&1; then
        echo "Error: sqlite3 is required but not installed."
        echo "Install it with: sudo dnf install sqlite"
        exit 1
    fi
}

# Function to check if FTS5 is available
check_fts5() {
    # Check if SQLite3 is available
    if ! command -v sqlite3 >/dev/null 2>&1; then
        return 1
    fi
    
    # Check SQLite version (FTS5 requires SQLite 3.9.0+)
    local version=$(sqlite3 :memory: "SELECT sqlite_version();" 2>/dev/null)
    if [ $? -ne 0 ]; then
        return 1
    fi
    
    # Parse version and check if it's >= 3.9.0
    local major=$(echo "$version" | cut -d. -f1)
    local minor=$(echo "$version" | cut -d. -f2)
    
    if [ "$major" -lt 3 ] || ([ "$major" -eq 3 ] && [ "$minor" -lt 9 ]); then
        return 1
    fi
    
    # Test if FTS5 can be created
    if ! sqlite3 :memory: "CREATE VIRTUAL TABLE test_fts USING fts5(test); DROP TABLE test_fts;" >/dev/null 2>&1; then
        return 1
    fi
    
    return 0
}

# Main script logic
case "${1:-}" in
    "add")
        if [ $# -lt 4 ]; then
            echo "Usage: $0 add <username> <hash> [description]"
            exit 1
        fi
        check_sqlite
        add_hash "$2" "$3" "${4:-}"
        ;;
    "generate")
        if [ $# -lt 3 ]; then
            echo "Usage: $0 generate <username> <key_file>"
            exit 1
        fi
        check_sqlite
        generate_from_file "$2" "$3"
        ;;
    "list")
        if [ $# -lt 2 ]; then
            echo "Usage: $0 list <username>"
            exit 1
        fi
        check_sqlite
        list_hashes "$2"
        ;;
    "remove")
        if [ $# -lt 3 ]; then
            echo "Usage: $0 remove <username> <hash>"
            exit 1
        fi
        check_sqlite
        remove_hash "$2" "$3"
        ;;
    "search")
        if [ $# -lt 3 ]; then
            echo "Usage: $0 search <username> <search_term>"
            exit 1
        fi
        check_sqlite
        search_hashes "$2" "$3"
        ;;
    *)
        echo "Usage: $0 {add|generate|list|remove|search} [args...]"
        echo ""
        echo "Examples:"
        echo "  $0 generate john ~john/.ssh/id_rsa.pub"
        echo "  $0 add john SHA256:abc123... 'John's desktop'"
        echo "  $0 list john"
        echo "  $0 remove john SHA256:abc123..."
        echo "  $0 search john 'laptop'"
        echo ""
        echo "Note: This system uses SQLite database for ultra-fast lookups"
        exit 1
        ;;
esac

Conclusion

This SSH hash-based authentication system provides a practical, secure, and well-designed solution for SSH key management that can scale from small deployments to enterprise environments.

Key Advantages

  • Secure: No plain-text key storage
  • Fast: SQLite binary lookups
  • Scalable: Per-user databases
  • Manageable: Comprehensive management tools
  • Auditable: Complete audit trail
  • Flexible: Supports multiple key types
  • Reliable: Proven in production use

Production Readiness

The system is production-ready and provides significant security and performance benefits over traditional authorized_keys files. The enhanced version adds enterprise-grade features like expiration, audit logging, and rate limiting while maintaining the simplicity and reliability of the original system.

💡 Disclaimer:
This system is designed to be practical, secure, and maintainable - focus on incremental improvements rather than major changes.

0 comments:

Post a Comment

Creative Commons - Attribution 2.5 Generic. Powered by Blogger.

Steganography in Web Standards

Steganography in Web Standards Exploring the use of HTML IDs, UUIDs, and HMAC for cove...

Search This Blog

Translate