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.

Encrypting files using AES keys on SmartCard HSM or Nitrokey HSM2 without key derivation

Table of Contents

  1. Overview
  2. Prerequisites
  3. Installation
  4. Configuration
  5. AES Key Generation
  6. Testing AES Keys
  7. Encryption/Decryption Scripts
  8. Usage Examples
  9. Important Notes
  10. Troubleshooting
  11. Security Considerations
  12. Conclusion

Overview

This guide explains how to use AES keys with your SmartCard HSM (Identiv uTrust 3512) for encryption and decryption operations.

What This Guide Covers

Primary Use Cases:

  • File Encryption/Decryption: Encrypt and decrypt files using AES keys stored on the SmartCard HSM. Files are transferred to the HSM token where encryption/decryption operations are performed, ensuring no sensitive data remains on the host system.
  • Data Protection: Secure sensitive data with hardware-backed AES encryption
  • Key Management: Generate, store, and manage AES keys on the HSM token
  • Multi-Token Deployment: Export and import AES keys across multiple SmartCard HSM tokens

Supported Operations:

  • AES-CBC encryption and decryption
  • AES-CMAC message authentication
  • Key wrapping and unwrapping
  • Key derivation (SP800-56C)

What This Guide Does NOT Cover:

  • RSA or ECC key operations (different guide)
  • Key derivation for file encryption
  • SSH key management (different use case)
  • Certificate operations (separate topic)

Target Applications:

  • Data at Rest Protection: Secure storage of sensitive files and databases with hardware-backed encryption
  • Secure file storage and transmission
  • Database encryption
  • Application-level data protection
  • Compliance requirements (FIPS, Common Criteria)

[WARNING] CRITICAL SECURITY WARNING: Before using AES keys on your SmartCard HSM, you must verify your firmware version. SmartCard HSM devices with firmware versions 3.1 and 3.2 have a critical bug that generates weak AES keys with little to no entropy. These keys must be considered broken and should not be used for any security operations. Read the advisory.

Required Action: SmartCard HSM and Nitrokey HSM2 - Update to firmware version 3.3 or later via the PKI-as-a-Service Portal before generating or using AES keys.

Note: RSA and ECC keys are not affected by this bug. Only AES key generation is impacted.

Prerequisites

Hardware Requirements

  • SmartCard HSM (Identiv uTrust 3512) with firmware version 3.3 or later
  • CCID-compatible card reader
  • USB connection

Software Requirements

  • Linux with PC/SC support
  • SmartCard HSM PKCS#11 library (MANDATORY) - OpenSC PKCS#11 library cannot detect or use AES keys on the token
  • OpenSC tools (pkcs11-tool)
  • SmartCard Shell3 (for AES key generation)

Firmware Verification

Before proceeding, verify your SmartCard HSM firmware version:

pkcs11-tool --module "/home/user/bin/sc-hsm-embedded-2.12/lib/libsc-hsm-pkcs11.so" \
    --show-info

Look for the firmware version in the output. If it shows version 3.1 or 3.2, you must update before using AES keys.

Installation

1. Install Development Tools

# Install development tools
sudo dnf groupinstall "Development Tools"
sudo dnf install autoconf automake libtool
sudo dnf install pcsc-lite-devel

2. Download and Compile SmartCard HSM PKCS#11 Library

[WARNING] CRITICAL: The SmartCard HSM PKCS#11 library is MANDATORY for AES key operations. The standard OpenSC PKCS#11 library cannot detect or use AES keys stored on the SmartCard HSM token.

# Download from GitHub releases
cd /tmp
wget https://github.com/CardContact/sc-hsm-embedded/releases/download/v2.12/\
    sc-hsm-embedded-2.12.tar.gz
tar -xzf sc-hsm-embedded-2.12.tar.gz
cd sc-hsm-embedded-2.12

# Configure and compile
autoreconf -fi
./configure --prefix=/home/user/bin/sc-hsm-embedded-2.12
make
make check
make install

Configuration

Environment Variables

# Set the module path
export SC_HSM_MODULE="/home/user/bin/sc-hsm-embedded-2.12/lib/libsc-hsm-pkcs11.so"
export HSM_SLOT=1

Token Detection

