The Secure Remote Password protocol (SRP) is an augmented password-authenticated key exchange (PAKE) protocol, specifically designed to work around existing patents.
Like all PAKE protocols, an eavesdropper or man in the middle cannot obtain enough information to be able to brute-force guess a password or apply a dictionary attack without further interactions with the parties for each guess. Furthermore, being an augmented PAKE protocol, the server does not store password-equivalent data. This means that an attacker who steals the server data cannot masquerade as the client unless they first perform a brute force search for the password.
In layman's terms, during SRP (or any other PAKE protocol) authentication, one party (the "client" or "user") demonstrates to another party (the "server") that they know the password, without sending the password itself nor any other information from which the password can be derived. The password never leaves the client and is unknown to the server.
Furthermore, the server also needs to know about the password (but not the password itself) in order to instigate the secure connection. This means that the server also authenticates itself to the client which prevents phishing without reliance on the user parsing complex URLs.
The only mathematically proven security property of SRP is that it is equivalent to Diffie-Hellman against a passive attacker. NB: source refers to SRP-6 as SRPv4 for reason unknown. While mature and widely deployed, SRP is an older design with some variants showing subtle weaknesses; it is not UC‑secure, lacks resistance to all precomputation attacks, has weaker formal proofs, and offers no protection against certain modern attack models. For these reasons, SRP is now largely considered superseded. OPAQUE is the preferred augmented PAKE, while CPace or SPAKE2 are favored for balanced PAKE scenarios where both parties share the password.
Overview The SRP protocol has a number of desirable properties: it allows a user to authenticate themselves to a server, it is resistant to dictionary attacks mounted by an eavesdropper, and it does not require a trusted third party. It effectively conveys a zero-knowledge password proof from the user to the server. In revision 6 of the protocol only one password can be guessed per connection attempt. One of the interesting properties of the protocol is that even if one or two of the cryptographic primitives it uses are attacked, it is still secure. The SRP protocol has been revised several times, and is currently at revision 6a.
The SRP protocol creates a large private key shared between the two parties in a manner similar to Diffie–Hellman key exchange based on the client side having the user password and the server side having a cryptographic verifier derived from the password. The shared public key is derived from two random numbers, one generated by the client, and the other generated by the server, which are unique to the login attempt. In cases where encrypted communications as well as authentication are required, the SRP protocol is more secure than the alternative SSH protocol and faster than using Diffie–Hellman key exchange with signed messages. It is also independent of third parties, unlike Kerberos.
The SRP protocol, version 3 is described in RFC 2945. SRP version 6a is also used for strong password authentication in SSL/TLS RFC 5054 (in TLS-SRP) and other standards such as EAP Draft. and SAML, and is part of IEEE 1363.2 and ISO/IEC 11770-4.
Protocol The following notation is used in this description of the protocol, version 6: q and N = 2q + 1 are chosen such that both are prime (which makes q a Sophie Germain prime and N a safe prime). N must be large enough so that computing discrete logarithms modulo N is infeasible. All arithmetic is performed in the ring of integers modulo N, . This means that below g should be read as gmod N g is a generator of the multiplicative group . H() is a hash function; e.g., SHA-256. k is a parameter derived by both sides; in SRP-6, k = 3, while in SRP-6a it is derived from N and g : k = H(N, g). It is used to prevent a 2-for-1 guess when an active attacker impersonates the server. s is a salt. is an identifying username. p is the user's password. v is the host's password verifier, v = g where at a minimum x = H(s, p). As x is only computed on the client it is free to choose a stronger algorithm. An implementation could choose to use without affecting any steps required of the host. The standard RFC2945 defines . Use of within x avoids a malicious server from being able to learn if two users share the same password. A and B are random one time ephemeral keys of the user and host respectively. | (pipe) denotes concatenation.
All other variables are defined in terms of these.
First, to establish a password p with server Steve, client Carol picks a random salt s, and computes x = H(s, p), v = g. Steve stores v and s, indexed by , as Carol's password verifier and salt. Carol must not share x with anybody, and must safely erase it at this step, because it is equivalent to the plaintext password p. This step is completed before the system is used as part of the user registration with Steve. Note that the salt s is shared and exchanged to negotiate a session key later so the value could be chosen by either side but is done by Carol so that she can register , s and v in a single registration request. The transmission and authentication of the registration request is not covered in SRP.
Then to perform a proof of password at a later date the following exchange protocol occurs:
Carol → Steve: generate random value a; send and A = g Steve → Carol: generate random value b; send s and B = kv + g Both: u = H(A, B) Carol: SCarol = (B − kg)(a + ux) = (kv + g − kg)(a + ux) = (kg − kg + g)(a + ux) = (g)(a + ux) Carol: KCarol = H(SCarol) Steve: SSteve = (Av) = (gv) = [g(g)] = (ga + ux) = (g)(a + ux) Steve: KSteve = H(SSteve) = KCarol
Now the two parties have a shared, strong session key K. To complete authentication, they need to prove to each other that their keys match. One possible way is as follows:
Carol → Steve: . Steve verifies M1. Steve → Carol: . Carol verifies M2.
This method requires guessing more of the shared state to be successful in impersonation than just the key. While most of the additional state is public, private information could safely be added to the inputs to the hash function, like the server private key.
Alternatively, in a password-only proof the calculation of K can be skipped and the shared S proven with:
Carol → Steve: . Steve verifies M1. Steve → Carol: . Carol verifies M2.
When using SRP to negotiate a shared key K which will be immediately used after the negotiation, it is tempting to skip the verification steps of M1 and M2. The server will reject the very first request from the client which it cannot decrypt. This can however be dangerous as demonstrated in the Implementation Pitfalls section below.
The two parties also employ the following safeguards:
Carol will abort if she receives B = 0 (mod N) or u = 0. Steve will abort if he receives A (mod N) = 0. Carol must show her proof of K (or S) first. If Steve detects that Carol's proof is incorrect, he must abort without showing his own proof of K (or S)
Example code in Python """ An example SRP authentication
WARNING: Do not use for real cryptographic purposes beyond testing. WARNING: This below code misses important safeguards. It does not check A, B, and U are not zero.
based on http://srp.stanford.edu/design.html """ import hashlib import random
# Note: str converts as is, str([1,2,3,4]) will convert to "[1,2,3,4]" def H(*args) -> int: """A one-way hash function.""" a = ":".join(str(a) for a in args) return int(hashlib.sha256(a.encode("utf-8")).hexdigest(), 16)
def cryptrand(n: int = 1024): return random.SystemRandom().getrandbits(n) % N
# A large safe prime (N = 2q+1, where q is prime) # All arithmetic is done modulo N # (generated using "openssl dhparam -text 1024") N = """00:c0:37:c3:75:88:b4:32:98:87:e6:1c:2d:a3:32: 4b:1b:a4:b8:1a:63:f9:74:8f:ed:2d:8a:41:0c:2f: c2:1b:12:32:f0:d3:bf:a0:24:27:6c:fd:88:44:81: 97:aa:e4:86:a6:3b:fc:a7:b8:bf:77:54:df:b3:27: c7:20:1f:6f:d1:7f:d7:fd:74:15:8b:d3:1c:e7:72: c9:f5:f8:ab:58:45:48:a9:9a:75:9b:5a:2c:05:32: 16:2b:7b:62:18:e8:f1:42:bc:e2:c3:0d:77:84:68: 9a:48:3e:09:5e:70:16:18:43:79:13:a8:c3:9c:3d: d0:d4:ca:3c:50:0b:88:5f:e3"""
N = int("".join(N.split()).replace(":", ""), 16) g = 2 # A generator modulo N
k = H(N, g) # Multiplier parameter (k=3 in legacy SRP-6)
F = '#0x' # Format specifier
print("#. H, N, g, and k are known beforehand to both client and server:") print(f'{H = }\n{N = :{F}}\n{g = :{F}}\n{k = :{F}}')
print("\n0. server stores (I, s, v) in its password database")
# The server must first generate the password verifier I = "person" # Username p = "password1234" # Password s = cryptrand(64) # Salt for the user x = H(s, I, p) # Private key v = pow(g, x, N) # Password verifier
print(f'{I = }\n{p = }\n{s = :{F}}\n{x = :{F}}\n{v = :{F}}')
# 0. server stores(I, s, v) in its password database # I = 'person' # p = 'password1234' # s = 0x67bc8932cfd26a49 # x = 0x98a4bce8dde877762a90222f1a1161eba9248590a47eb83aa9e5bd7ecda5368d # v = 0xa7e2038e675d577ac0f318999cab67bba7ec2daf45d2d09f7911b1b78d2fc7f963cd0ac8f17851e0516f059e453672c3b70fcecf5f6843180b271abdd01f552ccda7b24fe4719336409cbc1352f8517be651b8935cc0b74ff2819fa07a3f031537d4cfd9f8df7b788a5f2f88e1cd4106b35c38b3d7205a
# <demo> --- stop ---
print("\n1. client sends username I and public ephemeral value A to the server") a = cryptrand() A = pow(g, a, N) print(f"{I = }\n{A = :{F}}") # client->server (I, A)
# 1. client sends username I and public ephemeral value A to the server # I = 'person' # A = 0x678556a7e76581e051af656e8cee57ae46df43f1fce790f7750a3ec5308a85da4ec4051e5cb74d3e463685ee975a2747cf49035be67c931b56e793f23ea3524af8909dcfbc8675d872361025bf884778587ac49454a57c53a011ac2be2839bfb51bf7847a49a483aba870dc7a8b467a81cec91b8ae7813
# <demo> --- stop ---
print("\n2. server sends user's salt s and public ephemeral value B to client") b = cryptrand() B = (k * v + pow(g, b, N)) % N print(f"{s = :{F}}\n{B = :{F}}") # server->client (s, B)
# 2. server sends user's salt s and public ephemeral value B to client # s = 0x67bc8932cfd26a49 # B = 0xb615a0a5ea6abf138077bbd869f6a8da37dfc0b7e06a9f5fac5c1e4109c6302cb3e94dcc2cc76da7b3d87d7e9b68a1db998ab239cfde609f3f7a1ece4a491ce3d9a665c20cf4e4f06730daaa8f52ed61e45bbb67cdc337bf648027ffa7f0f215d5ebe43f9f51832518f1142266aae0dfa960e0082b5154
# <demo> --- stop ---
print("\n3. client and server calculate the random scrambling parameter") u = H(A, B) # Random scrambling parameter print(f"{u = :{F}}")
# 3. client and server calculate the random scrambling parameter # u = 0x796b07e354c04f672af8b76a46560655086355a9bbce11361f01b45d991c0c52
# <demo> --- stop ---
print("\n4. client computes session key") x = H(s, I, p) S_c = pow(B - k * pow(g, x, N), a + u * x, N) K_c = H(S_c) print(f"{S_c = :{F}}\n{K_c = :{F}}")
# 4. client computes session key # S_c = 0x699170aff6e9f08ed09a1dff432bf0605b8bcba05aadcaeea665757d06dbda4348e211d16c10ef4678585bcb2809a83c62b6c19d97901274ddafd4075f90604c06baf036af587af8540342b47867eaa22b9ca5e35ac14c8e85a0c4e623bd855828dffd513cea4d829c407137a0dd81ab4cde8a904c45cc # K_c = 0x43f8df6e1d2ba762948c8316db5bf03a7af49391742f5f51029630711c1671e
# <demo> --- stop ---
print("\n5. server computes session key") S_s = pow(A * pow(v, u, N), b, N) K_s = H(S_s) print(f"{S_s = :{F}}\n{K_s = :{F}}")
# 5. server computes session key # S_s = 0x699170aff6e9f08ed09a1dff432bf0605b8bcba05aadcaeea665757d06dbda4348e211d16c10ef4678585bcb2809a83c62b6c19d97901274ddafd4075f90604c06baf036af587af8540342b47867eaa22b9ca5e35ac14c8e85a0c4e623bd855828dffd513cea4d829c407137a0dd81ab4cde8a904c45cc # K_s = 0x43f8df6e1d2ba762948c8316db5bf03a7af49391742f5f51029630711c1671e