What is Secure Remote Password (SRP) and How to use it to protect users' passwords.

What is Secure Remote Password (SRP) and How to use it to protect users' passwords.

Introduction

Secure Remote Password (SRP) protocol is a method of secure authentication designed to safely verify a user's credentials without transmitting the password over the network.

Unlike traditional password hashes, which rely on a password being sent to the server and then compared to a stored hash, SRP will never transmit or store a user's password on the server, significantly reducing the risk of interception or server compromise. The benefits are that it stops different types of attacks like MITM (man-in-the-middle) attacks, doesn't need a password change if user data leaks, and lets both the user and server confirm each other's identity.

Major companies like Apple and 1Password have adopted SRP as part of their authentication mechanisms. Apple, for instance, has implemented SRP in its iCloud Keychain to securely synchronize passwords across devices without exposing them. Similarly, 1Password uses the user's master password and the secret to encrypt data, and both the password and secret use SRP to authenticate accounts and ensure that they won't transmit to the server.

About This Article

This article aims to explain each step of SRP-6a and provides a combined overview of RFC-5054 and RFC-2945. It also maps each calculation formula in the variable introduction and the sample code later in the article. This will be particularly helpful for developers who are new to SRP and want to understand how it's implemented in the RFCs, ensuring they can follow every calculation step mentioned.

Not all open-source packages follow the RFC standards completely, which means some may not work well with others. This article strictly follows SRP-6a, using the implementation in windwalker/srp as a model. By following this example, your implementation should easily work with other packages that are fully RFC-compliant.

If you are interested in finding more packages that are fully implemented SRP-6a, see this SRP implementations list.

The SRP Flow

The definitions and processes of SRP-6a are dispersed in RFC 2945 and RFC 5054; this is an attempt to integrate them for an overview. Please follow strictly to the RFC-specified procedures without custom modification, and do not transmit any variables unnecessarily to avoid security breaches.

Definition

VariableNameSendCalc
I, identityThe main identity (username or email).C=>S
NA large safe prime, All arithmetic is done modulo N.X
gA generator modulo NX
kMultiplier parameterXSHA1(N ‖ PAD(g))
sThe user salt.C<=Srandom()
vPassword VerifierXg^x % N
xThe hash of salt + identity + password.XSHA1(s ‖ SHA1(I ‖ ":" ‖ P))
a, bClient & server secret keyXrandom()
AClient public keyC=>Sg^a % N
BServer public keyC<=Sk*v + g^b % N
uThe value of preventing attacker who learns a user's verifierXH(PAD(A) ‖ PAD(B))
S (client)Pre-master secret (The secure common session key)X(B - (k * g^x)) ^ (a + (u * x)) % N
S (server)Pre-master secret (The secure common session key)X(A * v^u) ^ b % N
KThe session key hash for used to generate MXH(S)
M1Evidence message 1, To verify both sides generated the same session key.C=>SH(H(N) XOR H(g) ‖ H(U) ‖ s ‖ A ‖ B ‖ K)
M2Evidence message 2, To verify both sides generated the same session key.C<=SH(A ‖ M ‖ K)

Registration

Registration flow, img via windwalker/srp

When an app (web/mobile) start the registration flow, it may display a identity (I) (username or email) and password (P) field to user. They entered their username and password and then clicked the register button. The SRP client will generate a random salt (s), and a password verifier (v) which is generated from salt, identity and password.

Then app will send only the salt, verifier and identity to the server and do not send password. It is a protocol violation and security bug if the raw password is accidently transmitted to the server even if it is ignored by the server.

You can save the user info and salt, verifier to DB when server receives the registration request. It is optional if you want to encrypt the salt and verifier before saving, make sure you encrypt it by a key that is only known by server.

Login

Login flow, img via windwalker/srp

Hello and Server step1

When a user starts the login process, they may enter their identity and password on form fields, and click the login button. The SRP client will send a Hello request with identity to server. The server should check user exists by this identity, and get salt and verifier from user data. Next, server will generate a random private b and a public B, and remember them by DB, session or cache storage that we will need them in the further steps, then, return the salt, B back to client (Server Hello). This process is similar to a handshake, to create a connecting session for both sides.

Some package calls the client Hello as challenge action, and the B is the server challenge value.

Client steps 1 & 2