Verify your SmartCard HSM is detected using the SmartCard HSM PKCS#11 library (not OpenSC):

pkcs11-tool --module "/home/user/bin/sc-hsm-embedded-2.12/lib/libsc-hsm-pkcs11.so" \
    --list-token-slots

Expected output:

Available slots:
Slot 0 (0x1): Identiv uTrust 3512 SAM slot Token [CCID Interface] (55512030605)
  token label        : [REDACTED]-HISEC-1
  token manufacturer : CardContact (www.cardcontact.de)
  token model        : SmartCard-HSM
  token flags        : login required, rng, token initialized, PIN initialized

AES Key Generation

Important Note: AES Key Generation Method

AES keys on SmartCard HSM are generated and stored using SmartCard Shell3 software, not through direct PKCS#11 operations. The SmartCard Shell3 provides the interface for creating AES keys on the HSM token.

SmartCard Shell3 Requirements

  • GUI Version: SmartCard Shell3 GUI (scsh3gui) is the primary method for AES key generation
  • CLI Version: SmartCard Shell3 CLI (scsh3) has known card detection issues (see separate documentation)
  • Key Management: Keys are created through the SmartCard Shell3 Key Manager interface

Key Generation Process

  1. Launch SmartCard Shell3 GUI:
    cd /home/user/CardContact/scsh/scsh-3.18.55/
    ./scsh3gui
    
  2. Authenticate to the HSM:
    • Select your SmartCard HSM token
    • Enter your User PIN when prompted
  3. Access Key Manager:
    • Navigate to the Key Manager in the SmartCard Shell3 interface
    • Right-click on the appropriate key domain (e.g., DKEK share)
  4. Generate AES Key (presumably on the DKEK share):
    • Select "Generate Key" → "AES"
    • Choose key size (128, 192, or 256 bits)
    • Provide a label for the key
    • Select algorithms (AES-CBC, AES-CMAC, etc.)

Exporting AES Keys for Multiple Tokens

If you need to use the same AES key across multiple SmartCard HSM tokens that share the same DKEK, you can export the key:

  1. In the SmartCard Shell3 GUI, click on the AES key object in the list (PIN required)
  2. Right-click on the key and select "Wrap Key (and Certificate)"
  3. Save the key as a *.wky file
  4. Import the *.wky file on other tokens with the same DKEK share

Verifying Generated Keys

After generating AES keys via SmartCard Shell3, you can verify them using PKCS#11 tools, as shown below.

Testing AES Keys

List All AES Keys

Use the following script to list all AES keys on your token:

[WARNING] IMPORTANT: You may need to modify the following variables in the script to match your setup:

  • HSM_SLOT: The slot number where your SmartCard HSM is detected
  • SC_HSM_MODULE: Path to your SmartCard HSM PKCS#11 library

Save this script as list_aes_keys.sh:

#!/bin/bash
# list_aes_keys.sh

set -e

# Configuration
SC_HSM_MODULE="/home/user/bin/sc-hsm-embedded-2.12/lib/libsc-hsm-pkcs11.so"
HSM_SLOT=1

echo "=== AES Keys on SmartCard HSM ==="
echo "Library: $SC_HSM_MODULE"
echo "Slot: $HSM_SLOT"
echo

echo "Please enter your SmartCard HSM PIN when prompted:"

# List all secret key objects
echo "=== All Secret Key Objects ==="
pkcs11-tool --module "$SC_HSM_MODULE" \
    --slot $HSM_SLOT \
    --login \
    --list-objects \
    --type secrkey

echo
echo "=== Summary ==="
echo "The above shows all secret key objects on your SmartCard HSM token."
echo "Look for entries starting with 'Secret Key Object; AES' to find AES keys."
echo "Each AES key will show:"
echo "- Label (name)"
echo "- ID (unique identifier)"
echo "- Usage (encrypt, decrypt)"
echo "- Access (security attributes)"
echo

Usage:

chmod +x list_aes_keys.sh
./list_aes_keys.sh

Expected Output:

Secret Key Object; AES length 32
  label:      AES-1
  ID:         b20ba7e28e29c90f
  Usage:      encrypt, decrypt
  Access:     sensitive, always sensitive, never extractable, local
