SRP is a key exchange protocol published by Stanford in 1998. It is described well on their website:
SRP is a secure password-based authentication and key-exchange protocol. It solves the problem of authenticating clients to servers securely, in cases where the user of the client software must memorize a small secret (like a password) and carries no other secret information, and where the server carries a verifier for each user, which allows it to authenticate the client but which, if compromised, would not allow the attacker to impersonate the client. In addition, SRP exchanges a cryptographically-strong secret as a byproduct of successful authentication, which enables the two parties to communicate securely.
There are two discrete processes defined by SRP:
- Client Registration
- A client is created by generating it's credentials, and transmitting them to the server for storage.
- Session Creation
- A client and server negotiate a strong session key.
// A client chooses an identifier and passphrase identifier := []byte("[email protected]") passphrase := []byte("Password123!") // SRP creates a salt and verifier based on the client's identifier and passphrase s, v, err := srp.NewClient(identifier, passphrase) if err != nil { panic("Client creation failed!") }
The client now sends it's identifier, s, and v values to the server for storage.
// The client's identifier and passphrase, registered with the server. identifier := []byte("[email protected]") passphrase := []byte("Password123!") // The client creates a public key "A" and a private key "a" A, a, err := srp.InitiateHandshake() if err != nil { panic("Handshake initiation failed!") }
The client now sends it's identifier and A to the server.
// The server receives a client's "identifier" and "A" value. Assume the // following variables are populated accordingly. var identifier []byte var A []byte // The server looks up a client's salt and verifier from the provided // identifier. Assume the following variables are populated accordingly. var s []byte var v []byte // Create a public key to share with the client, and compute the session key. B, K, err := srp.Handshake(A, v) if err != nil { panic("Handshake failed!") }
The server should now persist the value of K, and send s and B to the client.
// The client receives its salt "s" along with a public key "B" from the server. // Assume the following variables are populated accordingly. var s []byte var B []byte // Recall that the client has "A", "a", and "passphrase" variables from the // first step of session creation. // Compute the session key! K, err := srp.CompleteHandshake(A, a, passphrase, s, B) if err != nil { panic("Failed to complete the handshake!") }
At this point, the client and server will have a shared secret K if the authentication was successful. The shared secret can be verified as follows:
proof := srp.Hash(K)
The client can then send proof to the server for easy verification, and demand proof of its own.
// The server received the client's proof, and assigns it to the variable below: var clientProof []byte // Check if the client's proof is acceptable if subtle.ConstantTimeCompare(clientProof, srp.Hash(K)) != 1 { panic("Server does not accept client's proof!") } serverProof := srp.Hash(s, K)
Now the server sends serverProof to the client.
// The client received the server's proof, and assigns it to the variable below: var serverProof []byte if subtle.ConstantTimeCompare(serverProof, srp.Hash(s, K)) != 1 { panic("Client does not accept server's proof!") }
| Name | Link | Note |
|---|---|---|
| Stanford's SRP Home Page | http://srp.stanford.edu/ | |
| RFC 2945 | https://tools.ietf.org/html/rfc2945 | Older SRP-3 implementation |
| RFC 5054 | https://tools.ietf.org/html/rfc5054 | Newer SRP-6(a) implementation |
| node-srp | https://github.com/mozilla/node-srp | A compatible Javascript library |
| node-srp | https://github.com/voynic/node-srp | A more modern fork of ^ |