#client-server #srp #workflow #key #salt #user

easy-srp

easy-srp wraps the rust srp crate and provides an easy to use API

3 releases (breaking)

0.3.0 Aug 29, 2024
0.2.0 Aug 28, 2024
0.1.0 Aug 26, 2024

#311 in Cryptography

Download history 212/week @ 2024-08-22 200/week @ 2024-08-29 1/week @ 2024-09-05 16/week @ 2024-09-12

429 downloads per month

BSD-3-Clause

33KB
455 lines

Wrapper crate for the srp for easy usage

Overview

The srp crate provides the means for using SRP6a athentication. This crate wraps the srp crate to make the authentication workflows more obvious easy to use.

This is done via the following means:

  • provide Workflow structs for the 3 typical workflows:
    • generation of initial registration data
    • client authentication
    • server authentication
  • name methods step# with increasing number #, so it is obvious in which phase of the authentication the method needs to be called.
  • include automatic random value generation (for ephemeral keys and salt)

Features

  • Support for the web via WebAssembly. Therefore, this crate can be used with WASM-based web-UI frameworks like yew.
  • Easy to use.

Available rust/cargo features

  • client: Include client classes (ClientAuthenticationWorkflow, ClientRegistrationWorkflow, ...).
  • server: Include server classes (ServerAuthenticationWorkflow, etc.)
  • base64: Include the base64 crate and provide base64 serialization for various byte array parameters.
  • serialization: Include the serde crate and add Serialize and Deserialize to various structs. This also enables the base64 feature.
  • js: This crate uses the getrandom crate for generating random numbers (salt, client/server ephemeral key). getrandom supports a variety of platforms, including web browsers (in WebAssembly). If easy-srp is used inside the browser, use the js feature to make getrandom use the Crypto.getRandomValues() JavaScript method for random number generation.

Usage

SRP6a protocol

User registration

  1. User enters username and password
  2. Client generates random salt
  3. Client computes verifier from the above credentials
  4. Client sends username, salt and verifier to the server via a secure channel.
  5. Server stores these three credentials.

User authentication

Client Server
(username, pub_a) ->
<- (salt, pub_b)
(proof_a) ->
<- (proof_b)
  1. client generates ephemeral private key a and derices its public key pub_a
  2. client temporarily stores a for this authentication session.
  3. client sends (username, pub_a) to the server.
  4. server looks up (salt, verifier) for username.
  5. server temporarily stores pub_a for this authentication session.
  6. server computes an ephemeral private key b and derives its public key pub_b.
  7. server sends (salt, pub_b) to the client.
  8. client calculates proof proof_a and sends (proof_a) to the server.
  9. server verifies proof_a
  10. server computes its own proof proof_b and sends it to the client
  11. client verifies proof_b
  12. both sides are now able to calculate a common secret key. the size of the key depends on the used digest, e.g. 256 bits (32 bytes) for SHA256

Generate client registration data

use sha2::Sha256;
use easy_srp::groups::G_4096;
use easy_srp::client::{ClientRegistrationWorkflow, GenerateVerifierParams};

let verifier_wf = ClientRegistrationWorkflow::<Sha256>::new(&G_4096);

let username = "lorem".to_string();
let password = "ipsum".to_string();

let verifier = verifier_wf.generate_verifier(GenerateVerifierParams {
  username: username.clone(),
  password: password.clone(),
  salt: None
}).expect("could not generate verifier.");

Client example

In this example, the step#_on_server(...) methods are just placeholders to show what data needs to be sent to the server and what data the server needs to reply with.

use sha2::Sha256;
use easy_srp::groups::G_4096;
use easy_srp::client::{ClientAuthenticationWorkflow, ClientStep1Result,
  ClientStep3Result, ClientStep3Params}

let username = "someuser";
let password = "somepassword";

let client_wf = crate::client::ClientAuthenticationWorkflow::<Sha256>::new(&G_4096);
// compute ephemeral key of the client:
let step1_result: ClientStep1Result = client_wf.step1().expect("could not compute step1");
// send username and step1_result.client_public_a to the server and get salt and server_public_a
let (salt, server_public_b) = step2_on_server(username, step1_result.client_public_a);
let step3_result: ClientStep3Result<Sha256> = client_wf.step3(ClientStep3Params {
  client_a: step1_result.client_private_a.as_slice(),
  username: username.clone(),
  password: password.clone(),
  salt,
  server_public_b
}).expect("could not compute step3");
let server_proof = step4_on_server(step3_result.proof());
step3_result.verify_server(server_proof).expect("invalid server proof.");
let key = step3_result.key();

Server example

In this example, the step#_on_client(...) methods are placeholders to show what data needs to be sent to/received from the client. The retrieve_stored_credentials(username) method is a placeholder to demonstrate that salt and stored_verifier (the verifier saved in the user registration process) need to be looked up given the username.

use sha2::Sha256;
use easy_srp::groups::G_4096;

// receive username and client ephemeral public key from client
let (username, client_public_a) = step1_on_client();
let (salt, stored_verifier) = retrieve_stored_credentials(username);
let server_wf = crate::server::ServerAuthenticationWorkflow::<Sha256>::new(test_group);
let step2_result: ServerStep2Result = server_wf.step2(ServerStep2Params {
  stored_verifier: verifier.verifier.as_slice()
}).expect("could not compute step2");
let step4_result: ServerStep4Result<Sha256> = server_wf.step4(ServerStep4Params {
  client_public_a: step1_result.client_public_a.as_slice(),
  server_private_b: step2_result.server_private_b.as_slice(),
  stored_verifier: verifier.verifier.as_slice(),
  client_proof: step3_result.proof()
}).expect("could not compute step4");
// verify the client proof before sending any encrypted data (including the
// server proof) to the client.
step4_result.verify_client(step3_result.proof())
  .expect("could not verify client.");
let key = step4_result.key();

Dependencies

The following dependencies are included intentionally (aka directly):

Additional dependencies may be included transitively.

License

This project is licensed under the BSD-3-Clause license. See LICENSE.txt for the full license.

Dependencies

~1–1.6MB
~33K SLoC