Secret Key Object; AES length 32
  label:      AES-2
  ID:         4eaa8024a063e8b6
  Usage:      encrypt, decrypt
  Access:     sensitive, always sensitive, never extractable, local

Encryption/Decryption Scripts

Simple AES Encryption Test

Save this script as aes_encrypt_simple.sh:

#!/bin/bash
# aes_encrypt_simple.sh

set -e

# Configuration
# [WARNING] IMPORTANT: Modify these variables to match your setup
SC_HSM_MODULE="/home/user/bin/sc-hsm-embedded-2.12/lib/libsc-hsm-pkcs11.so"
HSM_SLOT=1
AES_KEY_ID="b20ba7e28e29c90f"  # Use the ID from your AES key

echo "=== Simple AES-1 Encryption Test ==="
echo "Library: $SC_HSM_MODULE"
echo "Slot: $HSM_SLOT"
echo "Key ID: $AES_KEY_ID"
echo

# Create a test file with exactly 16 bytes (AES block size)
echo "Creating test data..."
echo -n "1234567890123456" > test_block.txt
echo "[INFO] Created test file with 16 bytes (AES block size)"

# Generate IV
echo "Generating IV..."
openssl rand -hex 16 > iv.txt
IV=$(cat iv.txt)
echo "[INFO] IV: $IV"

echo
echo "=== Attempting AES-CBC Encryption ==="
echo "Please enter your SmartCard HSM PIN when prompted:"

# Try encryption with the exact block size
pkcs11-tool --module "$SC_HSM_MODULE" \
    --slot $HSM_SLOT \
    --login \
    --encrypt \
    --mechanism AES-CBC \
    --iv "$IV" \
    --input-file test_block.txt \
    --output-file test_block.enc \
    --id "$AES_KEY_ID"

if [ $? -eq 0 ]; then
    echo "[SUCCESS] Encryption completed!"
    echo "[INFO] Encrypted file: test_block.enc"
    echo "[INFO] IV saved to: iv.txt"
else
    echo "[ERROR] Encryption failed"
fi

echo
echo "=== Test Complete ==="

Simple AES Decryption Test

Save this script as aes_decrypt_simple.sh:

#!/bin/bash
# aes_decrypt_simple.sh

set -e

# Configuration
# [WARNING] IMPORTANT: Modify these variables to match your setup
SC_HSM_MODULE="/home/user/bin/sc-hsm-embedded-2.12/lib/libsc-hsm-pkcs11.so"
HSM_SLOT=1
AES_KEY_ID="b20ba7e28e29c90f"  # Use the ID from your AES key

echo "=== Simple AES-1 Decryption Test ==="
echo "Library: $SC_HSM_MODULE"
echo "Slot: $HSM_SLOT"
echo "Key ID: $AES_KEY_ID"
echo

# Check if encrypted file exists
if [ ! -f test_block.enc ]; then
    echo "[ERROR] Encrypted file not found: test_block.enc"
    echo "Please run aes_encrypt_simple.sh first"
    exit 1
fi

# Check if IV file exists
if [ ! -f iv.txt ]; then
    echo "[ERROR] IV file not found: iv.txt"
    echo "Please run aes_encrypt_simple.sh first"
    exit 1
fi

IV=$(cat iv.txt)
echo "[INFO] Using IV: $IV"

echo
echo "=== Attempting AES-CBC Decryption ==="
echo "Please enter your SmartCard HSM PIN when prompted:"

# Try decryption
pkcs11-tool --module "$SC_HSM_MODULE" \
    --slot $HSM_SLOT \
    --login \
    --decrypt \
    --mechanism AES-CBC \
    --iv "$IV" \
    --input-file test_block.enc \
    --output-file test_block.dec \
    --id "$AES_KEY_ID"

if [ $? -eq 0 ]; then
    echo "[SUCCESS] Decryption completed!"
    echo "[INFO] Decrypted file: test_block.dec"
    
    # Verify decryption
    if diff test_block.txt test_block.dec > /dev/null; then
        echo "[SUCCESS] Decryption verified - files match!"
    else
        echo "[ERROR] Decryption failed - files don't match"
    fi
else
    echo "[ERROR] Decryption failed"
fi

echo
echo "=== Test Complete ==="

Complete AES Key Usage Script

