Introduction
In this article, I’ll walk you through my implementation of the Secure Remote Password (SRP) [1] protocol, including its challenges, architecture, and testing strategies. You can find the full source code on my GitHub repository.
The Secure Remote Password (SRP) protocol is a type of password-authenticated key exchange (PAKE) protocol that has been specifically designed to avoid conflicts with existing patents. It falls under the category of augmented PAKE protocols, which offer enhanced security properties compared to traditional password-based systems.
As with all PAKE protocols, SRP ensures that an attacker who intercepts communication betw…
Introduction
In this article, I’ll walk you through my implementation of the Secure Remote Password (SRP) [1] protocol, including its challenges, architecture, and testing strategies. You can find the full source code on my GitHub repository.
The Secure Remote Password (SRP) protocol is a type of password-authenticated key exchange (PAKE) protocol that has been specifically designed to avoid conflicts with existing patents. It falls under the category of augmented PAKE protocols, which offer enhanced security properties compared to traditional password-based systems.
As with all PAKE protocols, SRP ensures that an attacker who intercepts communication between the client and server cannot gather enough information to guess the password through brute force or dictionary attacks. This is because the protocol requires additional interactions for each password guess, making such attacks impractical. Moreover, since SRP is an augmented PAKE, the server does not store any data that is equivalent to the password. This means that even if an attacker gains access to the server’s database, they cannot impersonate the client unless they first perform a computationally expensive brute-force search to recover the password.
In simpler terms, SRP allows a user (the “client”) to prove to a server that they know their password without ever transmitting the password itself or any data that could be used to reconstruct it. The password remains securely on the client side and is never shared with the server. The server, on the other hand, stores only a “verifier,” which is a cryptographic representation of the password that cannot be reversed to reveal the original password.
Additionally, SRP provides mutual authentication. The server must also prove its legitimacy to the client, ensuring that the client is not connecting to a malicious or fake server. This feature helps protect users from phishing attacks without requiring them to analyze complex URLs or other indicators of legitimacy [2].
SRP’s primary mathematically proven security property is its equivalence to the Diffie-Hellman protocol when facing a passive attacker. While SRP is a mature and widely used protocol, it is based on an older design that has shown some subtle weaknesses in certain variants. For example, SRP is not universally composable (UC-secure), does not fully resist all precomputation attacks, has less robust formal security proofs, and lacks protection against some modern attack scenarios.
As a result, SRP is now considered somewhat outdated. Modern alternatives have emerged, such as OPAQUE, which is the preferred augmented PAKE protocol, and CPace or SPAKE2, which are better suited for balanced PAKE scenarios where both parties share the password. These newer protocols address many of the limitations found in SRP and are designed to meet the demands of contemporary security models [2].
Even so a detailed study of SRP protocol and its implementation is considered a good learning exercise as I will try to show.
The ins and outs of SRP protocol
SRP leverages the computational difficulty of the discrete logarithm problem. Both client and server perform complex mathematical operations involving large prime numbers, generating ephemeral keys and shared secrets that prove knowledge without revelation. The protocol ensures that even if someone intercepts all network traffic, they gain no advantage in discovering the client’s password.
The result is an authentication that’s both more secure and more private than anything passwords alone can provide.
There are two phases in the SRP protocol.
In the first phase the user makes the registration in the server, afterwards he can perform the authentication whenever he needs.
Registration at the SRP protocol
The registration process in the SRP serves one fundamental purpose: to establish a mathematical relationship that enables future authentication without ever requiring password transmission or storage.
The goal of the registration process is to the client and the server come into an agreement about the group parameters, namely the big prime number N and the generator g. Also in this phase the client defines the password and sends it to the server its verifier, without exposing by itself the password, figure 1.
Figure 1: Registration process at the SRP protocol.
Initially the client identifies itself by its username (U) and optionally the requested group ID, the server generates a cryptographically secure, unique per user, random salt and returns also the required N and g from the group ID that ensures the minimum level of security that is acceptable at the server side.
The N and g parameters are chosen according to the group ID defined at the RFC-5054, where the ID 1 correspond to the minimum security level with a smaller prime N (1024 bits size) and the group ID 7 correspond to the the maximum security level with the greater prime N (8192 bits size).
Full information of the groups ID defined at RFC-5054 can be found in this json file used in this implementation.
With a greater group ID, the size of the prime will increase and the hash algorithm will be more complex, that will bring more security but at the cost of extra computation every time this protocol needs to be executed.
After the successful conclusion of the registration process at the SRP protocol the client will have the corresponding salt of that session, and also the given N and g parameters from the required group ID to be used at the SRP protocol with that server.
At the server side, he will have the salt s generated for that given user U, and also the verifier of the password v.
Authentication of the SRP protocol
The authentication process in SRP serves one fundamental purpose: to enable mutual verification of identity between client and server without transmitting the password or any password-equivalent data.
Here is the communication flow between the client and the server at the authentication using the SRP protocol, figure 2:
Figure 2: Authentication process at the SRP protocol [1].
Primary Objectives:
Zero-Knowledge Password Verification
- The authentication process allows the client to prove he knows the password without transmitting the password over the network, revealing any information that could be used to recover the password, giving the server access to password-equivalent data.
Mutual Authentication:
Unlike traditional password systems, SRP authentication provides bidirectional verification:
Client authenticates to Server: “I know my password” (via M proof)
Server authenticates to Client: “I have your legitimate verifier” (via M2 proof)
Secure session establishment: The authentication process generates a shared session key (K) that both parties can compute independently:
Client: S = (B - k * g^x) ^ (a + u * x) mod N
Server: S = (A * v^u)^b mod N
Implementation Details of the SRP protocol
The SRP protocol was implemented in C++, using the HTTP requests library from Crow and regarding the big number arithmetic, it was used the services available at the OpenSSL.
OpenSSL provides cryptographic primitives such as hashing (SHA-1, SHA-256, etc.) and big integer arithmetic (via BIGNUM), ensuring compliance with cryptographic standards and avoids reinventing the wheel for low-level operations.
The HTTP server Implements the SRP protocol as a web service with endpoints for:
- registration (/srp/register/init, /srp/register/complete);
- authentication (/srp/auth/init, /srp/auth/complete);
In this way the clients interact with the server over HTTP, simulating real-world use cases.
The validation of the protocol was performed against python scripts used to cross-validate the C++ implementation against RFC 5054 [1] test vectors, ensuring correctness of key computations (e.g., x, v, S, K, M, M2) by comparing results from both implementations.
The unit tests in C++ also validate all the intermediate results against the RFC 5054 [1] test vectors.
Architecture decisions
Modular Design
To manage the complexity of the SRP protocol, it was adopted a modular and extensible architecture, namely:
Client Class:
- Handles the client-side computations (e.g., A, x, S, K, M).
- Manages the registration and authentication flows from the client’s perspective.
Server Class:
- Handles the server-side computations (e.g., B, S, M2).
- Manages user sessions and validates client proofs during authentication.
SecureRemotePassword Class:
- Encapsulates the SRP protocol logic, including cryptographic operations and parameter validation.
- Acts as the core engine for both the client and server.
SessionData Class:
- Manages session-specific data (e.g., ephemeral keys, group parameters, and state).
- Ensures that each session is isolated and stateless between requests.
SrpParametersLoader:
- Loads SRP group parameters (e.g., N, g) from a JSON file (SrpParameters.json).
- Supports multiple groups (1024-8192 bits) as defined in RFC 5054 [1].
EncryptionUtility:
- Provides helper functions for hashing, big integer operations, and padding.
- Abstracts cryptographic details, making the code more readable and maintainable.
Multi-Group Support:
The implementation supports multiple SRP groups, allowing flexibility in cryptographic strength, accordingly to the server requirements in terms of security:
Groups Supported:
- 1024-bit (Group 1)
- 1536-bit (Group 2)
- 2048-bit (Group 3)
- 3072-bit (Group 4)
- 4096-bit (Group 5)
- 6144-bit (Group 6)
- 8192-bit (Group 7)
It exists a multi group support because different applications may require different levels of security. Smaller groups (e.g., 1024-bit) are faster but less secure, while larger groups (e.g., 8192-bit) provide stronger security at the cost of performance.
Group parameters (N, g) are loaded dynamically from SrpParameters.json. The client and server negotiate the group during the registration phase.
Multiple Hash Algorithms:
To ensure flexibility and compliance with RFC 5054 [1], it was included support for multiple hash algorithms,. namely:
- SHA-1 (to validate against the RFC 5054 [1] test vectors)
- SHA-256
- SHA-384
- SHA-512
Different systems and applications may require different hash algorithms based on security policies, as the requirements of each group varies in relation to the hash algorithms.
SHA-1 is included for compatibility with RFC 5054 [1] test vectors, but stronger hashes (e.g., SHA-256, SHA-512) are recommended for modern systems.
Hash functions are abstracted in the EncryptionUtility class. The hash algorithm is negotiated during the registration phase and stored as part of the session.
Architecture Highlights
The registration flow is composed of these requests by the client:
- /srp/register/init: The client requests SRP parameters (e.g., N, g, salt) from the server;
- /srp/register/complete: The client sends the computed verifier (v) to the server, which stores it for future authentication.
If the registration proceeds until completion then the client is then able to perform the authentication with the server afterwards.
The authentication flow is implemented as two HTTP endpoints:
- /srp/auth/init: The client sends its username (U) to the server. The server responds with s, B, and groupId.
- /srp/auth/complete: The client sends A and M to the server. The server validates M and responds with M2 for mutual authentication.
Both client and server compute the shared secret S independently. The session key K = H(S) is derived for secure communication.
The Class diagram of the implementation of the SRP protocol is available at the figure 3:
Figure 3: Class diagram of the SRP protocol implementation
Figure 4: Sequence diagram of the SRP protocol implementation
Testing & Validation Strategy
Testing and validation were critical to ensuring the correctness, security, and compliance the SRP implementation with RFC 5054[1]. A multi-layered approach was adopted to cover all aspects of the protocol, from individual components to full end-to-end flows.
Unit Tests: Test cases across all Components
Unit testing was the foundation of the validation strategy. Each component of the SRP protocol was tested in isolation to ensure correctness and robustness.
Key Areas Covered:
- Mathematical Operations: Modular exponentiation, padding, and big integer arithmetic using OpenSSL’s BIGNUM.
- Hashing Functions: Validation of SHA-1, SHA-256, SHA-384, and SHA-512 wrappers.
- Parameter Validation: Ensuring constraints like 1 < A < N and 1 < B < N are enforced.
- Session Management: Testing the SessionData class for proper state handling.
Example Test Cases:
- Verifying that calculateU produces the correct scrambling parameter u for given inputs.
- Ensuring calculateX correctly derives the private key x from the salt, username, and password.
- Validating calculateV to ensure the verifier v matches RFC 5054[1] test vectors.
RFC Vector Testing: Validation Against Official Test Vectors:
To ensure compliance with the SRP protocol as defined in RFC 5054 [1], the implementation was tested against the official test vectors provided in the specification.
Test Vector input parameters:
- Username: “alice”
- Password: “password123”
- Salt: “BEB25379D1A8581EB5A727673A2441EE”
- Group: 1024-bit (Group 1)
- Hash Algorithm: SHA-1
- Private key a: “60975527035CF2AD1989806F0407210BC81EDC04E2762A56AFD529DDDA2D4393”
- Private key b: “E487CB59D31AC550471E81F00F6928E01DDA08E974A004F49E61F5D105284D20”
Validate Outputs:
- Public keys (A, B)
- Scrambling parameter (u)
- Shared secret (S)
- Session key (K)
- Intermediate results: (k, x)
All computed values matched the RFC 5054 [1] test vectors, confirming the correctness the implementation, these test vector were essential to confirm the entire correctness of this implementation.
Cross-Language Validation: Python scripts for mathematical verification
To further validate the correctness of the C++ implementation, Python scripts were used to cross-check key computations.
Python Validation Scripts:
- calculateX.py: Validates the private key x.
- calculateV.py: Validates the verifier v.
- calculateSClient.py and calculateSServer.py: Validate the shared secret S on both client and server sides.
- calculateM.py and calculateM2.py: Validate the client and server proofs.
- calculate_k_MultiplierParameter.py : Verifies the correctness of the multipliers k for each group.
- calculateK.py: Validates the Key generation K.
- calculatePublicKey.py: Validates the public key generation A and B.
- calculateU.py: Validates the parameter u calculation.
- testSRP_withRFC_vector.py: Test that the S at the client and server match with RFC-5054 test vectors.
Python’s simplicity and precision in handling big integers made it ideal for cross-referencing results. The scripts served as a secondary implementation to catch potential bugs in the C++ code during the development.
Integration Testing: Full Protocol Flows
Integration tests were designed to validate the entire SRP protocol flow, from registration to authentication.
Tested flow:
- Registration: Ensuring the client computes v correctly and the server stores it securely.
- Authentication: Verifying that both client and server compute the same shared secret S and session key K.
Edge cases:
- Invalid usernames or passwords.
- Mismatched group parameters (N, g).
Manual Testing: Curl Commands for Endpoint Validation
Manual testing was performed using curl’s to simulate client-server interactions and validate the HTTP endpoints.
Endpoints Tested:
- /srp/register/init: Validates the server’s response with group parameters and salt.
- /srp/register/complete: Ensures the server correctly stores the verifier.
- /srp/auth/init: Validates the server’s generation of B and response to the client.
- /srp/auth/complete: Ensures mutual authentication and session key derivation.
Example curl command:
curl -X POST http://localhost:18080/srp/auth/init \
-H "Content-Type: application/json" \
-d '{
"clientId": "alice"
}' | jq
This multi-layered approach to testing and validation was essential to build a robust and standards-compliant SRP implementation.
Lessons Learned & Best Practices
Implementing the Secure Remote Password (SRP) protocol from scratch was both a challenging and rewarding experience. Along the way, I encountered several key insights and developed best practices that are applicable not only to SRP but to cryptographic implementations in general.
Mathematical Precision is Crucial
Cryptographic protocols like SRP rely heavily on precise mathematical operations. Even small errors in modular arithmetic, padding, or hashing can lead to incorrect results or security vulnerabilities.
- Lesson Learned: Always validate intermediate results against known test vectors (e.g., RFC 5054 [1]).
- Best Practice: Use well-tested libraries like OpenSSL for low-level operations (e.g., BIGNUM) instead of implementing them from scratch.
Cross-Validation Saves Time
Cross-language validation was invaluable in catching subtle bugs. Python scripts were used to verify the correctness of key computations (e.g., x, v, S, K) against the C++ implementation.
- Lesson Learned: A secondary implementation in a simpler language (like Python) can help identify discrepancies early.
- Best Practice: Write small, focused scripts to validate each step of the protocol independently.
Comprehensive Testing is Essential
Testing was the backbone of this implementation. From unit tests to integration tests, every component was rigorously validated, this was the solution to handle the complexity of the protocol.
- Lesson Learned: Cryptographic protocols require more than just functional testing; they need validation against edge cases and compliance with standards.
- Best Practice: Use official test vectors (e.g., RFC 5054 [1]) to ensure compliance.Test with multiple group sizes (1024-bit to 8192-bit) and hash algorithms (SHA-1 to SHA-512). Include negative tests (e.g., invalid inputs, mismatched parameters).
Modular Design Simplifies Complexity
SRP is a complex protocol with multiple moving parts (registration, authentication, cryptographic computations). A modular design made it easier to manage and debug.
- Lesson Learned: Breaking the implementation into smaller, reusable components (e.g., Client, Server, SecureRemotePassword) reduces complexity and improves maintainability.
- Best Practice: Follow the single responsibility principle—each class or module should handle one specific aspect of the protocol.
Documentation is key for maintainability
Cryptographic protocols are inherently complex, and maintaining clarity is critical. Detailed documentation, including UML diagrams, helped keep the implementation organized and understandable to onboard future developers and manage complexity.
- Lesson Learned: Clear documentation is as important as the code itself, especially for protocols with intricate flows like SRP.
- Best Practice: Use UML diagrams to visualize class relationships and sequence flows. Document every step of the protocol, including assumptions, constraints, and edge cases.
Security is Non-Negotiable
SRP is designed to be secure, but the implementation must also follow best practices to avoid introducing vulnerabilities.
- Lesson Learned: Security is not just about following the protocol; it’s about ensuring every detail is implemented correctly.
- Best Practice: Use cryptographically secure random number generators for ephemeral keys. Validate all inputs (e.g., A, B, u) to ensure they meet protocol constraints. Clear sensitive data (e.g., passwords, session keys) from memory after use.
Cross-Disciplinary Knowledge is Key
Implementing SRP required knowledge of cryptography, software engineering, and network protocols. Understanding the theoretical foundations (e.g., zero-knowledge proofs, modular arithmetic) was just as important as writing the code.
- Lesson Learned: Cryptographic implementations demand a balance of theoretical understanding and practical engineering skills.
- Best Practice: Invest time in studying the underlying mathematics and security principles before diving into the implementation.
Iterative Development Works Best
The SRP implementation was built iteratively, starting with the registration flow, followed by the authentication flow, and finally the integration of all components.
- Lesson Learned: Tackling one part of the protocol at a time reduces the risk of being overwhelmed and allows for focused testing.
- Best Practice: Break the implementation into milestones and validate each step before moving to the next.
Standards Are Your Guide
RFC 5054 [1] provided a clear road map for implementing SRP. Adhering to the standard ensured that the implementation was interoperable and secure.
- Lesson Learned: Standards like RFC 5054 [1] are invaluable for ensuring correctness and interoperability.
- Best Practice: Always refer to the official specification and test against its requirements.
Quick Demo of the SRP protocol
In this section it is shown a quick demo of the SRP protocol, and in the end the unit tests are executed as well. The server process is launched first to ensure that the client’s request reach it’s destination, video 1: SRP protocol implementation demo in the first part, complete set of unit tests in the last part of the video.
Explore the Code
If you’re interested in diving deeper into the implementation, you can find the full source code, including all tests and Python validation scripts, on my GitHub repository: GitHub Repository - SRP Implementation.
References:
[1] D. Taylor, T. Wu, N. Mavrogiannopoulos, and T. Perrin, “Using the Secure Remote Password (SRP) Protocol for TLS Authentication,” IETF RFC 5054, November 2007. [Online]. Available: https://datatracker.ietf.org/doc/html/rfc5054.
[2] Wikipedia, “Secure Remote Password Protocol,” [Online]. Available: https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol.