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
<PackageReference Include="Vaultic.OPAQUE.Net" Version="1.3.0" />
<PackageVersion Include="Vaultic.OPAQUE.Net" Version="1.3.0" />
<PackageReference Include="Vaultic.OPAQUE.Net" />
paket add Vaultic.OPAQUE.Net --version 1.3.0
#r "nuget: Vaultic.OPAQUE.Net, 1.3.0"
#addin nuget:?package=Vaultic.OPAQUE.Net&version=1.3.0
#tool nuget:?package=Vaultic.OPAQUE.Net&version=1.3.0
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
- Never accidentally log passwords
- Security against pre-computation attacks upon server compromise
- Foundation for encrypted backups and end-to-end encryption apps
- Original repo had a penetration test and whitebox security review done through OTF’s Red Team Lab, via 7ASecurity
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.
RFC Draft Recommended
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 | Versions 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. |
-
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 |