Save this script as aes_encrypt_decrypt.sh:

#!/bin/bash

# Use the AES-1 key on SmartCard HSM for encryption/decryption
# The key was found with the new SmartCard HSM PKCS#11 library

set -e

# Configuration
# [WARNING] IMPORTANT: Modify these variables to match your setup
SC_HSM_MODULE="/home/user/bin/sc-hsm-embedded-2.12/lib/libsc-hsm-pkcs11.so"
HSM_SLOT=1
AES_KEY_LABEL="AES-1"  # Use the label from your AES key
AES_KEY_ID="b20ba7e28e29c90f"  # Use the ID from your AES key

echo "=== Using AES-1 Key on SmartCard HSM ==="
echo "Library: $SC_HSM_MODULE"
echo "Slot: $HSM_SLOT"
echo "Key Label: $AES_KEY_LABEL"
echo "Key ID: $AES_KEY_ID"
echo

# Function to encrypt a file using AES-1 key
encrypt_file() {
    local input_file="$1"
    local output_file="$2"
    
    echo "=== Encrypting with AES-1 Key ==="
    echo "Input: $input_file"
    echo "Output: $output_file"
    echo "Please enter your SmartCard HSM PIN when prompted:"
    
    # Generate a random IV (16 bytes for AES)
    echo "[INFO] Generating random IV..."
    openssl rand -hex 16 > iv.txt
    IV=$(cat iv.txt)
    echo "[INFO] IV: $IV"
    
    # Use AES-CBC encryption with the AES-1 key and IV
    pkcs11-tool --module "$SC_HSM_MODULE" \
        --slot $HSM_SLOT \
        --login \
        --encrypt \
        --mechanism AES-CBC \
        --iv "$IV" \
        --input-file "$input_file" \
        --output-file "$output_file" \
        --id "$AES_KEY_ID"
    
    if [ $? -eq 0 ]; then
        echo "[SUCCESS] File encrypted successfully"
        echo "[INFO] IV saved to iv.txt for decryption"
    else
        echo "[ERROR] Encryption failed"
        rm -f iv.txt
        return 1
    fi
}

# Function to decrypt a file using AES-1 key
decrypt_file() {
    local input_file="$1"
    local output_file="$2"
    local iv_file="${3:-iv.txt}"
    
    echo "=== Decrypting with AES-1 Key ==="
    echo "Input: $input_file"
    echo "Output: $output_file"
    echo "IV file: $iv_file"
    echo "Please enter your SmartCard HSM PIN when prompted:"
    
    if [ ! -f "$iv_file" ]; then
        echo "[ERROR] IV file not found: $iv_file"
        echo "Please provide the IV used for encryption"
        return 1
    fi
    
    IV=$(cat "$iv_file")
    echo "[INFO] Using IV: $IV"
    
    # Use AES-CBC decryption with the AES-1 key and IV
    pkcs11-tool --module "$SC_HSM_MODULE" \
        --slot $HSM_SLOT \
        --login \
        --decrypt \
        --mechanism AES-CBC \
        --iv "$IV" \
        --input-file "$input_file" \
        --output-file "$output_file" \
        --id "$AES_KEY_ID"
    
    if [ $? -eq 0 ]; then
        echo "[SUCCESS] File decrypted successfully"
    else
        echo "[ERROR] Decryption failed"
        return 1
    fi
}

# Function to test the AES-1 key with a simple operation
test_aes1_key() {
    echo "=== Testing AES-1 Key ==="
    echo "Please enter your SmartCard HSM PIN when prompted:"
    
    # Try to get key info
    pkcs11-tool --module "$SC_HSM_MODULE" \
        --slot $HSM_SLOT \
        --login \
        --list-objects \
        --type secrkey \
        --id "$AES_KEY_ID"
}

# Function to show usage
show_usage() {
    echo "Usage: $0 {encrypt|decrypt|test} [ ]"
    echo
    echo "Examples:"
    echo "  $0 test                           # Test AES-1 key"
    echo "  $0 encrypt test.txt test.txt.enc  # Encrypt file"
    echo "  $0 decrypt test.txt.enc test.txt.dec [iv.txt]  # Decrypt file"
    echo
    echo "The script uses the AES-1 key stored on your SmartCard HSM"
    echo "for AES-CBC encryption/decryption operations."
}

