scramClientFirst
Syntax
scramClientFirst(user, cnonce)
Arguments
useris a string indicating a user name. It can only contain letters, underscores, or numbers. It cannot start with numbers. The length cannot exceed 30 characters.
cnonce is a client-generated nonce (a one-time random value). It must be a 16-byte random value, encoded in base64.
Details
scramClientFirst
is a function used in the first stage of SCRAM
(Salted Challenge Response Authentication Mechanism) authentication. It retrieves
the server's authentication information.
Return value:
A tuple containing the following elements:
- salt – The base64-encoded hash salt value.
- iterCount – The iteration count for password hashing.
- combined_nonce – The base64-encoded combination of the client nonce and server nonce.
SCRAM Authentication Workflow
1. Client initiates authentication request
The client generates a client nonce (a one-time random value) and sends it along with the username to the server.
- Upon receiving the request, the server uses
scramClientFirst
to generate:- A hash salt value for encryption
- The password hashing iteration count
- A server nonce, which is combined with the client nonce
- These details are then returned to the client.
3. Client generates authentication proof and submits final request
Using the received data, the client converts the plaintext password into a cryptographic key and generates a client proof, and then sends this proof back to the server for verification.
4. Server verifies the authentication request
The server uses scramClientFinal
to validate the combined
nonce and client proof. If successful, the server returns a
server signature to the client.
5. Client verifies the server signature
The client checks the server’s returned signature. If verification passes, login succeeds. If verification fails, the session is terminated.
Examples
The following Python script provides a complete example of how to implement SCRAM authentication for a user ("user01") in a Python client. The server-side operations are handled through the DolphinDB Python API.
import dolphindb as ddb
# Establish a session connection with DolphinDB
s = ddb.session("183.134.101.133", 8888)
s.run("print", 1, 2, 3)
##########################
import hashlib
import hmac
import os
import base64
import hashlib
# ------------------ Server Functions ------------------
def server_handle_client_first(username, client_nonce):
"""Handles the first authentication request, returns salt, iteration count, and combined nonce."""
salt, iter_count, nonce = s.run("scramClientFirst", username, client_nonce)
return {
"salt": salt,
"iteration_count": iter_count,
"combined_nonce": nonce
}
def server_handle_client_final(user, combined_nonce, client_proof):
"""Handles the final authentication request, verifies client_proof, and returns server_signature."""
return s.run("scramClientFinal", user, combined_nonce, client_proof)
# ------------------ Client Functions ------------------
def client_initiate_authentication(username):
"""Client initiates authentication by generating a client_nonce."""
client_nonce = base64.b64encode(os.urandom(16)).decode()
return {
"username": username,
"client_nonce": client_nonce
}
def client_generate_proof(user, password, salt, iteration_count, cnonce, combined_nonce):
"""Generates the client_proof."""
# Compute SaltedPassword
salted_password = hashlib.pbkdf2_hmac(
'sha256',
password.encode(),
base64.b64decode(salt),
iteration_count
)
# Generate keys
client_key = hmac.new(salted_password, b"Client Key", hashlib.sha256).digest()
stored_key = hashlib.sha256(client_key).digest()
server_key = hmac.new(salted_password, b"Server Key", hashlib.sha256).digest()
# Construct AuthMessage
auth_message = (
f"n={user},r={cnonce}," # client-first-bare
f"r={combined_nonce},s={salt},i={iteration_count}," # server-first
f"c=biws,r={combined_nonce}" # client-final-without-proof
)
# Compute ClientProof
client_signature = hmac.new(stored_key, auth_message.encode(), hashlib.sha256).digest()
client_proof = bytes([ck ^ cs for ck, cs in zip(client_key, client_signature)])
return {
"client_proof": base64.b64encode(client_proof).decode(),
"server_key": server_key,
"auth_message": auth_message
}
# ------------------ SCRAM Authentication ------------------
if __name__ == "__main__":
# Client initiates authentication
client_data = client_initiate_authentication("user01")
# Server processes the first request
server_response = server_handle_client_first(
client_data["username"],
client_data["client_nonce"]
)
password = "123456"
# Client generates proof
client_proof_data = client_generate_proof(
"user01",
password,
server_response["salt"],
server_response["iteration_count"],
client_data["client_nonce"],
server_response["combined_nonce"]
)
# Server verifies and returns signature
# auth_sessions[server_response["combined_nonce"]]["auth_message"] = client_proof_data["auth_message"]
server_signature = server_handle_client_final(
"user01",
server_response["combined_nonce"],
client_proof_data["client_proof"]
)
# Client verifies the server signature
computed_server_sig = hmac.new(
client_proof_data["server_key"],
client_proof_data["auth_message"].encode(),
hashlib.sha256
).digest()
# If verification passes, authentication succeeds
assert server_signature == base64.b64encode(computed_server_sig).decode()
print("SCRAM authentication succeeded!")
# Check current session and user
print(s.run("getCurrentSessionAndUser()"))