Authority Password Hashing Guidance
Audience: Authority operators, Security Guild, identity-plugin authors. Scope: How Stella Ops Authority hashes and verifies credentials, the recommended Argon2id parameters per environment, and how to configure and observe the rollout. Status: Drafted 2025-10-11 alongside SEC1.A / SEC1.PLG rollout; reconciled against source 2026-05-30. Argon2id is the default hashing algorithm for the Standard plug-in and is recommended for all Authority identity providers.
1. Overview
Stella Ops Authority issues and verifies credentials through the shared StellaOps.Cryptography provider abstraction. As of October 2025:
- Default algorithm: Argon2id (PHC format
$argon2id$v=19$m=<mem>,t=<time>,p=<parallelism>$<salt>$<hash>), emitted byArgon2idPasswordHasherinStellaOps.Cryptography. The salt is 16 bytes and the derived hash is 32 bytes (Base64-encoded in the PHC string). - Legacy support: PBKDF2-SHA256 hashes (custom encoding
PBKDF2.<iterations>.<base64-payload>, where the payload is0x01 || 16-byte salt || 32-byte hash) continue to verify viaPbkdf2PasswordHasher, but successful logins are transparently rehashed to Argon2id whenever the stored algorithm/parameters differ from the configured target. - Configuration path: Each identity plug-in carries its own
passwordHashingblock. For the Standard plug-in this lives at the root ofetc/authority/plugins/standard.yamland binds toStandardPluginOptions.PasswordHashing. The Standard plug-in’sCryptoPasswordHasheruses only the plug-in’s ownpasswordHashingvalues when hashing and rehashing.
Note (verified against source 2026-05-30): The host-level
authority.security.passwordHashingblock (AuthoritySecurityOptions.PasswordHashinginStellaOps.Configuration) is bound and validated, but is not currently consumed as a system-wide default that propagates to plug-ins. The Standard plug-in does not read the host block; there is no inheritance/override merge today. Treat the per-plug-inpasswordHashingblock as the operative configuration. See §3.
2. Recommended Parameters
| Environment | memorySizeInKib | iterations | parallelism | Notes |
|---|---|---|---|---|
| Production (default) | 19456 | 2 | 1 | Balances CPU with 19 MiB memory cost; ~175 ms on 4 vCPU host. |
| High-security enclave | 32768 | 3 | 1 | Increases memory pressure; confirm capacity on shared hosts. |
| Resource-constrained lab | 8192 | 2 | 1 | Use only for bootstrap/testing; increase once hardware upgraded. |
| PBKDF2 fallback | — | ≥210000 | — | Set algorithm: Pbkdf2 only when Argon2 hardware support unavailable. |
⚠️ Lowering parameters below these baselines should be a temporary measure. Document any deviations in runbooks and schedule follow-up work to restore defaults.
Defaults caveat: The Production-row values match the
PasswordHashOptionscode defaults (memorySizeInKib = 19456,iterations = 2,parallelism = 1), which apply to Argon2id. There is no algorithm-specific default for PBKDF2 —PasswordHashOptions.Iterationsdefaults to2regardless of algorithm, so when you setalgorithm: Pbkdf2you must explicitly raiseiterationsto a PBKDF2-appropriate value (the ≥210000 OWASP guidance above); leaving it at the Argon2id default of 2 would be insecure for PBKDF2. The~175 ms on 4 vCPUfigure is an operational estimate, not a value asserted by the code or tests.
3. Configuring Authority Defaults
authority.yaml (or equivalent) accepts the following block, which binds to AuthoritySecurityOptions.PasswordHashing:
security:
passwordHashing:
algorithm: Argon2id # Argon2id | Pbkdf2 (the PasswordHashAlgorithm enum)
memorySizeInKib: 19456 # ~19 MiB
iterations: 2
parallelism: 1
⚠️ This host block is validated but not yet propagated to plug-ins. As of the current implementation (
StellaOps.Configuration.AuthoritySecurityOptions), the only consumer of this block is its ownValidate()call at host startup. The Standard plug-in’sCryptoPasswordHasherreadsStandardPluginOptions.PasswordHashing— the plug-in’s ownpasswordHashingblock (§4) — and never the host block. To change the effective hashing parameters today, edit the per-plug-in block, not this one. Wiring host-default propagation/override is open follow-up work.
Runtime validation (PasswordHashOptions.Validate()) ensures memorySizeInKib, iterations, and parallelism are all strictly greater than 0; each failing value throws an InvalidOperationException with a specific message (memory cost / iterations / parallelism). The algorithm is a closed enum (Argon2id | Pbkdf2), so any unrecognised value fails at configuration-binding time rather than in Validate().
4. Plug-in Configuration
The Standard plug-in’s hashing parameters are set entirely by its own passwordHashing block (at the root of standard.yaml), which binds to StandardPluginOptions.PasswordHashing:
passwordHashing:
algorithm: Argon2id
memorySizeInKib: 8192
iterations: 2
parallelism: 1
- When the plug-in configuration omits
passwordHashing, the code defaults ofPasswordHashOptionsapply (Argon2id,memorySizeInKib = 19456,iterations = 2,parallelism = 1) — not values inherited from the hostsecurity.passwordHashingblock (see §3). The repositorystandard.yamlships these same Argon2id defaults explicitly. - Setting
algorithm: Pbkdf2makes the plug-in hash new passwords with PBKDF2-SHA256 as well as verify legacy PBKDF2 hashes. Existing PBKDF2 hashes are only flagged for rehash when their iteration count differs from the configured target (Pbkdf2PasswordHasher.NeedsRehash). When the target is Argon2id, any PBKDF2 hash that verifies is immediately rehashed to Argon2id (CryptoPasswordHasher.Verify). - Invalid values (e.g.,
memorySizeInKib: 0) cause startup to fail:StandardPluginOptions.ValidatecallsPasswordHashing.Validate(), which throws a descriptiveInvalidOperationException.
5. Observability & Migration
Reconciled against source 2026-05-30. The Standard plug-in does not emit a dedicated password-rehash metric. There is no
Meter/CounterinStellaOps.Authority.Plugin.Standard, and nopassword_rehash_total(or anyauth.plugins.standard.*) metric exists in the code. Earlier guidance referencing such a metric was aspirational and is not implemented.
- When a successful login uses a hash that no longer matches the configured target (a legacy PBKDF2 hash, or an Argon2id hash with stale parameters),
StandardUserCredentialStore.VerifyPasswordAsyncre-hashes the password and persists the new hash viaIUserRepository.UpdatePasswordAsync. The upgrade is surfaced as an audit-log property on the success event:plugin.rehashedwith valueargon2id(aClassifiedString.Public). There is no separate “rehash-needed” log line — the audit property on the successful verification is the signal. - The credential store is PostgreSQL-backed (
authorityschema,UserEntity/authority.userstable); the stored hash lives in thepassword_hashcolumn. To track migration progress, query that column for rows whosepassword_hashstill begins with thePBKDF2.prefix (i.e. have not yet been rehashed to$argon2id$). There are no MongoDB “collections” in the current store. - During migration, expect a gradual decline in
PBKDF2.-prefixed hashes as users authenticate, since each successful legacy login transparently rehashes to Argon2id. - If you need a rolled-up rehash signal for dashboards, derive it from the audit log (the
plugin.rehashedproperty) rather than from a metric.
6. Operational Checklist
- Update the plug-in
passwordHashingblock with the desired parameters (the hostsecurity.passwordHashingblock does not drive plug-in hashing today — see §3); restart the host. - Redeploy the updated plug-in configuration.
- Monitor the audit log for the
plugin.rehashedproperty (see §5 — there is nopassword_rehash_totalmetric) and watch login success rates; investigate any spike in failures (likely due to mis-sized limits). - Review hardware utilisation; Argon2id increases memory pressure compared to PBKDF2.
- Archive this document with the change request and notify SOC of the new baseline.
For additional context on tuning trade-offs, consult the OWASP Password Storage Cheat Sheet. For the complementary brute-force and burst controls on the authentication endpoints, see Authority rate-limit guidance.
7. Native Argon2 Preview Build Flag
- Set
dotnet build -p:StellaOpsCryptoSodium=true(or define the MSBuild property in your CI) to enable theSTELLAOPS_CRYPTO_SODIUMcompilation symbol. The property/symbol wiring lives inStellaOps.Cryptography.csproj. - The symbol selects an alternate partial-class implementation of
Argon2idPasswordHasher.DeriveHashCore. The default build compilesArgon2idPasswordHasher.BouncyCastle.cs(guarded by#if !STELLAOPS_CRYPTO_SODIUM); the flagged build compilesArgon2idPasswordHasher.Sodium.cs(#if STELLAOPS_CRYPTO_SODIUM). - Today the two implementations are functionally identical. Both derive the hash with BouncyCastle’s
Argon2BytesGenerator(BouncyCastle.Cryptographypackage). The.Sodium.csvariant is a placeholder that still falls back to the managed BouncyCastle path and carries aTODO(SEC1.B follow-up)to replace it with libsodium/core bindings. The managed default does not use the Konscious Argon2 library. Setting the flag currently changes nothing at runtime — it only validates the alternate compilation path. - Document any production usage of the flag in your change log so future upgrades can align with the Security Guild rollout plan.
