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.
Note:

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.

2. Server responds to authentication request
  • 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()"))