Security & Credentials Guide

Guide to credential management, encryption, and security best practices


Overview

The bot implements comprehensive security measures to protect your API credentials:

  • AES-256-GCM encryption for stored credentials
  • PBKDF2 key derivation with 100,000 iterations
  • Automatic secret redaction in logs
  • Secure memory handling for sensitive data

How It Works

Your exchange API credentials are encrypted using:

  1. Your passphrase → converted to encryption key via PBKDF2
  2. AES-256-GCM → industry-standard authenticated encryption
  3. Unique salt → stored separately for additional security
┌─────────────────┐     ┌─────────────────┐
│  Your Passphrase │────►│  PBKDF2 (100k)  │────► Encryption Key
└─────────────────┘     └─────────────────┘
                                │
┌─────────────────┐             ▼
│ API Credentials │────► AES-256-GCM ────► credentials.encrypted.json
└─────────────────┘

Setup

# 1. Set your passphrase
export CREDENTIALS_PASSPHRASE="your-secure-passphrase-here"

# 2. Create the encrypted credentials file
wealth credentials create

# 3. Add your exchange credentials (interactive mode - recommended)
wealth credentials add binance
wealth credentials add bybit
wealth credentials add hyperliquid

# Alternative: Add credentials with CLI args (less secure - appears in shell history)
# wealth credentials add binance --api-key YOUR_API_KEY --secret-key YOUR_SECRET_KEY

# 4. Verify everything works
wealth credentials verify

Security Note: Always prefer the interactive mode (without --api-key and --secret-key flags) as it prevents credentials from appearing in your shell history. The bot will prompt you securely for these values.

Files Created

FilePurposeSecurity
credentials.encrypted.jsonEncrypted credentialsAES-256-GCM encrypted
.credentials.saltEncryption saltRequired for decryption

⚠️ Both files are required for decryption. Back them up securely!

Production Best Practices

File Permissions

# Set restrictive permissions
chmod 600 credentials.encrypted.json
chmod 600 .credentials.salt

# Verify
ls -la credentials.encrypted.json .credentials.salt
# Should show: -rw------- (owner read/write only)

Passphrase Management

DO NOT:

  • ❌ Store passphrase in plain text files
  • ❌ Commit passphrase to version control
  • ❌ Share passphrase in chat/email
  • ❌ Use weak passphrases (< 12 characters)
  • ❌ Reuse passphrases across environments

DO:

  • ✅ Use secrets management systems (AWS Secrets Manager, HashiCorp Vault)
  • ✅ Use strong passphrases (12+ characters, mixed case, numbers, symbols)
  • ✅ Rotate passphrases regularly
  • ✅ Different passphrases for testnet/mainnet
  • ✅ Audit access to encrypted files

Examples:

# AWS Secrets Manager
export CREDENTIALS_PASSPHRASE="$(aws secretsmanager get-secret-value \
    --secret-id prod/wealth/passphrase \
    --query SecretString \
    --output text)"

# HashiCorp Vault
export CREDENTIALS_PASSPHRASE="$(vault kv get \
    -field=passphrase \
    secret/wealth/prod)"

# Kubernetes Secret
export CREDENTIALS_PASSPHRASE="$(kubectl get secret wealth-passphrase \
    -o jsonpath='{.data.passphrase}' | base64 -d)"

Version Control

Add to .gitignore:

# Encrypted credentials
credentials.encrypted.json
.credentials.salt

# Environment files
.env
.env.local
.env.*.local

Backup Strategy

What to backup:

  1. .credentials.salt - CRITICAL - Cannot decrypt without this
  2. credentials.encrypted.json - Encrypted credentials
  3. Passphrase - Store separately in secrets manager

Backup procedure:

# Backup to secure storage
aws s3 cp .credentials.salt s3://secure-backups/wealth/$(date +%Y%m%d)/
aws s3 cp credentials.encrypted.json s3://secure-backups/wealth/$(date +%Y%m%d)/

Docker Deployment

Docker Compose:

services:
  wealth:
    build: .
    environment:
      - CREDENTIALS_PASSPHRASE=${CREDENTIALS_PASSPHRASE}
    secrets:
      - credentials_passphrase
    volumes:
      - ./credentials.encrypted.json:/app/credentials.encrypted.json:ro
      - ./.credentials.salt:/app/.credentials.salt:ro

secrets:
  credentials_passphrase:
    external: true

Kubernetes Deployment

Create Secrets:

# Create from files
kubectl create secret generic wealth-credentials \
    --from-file=credentials.encrypted.json \
    --from-file=.credentials.salt

# Create passphrase secret
kubectl create secret generic wealth-passphrase \
    --from-literal=passphrase='your_secure_passphrase'

Deployment manifest:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wealth-bot
spec:
  template:
    spec:
      containers:
      - name: wealth
        image: wealth:latest
        env:
        - name: CREDENTIALS_PASSPHRASE
          valueFrom:
            secretKeyRef:
              name: wealth-passphrase
              key: passphrase
        volumeMounts:
        - name: credentials
          mountPath: /app/credentials.encrypted.json
          subPath: credentials.encrypted.json
          readOnly: true
        - name: credentials
          mountPath: /app/.credentials.salt
          subPath: .credentials.salt
          readOnly: true
      volumes:
      - name: credentials
        secret:
          secretName: wealth-credentials
          defaultMode: 0400

Security Audit Checklist

Before Production Deployment:

  • Credentials encrypted with strong passphrase (12+ chars)
  • Salt file exists and backed up (.credentials.salt)
  • File permissions set to 600
  • Encrypted files excluded from version control
  • Passphrase stored securely (not in plaintext files)
  • No hardcoded credentials
  • Verification test passed (wealth credentials verify)
  • Backup strategy implemented
  • Credential rotation procedure documented

Troubleshooting

"Failed to decrypt credentials" error:

  • Check passphrase is correct
  • Verify .credentials.salt file exists and hasn't been modified
  • Ensure credentials.encrypted.json hasn't been corrupted

"Salt file not found" error:

  • Verify .credentials.salt file exists in current directory
  • Check file permissions (should be readable)
  • Restore from backup if missing

Secrets still appearing in logs:

  • Verify using Secret<String> wrapper types
  • Check custom Debug implementation is present
  • Grep logs for credential patterns: grep -i "api_key" logs/
  • Should only show [REDACTED]

See Also