Vaultic.OPAQUE.Net 1.3.0

Prefix Reserved
dotnet add package Vaultic.OPAQUE.Net --version 1.3.0
                    
NuGet\Install-Package Vaultic.OPAQUE.Net -Version 1.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Vaultic.OPAQUE.Net" Version="1.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Vaultic.OPAQUE.Net" Version="1.3.0" />
                    
Directory.Packages.props
<PackageReference Include="Vaultic.OPAQUE.Net" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Vaultic.OPAQUE.Net --version 1.3.0
                    
#r "nuget: Vaultic.OPAQUE.Net, 1.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#addin nuget:?package=Vaultic.OPAQUE.Net&version=1.3.0
                    
Install Vaultic.OPAQUE.Net as a Cake Addin
#tool nuget:?package=Vaultic.OPAQUE.Net&version=1.3.0
                    
Install Vaultic.OPAQUE.Net as a Cake Tool

Opaque

Secure password based client-server authentication without the server ever obtaining knowledge of the password.

A C# implementation of the OPAQUE protocol forked from serenity-key/opaque.

Benefits

Documentation

In depth documentation can be found at https://opaque-auth.com/ (written for the original repo)

Requirements

This package will only work when running on the x64 platform. This is due to the rust dll. See Configuring build platforms for assistance.

Install

The package is available via nuget as Vaultic.OPAQUE.Net. You can use the GUI within Visual Studio or run the command from the nuget package manager console to install it

Install-Package Vaultic.OPAQUE.Net

Usage

The API is exposed through the OpaqueServer and OpaqueClient objects.

OpaqueServer server = new OpaqueServer();
OpaqueClient client = new OpaqueClient();

Server Setup

The server setup is a one-time operation. It is used to generate the server's long-term private key. The result is a 171 long string. Only store it in a secure location and make sure you have it available in your application e.g. via an environment variable.

You can generate a new server setup on the fly:

server.CreateSetup(out string? serverSetup);

Keep in mind that changing the serverSetup will invalidate all existing password files.

Registration Flow

// client
string password = "sup-krah.42-UOI"; // user password
if (!client.StartRegistration(
    password, 
    out StartClientRegistrationResult? startClientRegistrationResult))
{
    // handle failed 
}
// server
string userIdentifier = "20e14cd8-ab09-4f4b-87a8-06d2e2e9ff68"; // userId/email/username
if (!server.CreateRegistrationResponse(
    serverSetup, 
    userIdentifier, 
    startClientRegistrationResult.RegistrationRequest, 
    out string? registrationResponse))
{
    // handle failed
}
// client
if (!client.FinishRegistration(
    password, 
    registrationResponse, 
    startClientRegistrationResult.ClientRegistrationState, 
    out FinishClientRegistrationResult? finishClientRegistrationResult))
{
    // handle failed
}
// send finishClientRegistrationResult.RegistrationRecord to server and create user account

Login Flow

// client
string password = "sup-krah.42-UOI"; // user password
if (!client.StartLogin(
    password, 
    out StartClientLoginResult? startClientLoginResult))
{
    // handle failed
}
// server
string userIdentifier = "20e14cd8-ab09-4f4b-87a8-06d2e2e9ff68"; // userId/email/username
string registrationRecord; // retrieve original registration record

if (!server.StartLogin(
    serverSetup, 
    startClientLoginResult.StartLoginRequest, 
    userIdentifier, 
    registrationRecord, 
    out StartServerLoginResult? startServerLoginResult))
{
    // handle failed
}
// client
if (!client.FinishLogin(
    startClientLoginResult.ClientLoginState, 
    startServerLoginResult.LoginResponse, 
    password, 
    out FinishClientLoginResult? finishClientLoginResult))
{
    // handle failed
}
// server

if (!server.FinishLogin(
    startServerLoginResult.ServerLoginState, 
    finishClientLoginResult.FinishLoginRequest,
    out string? sessionKey))
{
    // handle failed
}

Advanced usage

ExportKey

After the initial registration flow as well as every login flow, the client has access to a private key only available to the client. This is the exportKey. The key is not available to the server and it is stable. Meaning if you log in multiple times your exportKey will stay the same.

Example usage

Registration

// client
if (!client.FinishRegistration(
    password, 
    registrationResponse, 
    startClientRegistrationResult.ClientRegistrationState, 
    out FinishClientRegistrationResult? finishClientRegistrationResult))
{
    // handle failed
}

finishClientRegistrationResult.ExportKey;

Login

// client
if (!client.FinishLogin(
    startClientLoginResult.ClientLoginState, 
    startServerLoginResult.LoginResponse, 
    password, 
    out FinishClientLoginResult? finishClientLoginResult))
{
    // handle failed
}

finishClientLoginResult.ExportKey;

Server Static Public Key

The result of client.FinishRegistration and client.FinishLogin also contains a property ServerStaticPublicKey. It can be used to verify the authenticity of the server.

It's recommended to verify the server static public key in the application layer e.g. hard-code it into the application code and verify it's correctness.

Example usage

Server

The ServerStaticPublicKey can be extracted like so:

if (!server.GetPublicKey(
    serverSetupString, 
    out string? publicKey))
{
    // handle failed
}

Client

Registration

// client
if (!client.FinishRegistration(
    password, 
    registrationResponse, 
    startClientRegistrationResult.ClientRegistrationState, 
    out FinishClientRegistrationResult? finishClientRegistrationResult))
{
    // handle failed
}

finishClientRegistrationResult.ServerStaticPublicKey;

Login

