Skip to content

Security: Migrate API key hashing to argon2id (#445)#492

Merged
filthyrake merged 2 commits intodevfrom
feature/445-argon2-api-key-hashing
Jan 3, 2026
Merged

Security: Migrate API key hashing to argon2id (#445)#492
filthyrake merged 2 commits intodevfrom
feature/445-argon2-api-key-hashing

Conversation

@filthyrake
Copy link
Copy Markdown
Owner

Summary

Migrates worker API key hashing from SHA-256 to argon2id, addressing the security improvement identified in #445. This provides defense-in-depth against brute-force attacks by using a memory-hard algorithm.

Key Changes

  • Add argon2-cffi dependency for secure password hashing
  • Add hash_version column to support dual-format verification during migration
  • New keys automatically use argon2id (version 2)
  • Legacy SHA-256 keys (version 1) continue to work for backward compatibility
  • Add shared authenticate_api_key() helper to eliminate code duplication
  • Fix admin.py reencode endpoints to use prefix-based lookup (argon2 hashes are non-deterministic)

Security Highlights

  • argon2id with OWASP-recommended parameters (time_cost=3, memory_cost=64MB, parallelism=4)
  • Unknown hash versions fail closed (don't default to legacy)
  • InvalidHash exceptions handled gracefully without information leakage
  • Backward compatible - existing workers continue working without changes

Database Migration

Migration 026 adds hash_version column and widens key_hash from 64 to 255 chars to accommodate argon2 hashes.

Test plan

  • All 83 worker API tests pass
  • New TestHashVersioning test class with 8 test cases
  • Tests cover: argon2 verification, SHA-256 legacy verification, cross-version security, unknown version handling, invalid hash handling
  • Ruff lint and format checks pass

Closes #445

🤖 Generated with Claude Code

filthyrake and others added 2 commits January 3, 2026 10:56
Replace SHA-256 with argon2id for API key hashing to provide defense-in-depth
against brute-force attacks. This addresses Issue #445.

Changes:
- Add argon2-cffi dependency for memory-hard password hashing
- Add hash_version column to track algorithm (1=SHA-256 legacy, 2=argon2id new)
- Update worker_auth.py with dual-format verification support
  - New keys use argon2id automatically
  - Legacy SHA-256 keys continue to work
  - Unknown versions fail closed for security
- Add authenticate_api_key() shared helper to eliminate code duplication
- Fix admin.py reencode endpoints to use prefix-based lookup
  (required because argon2 hashes are non-deterministic)
- Add comprehensive tests for hash versioning

Security notes:
- argon2id with OWASP-recommended parameters (time_cost=3, memory_cost=64MB)
- Timing-safe comparison preserved for legacy SHA-256 verification
- InvalidHash exceptions caught and logged without leaking info
- Backward compatible - existing workers continue working

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Reliability improvements per Margo's review:
- Fix prefix collision: authenticate_api_key now fetches ALL matching
  prefixes and iterates through candidates (fetch_one -> fetch_all)
- Add API key length validation: keys must be >= 8 chars for prefix extraction
- Add key_prefix to error logs for better debugging context
- Add build dependencies (gcc, libffi-dev) to Dockerfiles for argon2-cffi

Migration improvements:
- Add deployment sequence documentation
- Add post-migration verification queries
- Add explicit downgrade check that raises RuntimeError if argon2 keys exist
- Document that downgrade is destructive

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@filthyrake filthyrake merged commit 520aa01 into dev Jan 3, 2026
6 checks passed
@filthyrake filthyrake deleted the feature/445-argon2-api-key-hashing branch January 3, 2026 19:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security: API key hashing uses SHA-256 without per-key salt

1 participant