scramClientFinal

First introduced in version: 3.00.3

Syntax

scramClientFinal(user, combinedNonce, clientProof)

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).

Parameters

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.

Returns

A Base64-encoded server signature, if verification succeeds.

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