// client
if (!client.FinishLogin(
    startClientLoginResult.ClientLoginState, 
    startServerLoginResult.LoginResponse, 
    password, 
    out FinishClientLoginResult? finishClientLoginResult))
{
    // handle failed
}

finishClientLoginResult.ServerStaticPublicKey;

Identifiers

By default the server-side sets a userIdentifier during the registration and login process. This userIdentifier does not even need to be exposed to be exposed to a client.

client.FinishRegistration, server.StartLogin and client.FinishLogin all have overloads that take a optional string value for clientIdentifier and serverIdentifier.

client.FinishRegistration(
    password, 
    registrationResponse, 
    clientRegistrationState, 
    clientIdentifier, 
    serverIdentifier, 
    out FinishClientRegistrationResult? result);

server.StartLogin(
    serverSetup, 
    startLoginRequest, 
    userIdentifier, 
    registrationRecord, 
    clientIdentitiy, 
    serverIdentity, 
    out StartServerLoginResult? result);

client.FinishLogin(
    clientLoginState, 
    serverLoginResponse, 
    password, 
    clientIdentifier, 
    serverIdentifier, 
    out FinishClientLoginResult? result);

The identifiers will be public, but cryptographically bound to the registration record.

Once provided in the client.FinishRegistration function call, the identical identifiers must be provided in the server.StartLogin and client.FinishLogin function call. Otherwise the login will result in an error.

Example Registration
// client
if (!client.FinishRegistration(
    password, 
    registrationResponse, 
    startClientRegistrationResult.ClientRegistrationState, 
    "jane@example.com", 
    "mastodon.example.com", 
    out FinishClientRegistrationResult? result))
{
    // handle failed
}
// send registrationRecord to server and create user account
Example Login
// server
if (!server.StartLogin(
    serverSetup, 
    startClientLoginResult.StartLoginRequest, 
    userIdentifier, 
    registrationRecord, 
    "jane@example.com", 
    "mastodon.example.com", 
    out StartServerLoginResult? startServerLoginResult))
{
    // handle failed
}
// client
if (!client.FinishLogin(
    startClientLoginResult.ClientLoginState, 
    startServerLoginResult.LoginResponse, 
    password, 
    "jane@example.com", 
    "mastodon.example.com", 
    out FinishClientLoginResult? finishClientLoginResult))
{
    // handle failed
}

Key Stretching

The password input is passed through a key stretching function before being used in the OPRF. The key stretching function is argon2id. Depending on the application these parameters can be adjusted using the param config in client.FinishRegistration and client.FinishLogin Available options are:

Memory constrained (default)

This is the default in case the option is omitted. It is based on the recommendation for memory-constrained environments in the Argon2 RFC. It was chosen as a default based on this feedback.

Parameters:

  • Memory: 2^16
  • Iterations: 3
  • Parallelism: 4
{
  keyStretching: "memory-constrained";
}

Note: This configuration is faster, but less secure. client.FinishRegistration and client.FinishLogin each take around 1 seconds to complete on a MacBook Pro M1, 2020, 16 GB Memory.

This options is based on the recommendation in the Configurations section in the OPAQUE protocol with the exception that the memory is set to (2^21)-1 instead of (2^21) since we noticed (2^21) caused it to crash when running the registration in a browser environment.

Parameters:

  • Memory: 2^21-1
  • Iterations: 1
  • Parallelism: 4
{
  keyStretching: "rfc-draft-recommended";
}

Note: This configuration is the most secure but also the slowest. client.FinishRegistration and client.FinishLogin each take around 13 seconds to complete on a MacBook Pro M1, 2020, 16 GB Memory.

Custom

You can also provide custom parameters for the key stretching function. The parameters are passed directly to the argon2id function. In case you provide an invalid configuration, the function will throw an error.


int iterations = 1;
int memory = 65536;
int parallelism = 4;

KSFConfig config = KSFConfig.Create(KSFConfigType.Custom, iterations, memory, parallelism);
Example usage

Registration

// client
KSFConfig config = KSFConfig.Create(KSFConfigType.RfcDraftRecommended);

if (!client.FinishRegistration(password, serverRegistrationResponse!,
   clientRegistrationResult.ClientRegistrationState, clientIdentifier, serverIdentifier, config,
   out FinishClientRegistrationResult? finishRegistrationResult))
{
   throw new Exception();
}

Login

// client
KSFConfig config = KSFConfig.Create(KSFConfigType.RfcDraftRecommended);

if (!client.FinishLogin(clientLoginResult.ClientLoginState, serverLoginResult!.LoginResponse, password,
   clientIdentifier, serverIdentifier, config, out FinishClientLoginResult? finishClientLoginResult))
{
   throw new Exception();
}
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.3.0 137 4/7/2025
1.2.2 151 7/9/2024
1.2.1 98 7/9/2024
1.2.0 106 7/8/2024
1.1.14 125 7/8/2024
1.1.13 122 7/6/2024
1.1.12 142 7/6/2024
1.1.11 114 7/6/2024
1.1.9 128 7/6/2024
1.1.8 109 7/6/2024
1.1.7 96 7/6/2024
1.1.6 123 7/6/2024
1.1.5 97 7/6/2024
1.1.4 106 7/6/2024
1.1.3 119 7/6/2024
1.1.2 114 7/6/2024
1.1.1 117 7/6/2024
1.1.0 119 7/5/2024
1.0.7 103 7/5/2024
1.0.6 118 7/5/2024
1.0.5 99 7/5/2024
1.0.4 119 6/13/2024
1.0.3 102 6/13/2024
1.0.2 101 6/13/2024
1.0.1 100 6/13/2024
1.0.0 101 6/13/2024