# Main script logic
case "${1:-}" in
    encrypt)
        if [ $# -ne 3 ]; then
            echo "[ERROR] Usage: $0 encrypt  "
            exit 1
        fi
        encrypt_file "$2" "$3"
        ;;
    decrypt)
        if [ $# -lt 3 ] || [ $# -gt 4 ]; then
            echo "[ERROR] Usage: $0 decrypt   [iv_file]"
            exit 1
        fi
        decrypt_file "$2" "$3" "${4:-}"
        ;;
    test)
        test_aes1_key
        ;;
    *)
        show_usage
        exit 1
        ;;
esac

echo
echo "=== Operation Complete ==="

Usage Examples

1. List AES Keys

./list_aes_keys.sh

2. Test Simple Encryption/Decryption

# Encrypt
./aes_encrypt_simple.sh

# Decrypt
./aes_decrypt_simple.sh

3. Use Complete Script

# Test key
./aes_encrypt_decrypt.sh test

# Encrypt file
./aes_encrypt_decrypt.sh encrypt myfile.txt myfile.txt.enc

# Decrypt file
./aes_encrypt_decrypt.sh decrypt myfile.txt.enc myfile.txt.dec

Important Notes

Data Requirements

  • Block Size: Data must be padded to AES block size (16 bytes)
  • IV Required: CBC mode requires an Initialization Vector
  • PIN Authentication: All operations require SmartCard HSM PIN

Security Features

  • Hardware Protection: Keys never leave the HSM in plaintext
  • PIN Protection: All operations require authentication
  • Key Export: Keys can be exported only between tokens sharing the same DKEK share

Supported Operations

  • AES-CBC Encryption/Decryption
  • AES-CMAC Signing
  • AES Key Generation (via SmartCard Shell3 GUI only)

Firmware Security Considerations

[WARNING] Critical HSM Firmware Bug (Versions 3.1-3.2):

  • SmartCard HSM and Nitrokey HSM2 devices with firmware versions 3.1 and 3.2 generate weak AES keys. Read the advisory.
  • These keys have little to no entropy and must be considered broken
  • Impact: Only affects AES key generation, not RSA or ECC keys
  • Solution: Be sure the tokens are running the latest firmware version. Check if newer version is available at PKI-as-a-Service-Portal, and install it before generating or using AES keys.

Update Process:

  1. Register an account at CardContact Developer Network (CDN)
  2. Create a firmware update request
  3. Select "Current token in reader" and submit
  4. Follow the portal instructions for the update process
  5. Important: All keys must be removed before firmware update
  6. After update, reinitialize the device and restore keys

Troubleshooting

Common Issues

  1. "CKR_FUNCTION_NOT_SUPPORTED"
    • Ensure data is padded to 16-byte blocks
    • Use exact block size for testing
  2. "CKR_USER_NOT_LOGGED_IN"
    • Enter PIN when prompted
    • Don't redirect output during PIN prompts
  3. "CKR_SLOT_ID_INVALID"
    • Use slot 1 instead of slot 0
    • Verify token is present

Security Considerations

Direct AES Storage vs Key Derivation

Aspect Direct AES Storage HSM Key Derivation
Key Storage AES key stored on HSM Salt stored on disk, key derived on-demand
Intermediate Data None Salt file, derived key file
Cold Boot Vulnerability Low (no temp files) Medium (temp files on disk)
Performance Fast (direct operations) Slower (derivation + operation)
Flexibility Fixed key Variable key from passphrase

Best Practices

  1. Authentication: Always authenticate before operations
  2. Data Padding: Ensure data fits AES block requirements
  3. IV Management: Use random IVs and store them securely
  4. Error Handling: Check return codes and handle failures
  5. Cleanup: Remove temporary files securely

Conclusion

This guide provides a complete workflow for using AES keys with your SmartCard HSM. The approach offers hardware-level security with direct AES operations, making it suitable for high-security applications where key protection is critical.

