Skip to main content

Initial Authentication via Secure Remote Password (SRP)

warning

Initial authentication via the SRP protocol will be deprecated in a future release. Existing users are advised to migrate to the new OPAQUE-3DH aPAKE protocol.

The initial authentication process uses a WebSocket connection to the /api/auth endpoint. The rough process is as follows:

Let us examine the process in more detail.

Message Format

Each message is a JSON object containing the following fields:

  • status: Current authentication status. Can be OK, ERR, or null.
  • binary: Whether the included data is binary data. Can be true or false.
  • data: Message data. If binary is true, this field is a Base64 encoded byte array. Otherwise, it is a ASCII string.

Here are two examples of messages:

{
"status": "OK",
"binary": false,
"data": "U is OK"
}
{
"status": null,
"binary": true,
"data": "AQ=="
}

Specification

The specification of how the authentication process works is very technical and is described below.

Let

  • NN denote the SRP prime according to RFC5054, Appendix A;
  • gg denote the SRP generator according to RFC5054, Appendix A;
  • kk denote the SRP multiplier according to RFC5054, Section 2.6;
  • xx denote the SRP-xx key generated on the client-side; and
  • v=gxmodNv = g^x \mod N be the verifier value.
danger

xx and vv should be treated as secrets. The server should never have access to xx, and no one other than the server and client should have access to vv.

The initial authentication process is as follows:

  1. Client sends its username to the server.

  2. Server checks whether it has a record of the requested username.

    • If username is not found, server sends message with status ERR and data User does not exist. Terminate connection.
  3. Server sends message with status OK and the SRP group size (in bits) as an ASCII string (e.g., 1024).

  4. Server generates its ephemeral values (private value bb and public value B=(kv+gb)modNB = (kv + g^b) \mod N) and sends the public value BB to the client (specifying binary = true).

  5. Client checks the received BB.

    • If BB is invalid (e.g., BmodN=0B \mod N = 0), client sends message with status ERR (data can be anything). Client can choose to terminate the connection, or wait for server to try another BB.
  6. Client generates its ephemeral values (private value aa and public value A=gamodNA = g^a \mod N) and sends the public value AA to the server (specifying binary = true) with status OK.

  7. Server checks received AA.

    • If AA is invalid (e.g., AmodN=0A \mod N = 0), sends message with status ERR (data can be anything). Server can choose to terminate the connection, or wait for client to try another AA.
  8. Both client and server computes u=SHA1(AB)u = \texttt{SHA1}(A || B) where || refers to concatenation. Note that AA and BB have been padded to SRP group size bits using zero padding on the left.

    • If server detects u0(modN)u \equiv 0 \pmod N, it sends message with status ERR and data Shared U value is 0 and then terminates the connection.
    • If client detects u0(modN)u \equiv 0 \pmod N, it can just choose to close the connection.
  9. Server sends message with status OK and data U is OK.

  10. Client and server each computes their premaster value:

    • Client: preK=(Bkgx)a+uxmodN\texttt{pre}K = (B - kg^x)^{a + ux} \mod N
    • Server: preK=(Avu)bmodN\texttt{pre}K = (Av^u)^b \mod N

    The master KK is computed by computing SHA3-256 of the premaster, padded to the SRP group size bits using zero padding.

  11. Client computes M1=H((H(N)H(g))    username    salt    A    B    Kclient)M_1 = H((H(N) \oplus H(g)) \; || \; \texttt{username} \; || \; \texttt{salt} \; || \; A \; || \; B \; || \; K_{\texttt{client}}), where \oplus means XOR and HH is SHA3-256.

  12. Client sends M1M_1 to the server (setting binary = true) with status OK.

  13. Server computes its own M1M_1 value using the same formula (using KserverK_{\texttt{server}} in place of KclientK_{\texttt{client}}), and checks with the received M1M_1.

    • If they do not match, sends message with status ERR and data M1 values do not match, terminating the connection.
  14. Server computes M2=H(A    M1    Kserver)M_2 = H(A \; || \; M_1 \; || \; K_{\texttt{server}}), where \oplus means XOR and HH is SHA3-256.

  15. Server sends M2M_2 to the client (setting binary = true) with status OK.

  16. Client computes its own M2M_2 value using the same formula (using KclientK_{\texttt{client}} in place of KserverK_{\texttt{server}}), and checks with the received M2M_2.

    • If they do not match, client terminates the connection.
  17. Client sends message with status OK with no data.

  18. Server prepares authentication token in the form of a JSON Web Token (JWT) and encrypts it with the shared master key KK using AES-GCM-256.

  19. Server sends message with no status and JSON data containing three fields: nonce, token, and tag, each as a Base64 encoded byte array:

    • nonce: A random value
    • token: The encrypted JWT
    • tag: The authentication tag
  20. Both sides close the connection.

note

The full code that implements the server-side checking can be found in the comms.py file.