MongoDB 3.0 features a new password authentication mechanism called Salted Challenge Response Authentication Mechanism or SCRAM (see RFC5802). MongoDB 3.0 uses SCRAM as the default authentication mechanism, replacing MONGODB-CR.
In this article we will give an overview of the motivations behind SCRAM's design, a basic analysis of its security properties, and a tutorial on how to use it in MongoDB.
Background: Authentication in MongoDB
Authentication requires clients to provide a proof of identity in order to access the database. MongoDB 3.01 provides 3 types of authentication mechanisms:
- Password-based authentication - the client verifies identity by proving the possession of a predetermined secret value. This includes SCRAM-SHA-12 and MONGODB-CR (deprecated)
- Certificate-based authentication - the client verifies identity using an x.509 certificate signed by a trusted Certificate Authority.
- External authentication - the client verifies identity using an external service, such as Kerberos or LDAP.
MongoDB 3.0’s new default password-based authentication uses a mechanism known as a challenge-response protocol. In a challenge-response protocol, the server generates a question or "challenge" in response to client connection attempt. The client's answer then proves that it has knowledge of the password. Challenge-response protocols are designed to protect against replay attacks, but as detailed below, SCRAM features additional security mechanisms to prevent other types of attacks as well.
We consider an adversary that can capture and generate arbitrary network traffic, and inspect the contents of a server's on-disk storage. However, we assume our adversary to be computationally bound so it cannot compromise any standard cryptographic primitives, such as secure hash functions.
Here are some attacks on SCRAM that follow from this threat model. These attacks provide justification for SCRAM’s design, as it is specifically intended to counter them.
- Eavesdropping - The attacker can read all traffic exchanged between the client and server. To protect against eavesdropping, a SCRAM client never sends her password as plaintext over the network.
- Replay - The attacker can resend the client's valid responses to the server. Each SCRAM authentication session is made unique by random nonces, so that the protocol messages are only valid for a single session.
- Database Compromise - The attacker can view the contents of the server's persistent memory. A database compromise is mitigated by salting3 and iteratively hashing passwords before storing them.
- Malicious Server - The attacker can pose as a server to the client. An attacker is is unable to pose as server without knowledge of the client’s SCRAM credentials.
In the second part of this article, we’ll provide a detailed analysis of SCRAM’s resistance to these attacks.
This is an overview4 of the SCRAM client-server protocol. Please see RFC 5802 for the complete version.
In the description of the protocol we will use the following primitives:
- || - string concatenation
- Hash(str) - a cryptographic hash function5
- HMAC(str, key) - a hash-based message authentication code
- ⊕ - bitwise exclusive-or
- KeyDerive(str, salt, i) - a key derivation function6. The parameter i is an iteration count that determines the computational cost of the KeyDerive operation. A higher i value increases the cost of a brute-force attack, but also increases the time required for a user to authenticate to the server.
Setup: Account Creation
To create an identity on the server, an administrator executes the createUser command, specifying the plaintext username and password.
The server first applies the key derivation function to compute the SaltedPassword.
SaltedPassword = KeyDerive(password, salt, i)
The server then calculates the ClientKey.
ClientKey = HMAC(SaltedPassword, "Client Key")
The server then hashes the ClientKey to compute the StoredKey.
StoredKey = H(ClientKey)
The server uses the StoredKey to compute the ServerKey.
ServerKey = HMAC(SaltedPassword, "Server Key")
The server then stores the following information7:
- An iteration count for key derivation8.
- A per-user randomly generated salt to be used during key derivation.
- The StoredKey, used by the server to verify the client's identity.
- The ServerKey, used by the server to prove its identity to the client.
The StoredKey is a cryptographic digest of the ClientKey. The ClientKey is itself a cryptographic digest of the salted password. The key idea of the StoredKey is that is can be used to verify a ClientKey without having to store the ClientKey itself. The ServerKey can be derived from the client's password (but not the other way around). It serves as evidence that the MongoDB server at one time had access to the client's plaintext password.
Now we detail the exchange of messages between the client and server during an authentication session. Some of the values used are the same as those computed by the server when it generates the credential. In those instances we refer the reader to their initial definitions above.
Here is the complete set of messages exchanged:
Step 1: The client initiates a SCRAM authentication session
The client sends an authentication request to the server containing username and a random number (called the ClientNonce) used to prevent replay attacks.
Step 2: The server issues a challenge
The server first retrieves the user's credential, and responds with a message containing a salt, iteration count, and the CombinedNonce, a concatenation of the ClientNonce and an additional ServerNonce generated by the server.
Step 3: The client responds with a proof
The response to the challenge is a message containing the ClientProof and the CombinedNonce. The ClientProof allows the client to prove that it has possession of the ClientKey without having to send it over the network.
To compute the ClientProof, the client first computes the StoredKey and ClientKey in the same same manner as the server when it initially generates the credential.
The client then computes the ClientSignature using the StoredKey and the AuthMessage. AuthMessage is just a concatenation of the client's initial message, the server's challenge, and the client's response (without the ClientProof).
ClientSignature = HMAC(StoredKey, AuthMessage)
The client then calculates the ClientProof by performing a bitwise xor of the ClientKey and the ClientSignature.
ClientProof = ClientKey ⊕ ClientSignature
As the server can compute the ClientSignature using the information stored in its credential, the bitwise xor with the ClientKey, which is not stored by the server, prevents the server from being able to forge a valid ClientProof.
Step 4: The server verifies the client's proof, and issues a proof of its own The server now computes the ClientSignature using the StoredKey from the client's credential. The server can now verify the ClientProof using the following equality:
H(ClientSignature ⊕ ClientProof) = StoredKey.9
If this equation holds, the server has proof that the client has access to the ClientKey10. After verifying the ClientProof, the server generates its own proof.
ServerSignature = HMAC(ServerKey, AuthMessage)
The server then replies to the client with the ServerSignature.
Step 5: The client verifies the server's proof
The client verifies the server's proof by computing the ServerKey and ServerSignature, then comparing its ServerSignature to the one received from the server. If they are the same, the client has proof that the server has access to the ServerKey.
Now that we understand how SCRAM works under the hood, in the next post we’ll take a closer look at how SCRAM addresses several common types of attacks. In the interim, if you’re looking for more information about securing your MongoDB deployment, download our Security Architecture Guide. It includes details about MongoDB’s security features and provides a security configuration checklist:
About Adam Midvidy
Adam Midvidy is an engineer on the MongoDB Platforms team with an interest in cryptography.