Technical Summary

  1. AES Key Generation: SmartCard Shell3 GUI required for key generation; PKCS#11 direct operations not supported
  2. Firmware Security: Version 3.3+ mandatory due to weak key generation vulnerability in versions 3.1-3.2
  3. Security Architecture: Hardware-protected AES keys with mandatory PIN authentication for all operations
  4. CLI Implementation: SmartCard Shell3 CLI exhibits card detection failures due to initialization sequence differences
  5. Firmware Update Protocol: Requires complete key removal and device reinitialization post-update

SmartCard Shell3 3.18.72 CLI - HSM Card Detection Issue

This document provides a comprehensive technical analysis of the SmartCard Shell3 3.18.72 CLI card detection issue, where the CLI cannot detect the SmartCard HSM card despite the card being present and accessible via PC/SC and GUI interfaces.

Table of Contents

Problem Statement

The SmartCard Shell3 3.18.72 CLI cannot detect the SmartCard HSM card, even though the card is present and accessible via PC/SC and GUI.

This issue prevents the CLI from performing any card operations, including authentication, object inspection, and key management, while the GUI works perfectly with the same card and scripts.

System Environment

Hardware Configuration

  • SmartCard HSM: Identiv uTrust 3512 SAM slot Token
  • Interface: CCID Interface (55512033274236)
  • Status: Card inserted, Shared Mode
  • Manufacturer: CardContact (www.cardcontact.de)
  • Token Label: LABEL-1

Operating System

  • OS: Linux 5.14.0-570.26.1.el9_6.x86_64
  • Distribution: Red Hat Enterprise Linux 9 (RHEL 9)
  • User: [REDACTED]
  • Working Directory: /home/user/tmp/test-05

Key Components

  • PC/SC Daemon: pcscd V 1.6.2 (Running)
  • PC/SC Library: /usr/lib64/libpcsclite.so
  • SmartCard Shell3: Version 3.18.72 at /home/user/CardContact/scsh/scsh-3.18.72/
  • SmartCard HSM PKCS#11 Library: Version 2.12 at /home/user/bin/sc-hsm-embedded-2.12/
  • Java: OpenJDK with SmartCard support

Evidence

1. Card is Present and Accessible

PC/SC can detect the card:

$ pcsc_scan Reader 0: Identiv uTrust 3512 SAM slot Token [CCID Interface] (55512033274236) 00 00 Card state: Card inserted, Shared Mode ATR: 3B DE 96 FF 81 91 FE 1F C3 80 31 81 54 48 53 4D 31 73 80 21 40 81 07 92

PKCS#11 library can access the card:

$ pkcs11-tool --module /home/user/bin/sc-hsm-embedded-2.12/lib/libsc-hsm-pkcs11.so --list-token-slots Available slots: Slot 0 (0x1): Identiv uTrust 3512 SAM slot Token [CCID Interface] (55512033274) token label : LABEL-1 token manufacturer : CardContact (www.cardcontact.de) token model : SmartCard-HSM

2. GUI Can Access and Edit Card Objects

GUI successfully connects and performs all operations:

$ ./scsh3gui # GUI successfully connects to card, authenticates, and can: # - View all stored objects (keys, certificates, data) # - Generate new keys # - Import/export certificates # - Modify card contents # - Perform all card operations normally

3. CLI Cannot Detect the Card

Error when trying to access card via CLI:

$ echo 'var card = new Card(); print(card.isCardPresent());' | java -Dsun.security.smartcardio.t1GetResponse=false -Dorg.bouncycastle.asn1.allow_unsafe_integer=true -Djava.library.path=./lib -classpath 'lib/*' de.cardcontact.scdp.engine.CommandProcessor

GPError: Card (CARD_CONNECT_FAILED/0) - "No card in reader or mute card."

Root Cause Analysis

1. Different Main Classes

CLI Startup:

java -Dsun.security.smartcardio.t1GetResponse=false -Dorg.bouncycastle.asn1.allow_unsafe_integer=true -Djava.library.path=./lib -classpath 'lib/*' de.cardcontact.scdp.engine.CommandProcessor

GUI Startup:

java -Dsun.security.smartcardio.t1GetResponse=false -Dorg.bouncycastle.asn1.allow_unsafe_integer=true -Djava.library.path=./lib -classpath 'lib/*' de.cardcontact.scdp.scsh3.GUIShell

Key Difference: CommandProcessor vs GUIShell

2. Different Initialization Sequences

