SCRAM (Salted Challenge Response Authentication Mechanism) is MongoDB's default authentication method, based on IETF RFC 5802 (for SHA-1) and RFC 7677 (for SHA-256). Both variants follow a similar challenge-response flow to avoid sending plaintext passwords over the wire, but they differ in security, hashing, and MongoDB-specific tweaks. SCRAM-SHA-256 became the default in MongoDB 4.0+ due to SHA-1's deprecation and known weaknesses (e.g., collision vulnerabilities).
Here's a comparison:
| Aspect | SCRAM-SHA-1 | SCRAM-SHA-256 |
|---|---|---|
| Hashing Algorithm | SHA-1 (160-bit output) | SHA-256 (256-bit output) – stronger collision resistance and future-proof. |
| Security Level | Adequate but deprecated; vulnerable to certain attacks (e.g., length extension in HMAC). | Higher security; recommended for new deployments. Protects against replay attacks via stronger hashing. |
| MongoDB-Specific Password Preprocessing | Uses an extra MD5 hash: passphrase = MD5(username + ":mongo:" + password). This is then salted/iterated with SHA-1. (Non-standard; not in RFC 5802.) |
No MD5 step; uses raw normalized password directly, salted/iterated with SHA-256. (Follows RFC 7677.) |
| Iteration Count | Configurable via scramIterationCount (default: 10,000). |
Configurable via scramSHA256IterationCount (default: 15,000; higher for added work factor). Server sends the value in its challenge. |
| Password Digestion for Storage | Flexible: Client or server can compute/store the salted password hash (Hi) during user creation/update. | Server-side only: Client sends plaintext password (over TLS) to server for digestion/storage. Clients cannot pre-digest for SHA-256 users. |
| Stored Credential | storedKey and serverKey derived from SHA-1-based Hi. |
storedKey and serverKey derived from SHA-256-based Hi. |
| Protocol Flow | 3-message exchange (ClientFirst, ServerFirst, ClientFinal, ServerFinal). Bi-directional proof. | Identical flow, but with SHA-256 in all HMAC/H computations. |
| Compatibility | Supported in MongoDB 3.0+; fallback for older setups. | Default in 4.0+; requires compatible drivers (e.g., most modern MongoDB drivers auto-negotiate). |
| Performance | Faster (weaker hash). | Slightly slower due to stronger hash and higher default iterations. |
Key Notes:
- Both use per-user random salts and tunable iterations to resist brute-force attacks.
- Authentication requires TLS (e.g., STARTTLS or direct TLS) to protect the exchange.
- The stored credentials (in
system.users) include the salt, iterations,storedKey(H(ClientKey)), andserverKey(HMAC(SaltedPassword, "Server Key")) – never the plaintext password. - MongoDB drivers (e.g., in C#, Java, Python) typically auto-select the mechanism based on server capabilities and user config.
Since your SCRAM-SHA-1 implementation is already working, extending it to SHA-256 is straightforward: the protocol structure (message formats, proofs, signatures) is identical, per the RFCs. The changes are isolated to the hashing functions and password preprocessing. Assume your implementation handles the standard SCRAM flow:
- Client sends ClientFirst:
n,,n=username,r=client-nonce(mechanism name differs: "SCRAM-SHA-256"). - Server sends ServerFirst:
r=server-nonce,s=salt,i=iterations. - Client computes proofs: Using password, salt, iterations → SaltedPassword → ClientKey → StoredKey → ClientSignature → ClientProof. Sends ClientFinal:
c=biws,r=...,p=client-proof. - Server verifies and sends ServerFinal:
v=server-proof.
To add SHA-256 support, parameterize your code by mechanism (e.g., via a config flag or auto-detection from server capabilities). Here's how to adapt it step-by-step (pseudocode; adapt to your language/library, e.g., using crypto libs like OpenSSL, Java's MessageDigest, or Python's hmac/hashlib):
- Replace SHA-1 with SHA-256 everywhere it's used.
- Key functions to swap:
H(data): SHA-1 → SHA-256 digest.HMAC(key, data): HMAC-SHA-1 → HMAC-SHA-256.
Pseudocode:
import hmac
import hashlib
def get_hash_func(mechanism):
if mechanism == "SCRAM-SHA-1":
return hashlib.sha1
elif mechanism == "SCRAM-SHA-256":
return hashlib.sha256
else:
raise ValueError("Unsupported mechanism")
def H(data, mechanism):
hf = get_hash_func(mechanism)
return hf(data).digest()
def HMAC(key, data, mechanism):
hf = get_hash_func(mechanism)
return hmac.new(key, data, hf).digest()- For SHA-1: Compute
preprocessed_password = MD5(normalized_username + b":mongo:" + normalized_password). - For SHA-256: Use
preprocessed_password = normalized_passworddirectly (no MD5). - Normalization: Apply SASLprep (RFC 4013) to username and password (map non-ASCII, trim, etc.). Most crypto libs have helpers; skip if your SHA-1 impl already does it.
Pseudocode:
from hashlib import md5 # Only for SHA-1
def normalize(s):
# Implement SASLprep: lowercase, map specials, etc. (use a lib if available)
return s.lower().encode('utf-8') # Simplified
def preprocess_password(username, password, mechanism):
norm_user = normalize(username)
norm_pass = normalize(password)
if mechanism == "SCRAM-SHA-1":
# MongoDB-specific MD5 step
input_str = norm_user + b":mongo:" + norm_pass
return md5(input_str).digest()
elif mechanism == "SCRAM-SHA-256":
# Standard: no MD5
return norm_pass
else:
raise ValueError("Unsupported")- This is the core derivation:
SaltedPassword = Hi(preprocessed_password, salt, iterations). Hiis PBKDF2-like: U1 = HMAC(preprocessed, salt), U2 = HMAC(U1, salt), ..., UN = HMAC(U_{N-1}, salt); then XOR them.- Your SHA-1 impl likely has this loop – just plug in the new HMAC/H.
Pseudocode (add to your existing Hi function):
def Hi(password, salt, iterations, mechanism):
u = HMAC(password, b"Client Key" + H(b"SCRAM-SHA-256" if mechanism == "SCRAM-SHA-256" else b"SCRAM-SHA-1", mechanism), mechanism) # SaltedPassword = Hi(password, salt, i)
# Wait, no: SaltedPassword = Hi(password, salt, i) where Hi is the iterated HMAC:
result = b'\x00' * len(H(b'', mechanism)) # Init XOR result
u = HMAC(password, salt + b'\x01', mechanism) # U1 = HMAC(password, salt || int(1))
result = bytes(a ^ b for a, b in zip(result, u))
for _ in range(1, iterations):
u = HMAC(password, u, mechanism)
result = bytes(a ^ b for a, b in zip(result, u))
return result
# Then:
salted_password = Hi(preprocessed_password, salt, iterations, mechanism)Correction on Hi: The standard SCRAM Hi(P, S, i) is:
- U0 = empty
- U1 = HMAC(P, S + \x00\x00\x00\x01)
- U2 = HMAC(P, U1)
- ...
- Ui = HMAC(P, U_{i-1})
- SaltedPassword = U1 XOR U2 XOR ... XOR Ui
Yes, that's it. Your SHA-1 code likely has this; just swap HMAC.
ClientKey = HMAC(salted_password, "Client Key")StoredKey = H(ClientKey, mechanism)ClientSignature = H( AuthMessage + "," + ClientProof )(AuthMessage is concatenated prior messages)ClientProof = ClientKey XOR ClientSignature- Server proof similarly.
- Use SHA-256 for the channel-binding flag if needed (but usually
c=n,,for no binding).
- In MongoDB, query
isMasteror use driver auto-negotiation to pick "SCRAM-SHA-256" if supported. - Send the mechanism name in the SASL init message.
- Use the server's
ivalue (iterations) from ServerFirst.
- Test against a MongoDB 4.0+ instance with a SHA-256 user (create via
db.createUser({user: "test", pwd: "pass", mechanisms: ["SCRAM-SHA-256"]})). - Ensure TLS is used (password/salt exchanged).
- Verify iteration counts match server's.
- If your SHA-1 impl handles client-digested storage, disable it for SHA-256 (irrelevant for auth).
- Libraries: If refactoring from scratch, consider open-source impls like Python's
scramlib or MongoDB driver source (e.g., PyMongo'ssasl_scram.py).
This should get you ~95% there with minimal code changes (mostly in preprocessing and hash swaps). If you share your language or code snippets, I can refine further!
In MongoDB 8.1 and later, the buildInfo command requires authentication when access control is enabled, which prevents unauthenticated clients from retrieving server build details (e.g., version, storage engines). This is a security enhancement to limit exposure of internal server information. Earlier versions allowed buildInfo without auth, so clients that relied on it during connection setup may need adjustments.
The recommended authentication pattern for MongoDB clients (e.g., drivers in Node.js, Python, Java, etc.) follows the standard connection lifecycle, which is designed to handle this constraint gracefully. Here's the step-by-step sequence:
-
Establish the Initial Connection: Open a TCP socket to the MongoDB server(s). No commands are sent yet.
-
Run the
helloCommand (Before Authentication):- Send
{ hello: 1 }(or the legacy{ ismaster: 1 }) immediately after connecting. This is always safe and recommended before authentication. - Why before auth? It doesn't require authentication and is essential for topology discovery (e.g., identifying if the instance is a standalone, primary/secondary in a replica set, or shard). Drivers use this to build the cluster topology, select appropriate servers, and handle failover. Running it unauthenticated avoids blocking on auth failures and ensures the client can proceed even if credentials are invalid initially.
- If auth fails later, the client can still report topology info for debugging.
- Send
-
Authenticate the Session:
- Once topology is known, authenticate using your chosen mechanism (e.g., SCRAM-SHA-256, the default and best practice for most cases; or X.509 for certificate-based auth).
- Use the driver's built-in auth methods, passing credentials via the connection URI (e.g.,
mongodb://user:pass@host:27017/?authSource=admin) or explicitly in code. Authenticate against the appropriate database (usuallyadminfor cluster-wide users). - Best practices:
- Enable TLS/SSL for all connections to encrypt credentials in transit.
- Use role-based access control (RBAC): Grant minimal roles (e.g.,
readWritefor app users,clusterMonitorifbuildInfois needed). - Avoid storing credentials in plain text; use secure vaults or environment variables.
- For SCRAM, prefer SHA-256 over SHA-1 for stronger hashing.
-
Run Privileged Commands (After Authentication):
- Now execute commands like
buildInfo,getMore, or data operations (find,insert). - If your client needs server version/build info early (e.g., for feature detection), fetch it post-auth. Most modern drivers cache this from the initial
helloresponse (which includes amaxWireVersionfor compatibility checks), reducing the need forbuildInfoaltogether.
- Now execute commands like
from pymongo import MongoClient
# Step 1-2: Connect and implicit hello (PyMongo handles topology discovery)
client = MongoClient('mongodb://host:27017/')
# Step 3: Authenticate (PyMongo auto-auths if URI has creds; explicit here for clarity)
client.admin.authenticate('username', 'password', mechanism='SCRAM-SHA-256')
# Step 4: Now safe to run buildInfo
build_info = client.admin.command('buildInfo')
print(build_info['version']) # e.g., "8.1.0"- Resilient to Auth Failures:
helloensures basic connectivity and topology info even if auth fails. - Compliant with 8.1+: Avoids
buildInfopre-auth errors. - Efficient: Aligns with MongoDB's Server Discovery and Monitoring (SDAM) spec, used by all official drivers. No need for custom workarounds.
- Alternatives if
buildInfois Critical Early: If your app must have build info before full auth (rare), consider a separate unauthenticated "monitor" user withclusterMonitorrole, but this weakens security—avoid if possible.
For official driver-specific flows, refer to MongoDB's driver docs (e.g., C# Driver Authentication). If you're using a custom client, implement SDAM to mirror this sequence. If issues persist, check server logs for auth errors (e.g., SASL handshake failures).
No, you cannot reliably use hello.minWireVersion or hello.maxWireVersion to guess the authentication mechanism to be used. These fields indicate the range of supported wire protocol versions (an integer from 0 onward, where higher numbers denote newer protocol features like OP_MSG in version 6+). While they provide indirect clues about potential auth support—based on correlations with MongoDB server versions—they are not definitive for selecting or negotiating mechanisms. Here's why, plus details on other hello parameters:
-
Indirect Correlation Only:
- Wire protocol versions evolve with MongoDB releases, and some auth features were introduced alongside them:
MongoDB Version Min/Max Wire Version Range Key Auth-Related Changes Pre-3.0 0–2 MONGODB-CR (legacy, deprecated). 3.0–3.4 3–5 SCRAM-SHA-1 introduced as default (replaces MONGODB-CR). 3.6+ 6–7 Full OP_MSG support; SCRAM-SHA-1 stable. GSSAPI (Kerberos) enhanced. 4.0+ 8–9 SCRAM-SHA-256 added as default (stronger hashing per RFC 7677). 4.2+ 10–12 Minor auth tweaks; no major mechanism changes. 5.0+ 13–15 AWS IAM support via MONGODB-AWS. 6.0+ 16–17 OIDC preview; SCRAM-SHA-256 mandatory in some configs. 7.0+ 18+ Further OIDC integration. - How to Infer: If
maxWireVersion >= 8, the server likely supports SCRAM-SHA-256 (introduced in 4.0). FormaxWireVersion >= 13, AWS IAM might be viable. However, this is server-version-dependent, not mechanism-specific—admins can disable mechanisms (e.g., via--setParameter disableNonSSLConnectionLogging=1or role policies), and wire versions don't list enabled mechs. - Limitations: Guessing fails for custom configs, Enterprise-only mechs (e.g., LDAP via SASL/PLAIN), or if the client specifies a mechanism in the URI (e.g.,
?authMechanism=SCRAM-SHA-1). Always negotiate during SASL handshake instead.
- Wire protocol versions evolve with MongoDB releases, and some auth features were introduced alongside them:
-
Recommendation: Use wire versions for compatibility checks (e.g., client must support at least
minWireVersion), not auth selection. Drivers like PyMongo or the official Node.js driver use them to decide protocol features but fall back to SASL negotiation for auth.
The standard hello response (without optional args) does not include direct auth mechanism details. Relevant fields:
- No Other Auth-Relevant Fields: Parameters like
isWritablePrimary,maxBsonObjectSize,readOnly,hosts,setName, etc., focus on topology, limits, and status—none expose auth mechs. saslSupportedMechs(The Key Field for Auth Detection):- What It Is: An array of strings listing SASL-based mechanisms supported for a specific user (e.g.,
["SCRAM-SHA-256", "SCRAM-SHA-1", "GSSAPI"]). - How to Get It: Include the optional
saslSupportedMechs: "<username>"argument in yourhellocommand:db.runCommand({ hello: 1, saslSupportedMechs: "myUser" })
- This returns user-specific mechs (filtered by the user's roles and server config). It's available in MongoDB 4.4+ and requires no auth to query (but the username must exist).
- Without this arg, the field is absent.
- Use Cases: Ideal for clients to probe supported mechs pre-auth (e.g., fallback from SCRAM-SHA-256 to SHA-1 if unavailable). It's the best pattern for determining viable mechanisms without trial-and-error.
- Limitations: User-specific (run per user); doesn't include non-SASL mechs like X.509 or MONGODB-AWS directly (those are negotiated separately).
- What It Is: An array of strings listing SASL-based mechanisms supported for a specific user (e.g.,
- Run standard
{ hello: 1 }for topology/wire versions. - If needed, run
{ hello: 1, saslSupportedMechs: "<username>" }to list mechs. - Negotiate via SASL (drivers handle this; specify in URI like
?authMechanism=SCRAM-SHA-256). - Fallback: Start with SCRAM-SHA-256 (default in 4.0+); use wire versions to avoid deprecated clients.
This aligns with MongoDB's Server Discovery and Monitoring (SDAM) spec. For full details, see the hello command docs. If building a custom client, implement SASL per RFC 5802/7677.