Skip to content

Comments

security: replace SHA-256 password hashing with PBKDF2#659

Open
mmcintosh wants to merge 2 commits intoSonicJs-Org:mainfrom
mmcintosh:hotfix/password-hashing-pbkdf2
Open

security: replace SHA-256 password hashing with PBKDF2#659
mmcintosh wants to merge 2 commits intoSonicJs-Org:mainfrom
mmcintosh:hotfix/password-hashing-pbkdf2

Conversation

@mmcintosh
Copy link
Contributor

Security: Replace SHA-256 Password Hashing with PBKDF2

Summary

Replaces the insecure SHA-256 password hashing with PBKDF2 (100,000 iterations, random 16-byte salt, constant-time comparison). Existing users are transparently migrated to the new format on their next login -- no downtime or forced password resets required.

Changes

1. PBKDF2 Password Hashing

  • 100,000 iterations with random 16-byte salt via Web Crypto API (crypto.subtle.deriveBits)
  • Hash format: pbkdf2:<iterations>:<salt_hex>:<hash_hex>
  • Each hash is unique even for the same password (random salt)
  • Tuned for Cloudflare Workers CPU limits

2. Backward-Compatible Verification

  • verifyPassword() auto-detects hash format (PBKDF2 vs legacy SHA-256)
  • Legacy SHA-256 hashes (no pbkdf2: prefix) continue to work
  • Constant-time comparison on both paths to prevent timing attacks

3. Transparent Hash Migration

  • On successful login, if the stored hash is legacy SHA-256, it is silently re-hashed to PBKDF2
  • Applied to both /auth/login (API) and /auth/login/form (browser)
  • Migration is non-fatal -- if re-hashing fails, login still succeeds

4. Updated Unit Tests

  • Tests verify PBKDF2 format (pbkdf2:100000:...)
  • Tests confirm random salt produces unique hashes for same password
  • Tests verify legacy SHA-256 hashes still pass verification
  • Tests for isLegacyHash() detection

Technical Details

Core Changes:

  • packages/core/src/middleware/auth.ts -- PBKDF2 hashPassword(), legacy hashPasswordLegacy(), updated verifyPassword() with format auto-detect and constant-time comparison, isLegacyHash() helper
  • packages/core/src/routes/auth.ts -- Transparent re-hash on login (both API and form routes)

Updated Tests:

  • packages/core/src/__tests__/middleware/auth.test.ts -- 8 tests covering PBKDF2 format, random salt uniqueness, legacy verification, isLegacyHash()

Other:

  • my-sonicjs-app/scripts/seed-admin.ts -- Updated to use PBKDF2 hashing

Testing

  • Unit Tests: PASSED (8 auth tests updated/added)
  • E2E Tests: PASSED (3/3 shards green)
  • Login and register flows verified with new hashing

Performance Impact

Operation Before After
Hash generation ~instant (SHA-256) ~50-100ms (PBKDF2 100k iterations)
Login verification ~instant ~50-100ms
Security level Weak (unsalted SHA-256) Strong (salted PBKDF2)

Breaking Changes

None. Fully backward compatible with existing SHA-256 hashes.

Migration Notes

  • Zero-downtime: Existing SHA-256 hashes continue to work. Users are silently upgraded to PBKDF2 on next login.
  • No database migration: The password_hash column is a text field that accepts the new longer format.
  • Rollback risk: If reverted, users who logged in after the change will have PBKDF2 hashes the old code can't verify. They would need password resets via seed-admin.

Known Issues

None.

Demo / Screenshots

N/A -- no UI changes.

Related Issues

(Security hardening -- no linked issue)

Checklist

  • Code follows project coding standards
  • Tests added/updated and passing
  • Documentation updated
  • No breaking changes
  • Backward compatible

- Use PBKDF2-SHA256 with 600,000 iterations and per-user random salt
- Store as pbkdf2:<iterations>:<salt_hex>:<hash_hex> format
- Backwards compatible: legacy SHA-256 hashes still verify on login
- Transparent migration: re-hash to PBKDF2 on successful login
- Constant-time comparison for both PBKDF2 and legacy verification
- Update seed-admin.ts to use new PBKDF2 hashing
- Update unit tests for new hash format and legacy compatibility

Fixes VULN-002
@mmcintosh mmcintosh changed the title Hotfix/password hashing pbkdf2 security: replace SHA-256 password hashing with PBKDF2 Feb 20, 2026
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.

1 participant