GUI Initialization:

  1. Changes working directory: cd $(dirname $0)
  2. Loads opencard.properties configuration
  3. Initializes SmartCardIO factory: de.cardcontact.opencard.terminal.smartcardio.SmartCardIOFactory
  4. Sets up card service factories

CLI Initialization:

  1. No working directory change
  2. May not load opencard.properties
  3. May not initialize card services properly
  4. Different card access method

3. OpenCard Framework Configuration

GUI uses opencard.properties:

OpenCard.terminals = de.cardcontact.opencard.terminal.smartcardio.SmartCardIOFactory OpenCard.services = de.cardcontact.opencard.factory.SmartCardHSMCardServiceFactory

CLI may not load this configuration properly.

Technical Details

Card Access Methods

  1. PC/SC (Working): Direct PC/SC library access
  2. PKCS#11 (Working): SmartCard HSM PKCS#11 library
  3. SmartCard Shell3 GUI (Working): OpenCard Framework with SmartCardIO
  4. SmartCard Shell3 CLI (Broken): OpenCard Framework with different initialization

Error Analysis

Error: "No card in reader or mute card"

Possible Causes:

  1. Card Service Not Initialized: CLI doesn't load the proper card service factory
  2. Terminal Factory Not Configured: CLI doesn't use SmartCardIO factory
  3. Working Directory Issue: CLI doesn't change to proper directory
  4. Class Loading Issue: CLI can't load required card access classes

Attempted Solutions

1. Set PCSC Library Path

java -Dsun.security.smartcardio.library=/usr/lib64/libpcsclite.so ...

Result: ❌ Still fails

2. Set OpenCard Properties

java -DOpenCard.terminals="de.cardcontact.opencard.terminal.smartcardio.SmartCardIOFactory" ...

Result: ❌ Still fails

3. Kill Competing Processes

pkill -f "ssh-pkcs11-help"

Result: ❌ Still fails

4. Use GUI Working Directory

cd $(dirname $0) && java ...

Result: ❌ Class loading fails

Working Solutions

1. Hybrid Approach (Recommended)

Use GUI for card access, CLI for scripting:

# Terminal 1: Start GUI and authenticate ./scsh3gui # Select token, load keymanager, enter PIN # Keep GUI running # Terminal 2: Run CLI script echo 'load("test_card_script.js")' | java -Dsun.security.smartcardio.t1GetResponse=false -Dorg.bouncycastle.asn1.allow_unsafe_integer=true -Djava.library.path=./lib -classpath 'lib/*' de.cardcontact.scdp.engine.CommandProcessor

2. Pure GUI Approach

Use GUI directly for all operations:

./scsh3gui # Select token, load keymanager, enter PIN # Navigate to card objects # Right-click → Perform card operations

Impact on Card Operations

Since CLI cannot detect the card, it cannot:

  • Create card connections
  • Authenticate to the HSM
  • Perform any card operations
  • Access existing objects on the card

Any card operations via CLI are impossible until the card detection issue is resolved.

Conclusion

The CLI cannot detect the card because:

  1. Different Main Class: CommandProcessor vs GUIShell
  2. Different Initialization: CLI doesn't load proper card services
  3. Different Configuration: CLI may not use opencard.properties
  4. Different Working Directory: CLI doesn't change directory like GUI

The fundamental issue is that the CLI's card access initialization is broken or incomplete compared to the GUI.

Solution: Use the hybrid approach where GUI provides card access context for CLI scripting, or use GUI directly for all operations.

Summary

  • Hardware: Identiv uTrust 3512 SmartCard HSM with CCID interface
  • OS: RHEL 9 with PC/SC infrastructure
  • Tools: SmartCard Shell3 3.18.72 + PKCS#11 library 2.12
  • Status: GUI working, CLI broken for card detection
  • Issue: CLI cannot detect card due to different initialization
  • Solution: Use hybrid approach (GUI + CLI) or GUI only
Creative Commons - Attribution 2.5 Generic. Powered by Blogger.

IPv6 on MikroTik LTE/5G (RouterOS 7) with Vivacom (Bulgaria)

About Vivacom products and prefixes Topology assumed here Method: bind PDP IPv6 to the LAN port 1. APN profiles 2. Point the LTE...

Search This Blog

Translate