After receiving the B and salt, Client runs step 1 to generate a and A, and then, runs step2 to use all of the above values to generate a client proof M1. It will be sent to server with A (authenticate action). Server side also use all the generated values to generate a M1 and compare it. If compared failure, server will report an error, and if compared success, server will generate a server proof M2 and back to client. To this step, the authenticate actions are done, you can simply redirect user to the login success page.

There is an optional Client step3 is that you can verify the M2 to authority server is trusted and make sure both sides generate the same session key (S). If you have done this step3, it means you completed the authenticate handshake and did a two-way authentication. If you want to run the step3 to complete all the processes, you can redirect user after step3 done.

About the S and M

When client and server generating M, they will both generate a premaster secret (S). The S should be same, even if the 2 sides did not send S to another. The M1 and M2 is a verifier to make sure both side have a same S. So, S can be a trusted session key or encryption key if you want to do some other cryptography behavior in the future.

The Example Code

Here is a pseudo-code to show how SRP server-side and client-side cross works.

const server = SRPServer.create();
const client = SRPClient.create();

// Register
const identity = '...';
const password = '...';

// Register: generate new salt & verifier

// random()
const salt = client.generateSalt();
// (SHA(s | SHA(I | `:` | P)))
const x = client.generateX(salt, identity, password);
// (g^x % N)
const verifier = client.generateVerifier(x);


// Send salt and verifier to Server store


// Login start
// AJAX:hello?{identity} - Server step (1)
// salt & verifier has already stored on user data, server can get it from DB
// b & B must remember on session, we will use it at following steps.

// random()
const b = server.generateRandomSecret();
// ((k*v + g^b) % N)
const B = server.generateB(b, verifier);


// Server returns B & salt to client


// Client step (1)

// random()
const a = client.generateRandomSecret();
// (g^a % N)
const A = client.generateA(a);
// (SHA(s | SHA(I | `:` | P)))
const x = client.generateX(salt, identity, password);


// Client step (2)

// H(PAD(A) | PAD(B))
const u = client.generateU(A, B);
// ((B - (k * g^x)) ^ (a + (u * x)) % N)
const S = client.generateS(a, B, x, u);
// H(S)
const K = client.hash(S);
// H(H(N) xor H(g), H(I), s, A, B, K)
const M1Client = client.generateM1(identity, salt, A, B, K);


// AJAX:authenticate?{identity,A,M1} - Server step (2)
// Send identity & A & M1 to server and compare it.
// The salt & verifier stored on user data, get it from DB.
// The b, B stored in session state, get and clear them.

// H(PAD(A) | PAD(B))
const u = server.generateU(A, B);
// ((A * v^u) ^ b % N)
const S = server.generateS(A, b, verifier, u);
// H(S)
const K = server.hash(S);
// H(H(N) xor H(g), H(I), s, A, B, K)
const M1Server = server.generateM1(identity, salt, A, B, K);

// Do compare
if (!crypto.timeingSafeEquals(M1Client, M1Server)) {
  throw new Error('Invalid client session proof.');
}

// Now create a M2 as server proof
// H(A | M | K)
const M2Server = server.generateM2(A, M1Server, K);

// Server returns M2 to Client
// Client step (3) (optional)

// H(A | M | K)
const M2Client = client.generateM2(A, M1Client, K);


// Do compare
if (!crypto.timeingSafeEquals(M2Client, M2Server)) {
  throw new Error('Invalid server session proof.');
}

// If all passed, should not throw any exceptions.

Some Important Notes

  • You don't need to use AJAX to implement SRP flow. You can simply use form post to do all the steps. For example, you may separate username and password into 2 steps on your website, and store values in hidden inputs. Make sure you stored a and b in your browser and server cache that can use them cross steps and do not accidently send them to remote side.

  • The verifier is generated from identity and password, which means you must re-create a new verifier to replace old one if user changes either of identity or password.

  • Always make sure you don't send any unnecessary values to each side, even if server or client ignore them, it is considered as a protocol violation and security bug. Also, the MITM attacker can use these sensitive data.

  • Always clear values when restart an authenticate process. Generally, you can reload page so that all values and JS object will be reset. If you are developing a SPA app, wrap whole process in a function and do not cache values to object properties, and if you are using an SRP library, always re-create the library objects when user retries.

  • SRP should not replace HTTPS, you should always use SSL/TLS on your app and enable the Cookies HttpOnly and secure settings.


Images via windwalker/srp