Skip to content

pgconn: add optional SCRAM-SHA-256 SaltedPassword cache#2523

Open
mattrobenolt wants to merge 1 commit intojackc:masterfrom
mattrobenolt:master
Open

pgconn: add optional SCRAM-SHA-256 SaltedPassword cache#2523
mattrobenolt wants to merge 1 commit intojackc:masterfrom
mattrobenolt:master

Conversation

@mattrobenolt
Copy link
Copy Markdown

I wasn't sure if this warranted an issue or discussion first, but I was writing this as an experiment before I considered upstreaming, so here we are.

I'm happy to discuss through anything here as far as implementation details or how this can best be leveraged.

My intention is that this is disabled by default, documented on how to enable this performance boost.

Eventually I'd like this to be something that is capable of being graduated to a default on behavior since there's near no downsides, only positives IMO.

One thing to note about this, is there really is not much of a precedent of this in the Postgres ecosystem, so I think this is something worth being able of setting a precedent. This is a free win for client side connection poolers specifically.

Thanks!

RFC 5802 §5.1 and RFC 7677 §4 both permit caching derived SCRAM
key material to avoid repeated PBKDF2 computation:

  RFC 5802 §5.1:
    "a client implementation MAY cache ClientKey&ServerKey
     (or just SaltedPassword) for later reauthentication to
     the same service, as it is likely that the server is
     going to advertise the same salt value upon
     reauthentication."

  RFC 7677 §4:
    "This computational cost can be avoided by caching the
     ClientKey (assuming the Salt and hash iteration-count
     is stable)."

Add a ScramDeriveCache interface on Config that, when set,
caches the 32-byte SaltedPassword keyed by an opaque SHA-256
fingerprint of (password, salt, iterations). On subsequent
connections with the same verifier, PBKDF2 is skipped entirely.

Cache entries are invalidated on authentication failure when
cached material was used, so a password change or salt rotation
cannot produce stale hits.

Includes SimpleScramDeriveCache, a mutex+map implementation
suitable for most use cases. The interface allows callers to
provide their own LRU or sharded implementations.
@jackc
Copy link
Copy Markdown
Owner

jackc commented Mar 21, 2026

This is getting beyond my expertise. But from what I could gather from the code, this caches something to establish connections faster?

  • Have you quantified the improvement?
  • Is there precedent for this approach outside of PostgreSQL?
  • What are the security implications?

@mattrobenolt
Copy link
Copy Markdown
Author

Yes, great questions!

Have you quantified the improvement?

Yes, I can share some benchmarks, but they are highly dependent on a lot of variables, including the number of iterations in the PBKDF2.

Is there precedent for this approach outside of PostgreSQL?

I'm not sure honestly if any other real uses outside of Postgres, but my focus has been improving this for Postgres specifically, and it's in the RFC that you can do this.

What are the security implications?

None, this is considered safe in the RFC.

I explained more of this in my commit message, not in the PR body, including citing the appropriate RFC sections.

But tl;dr, there is a plaintext password the user supplies in their config. This is already known by pgx and in memory.

The material we are caching is called the SaltedPassword.

To derive the salted password, you combine the plaintext password with the server salt + iteration count.

This derivation part is computationally expensive intentionally. In the sense that bcrypt or scrypt bake in iterations of a derivation function to mitigate brute forcing.

The salt and iteration count is public information. This analogy is not correct, but akin to a public key exchange. It's a constant and does not change for the same server.

The expensive part of this is calculating the SaltedPassword from the plaintext password + the salt and iteration count. Thus the 3-tuple of (password, salt, iteration) is the unique bits.

From the SaltedPassword, two bit of cryptographic material are then derived, the ClientKey and ServerKey, but those are fast/cheap to calculate, and are also simple constants derived from the SaltedPassword.

So in this context, choosing to just cache the intermediate SaltedPassword instead of the Client/ServerKey.

@mattrobenolt
Copy link
Copy Markdown
Author

Anything I can do here to help push this along? Happy to work through all the CI stuff, but mostly want to get agreement on the implementation, and I want you to be comfortable knowing how this works.

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.

2 participants