scramClientFinal

Syntax

scramClientFinal(user, combinedNonce, clientProof)

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.

combinedNonce is the combination of the client nonce and server nonce, Base64-encoded (i.e., the combined_nonce returned by scramClientFirst).

clientProof is the client-generated proof, Base64-encoded.

Details

scramClientFinal is a function used in the second-stage of SCRAM authentication process. It checks the validity of the combined nonce (combinedNonce) and the client-provided proof (clientProof).

Return value: A Base64-encoded server signature, if verification succeeds.

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()"))