Expand description
An implementation of the OPAQUE augmented password authentication key exchange protocol
§Minimum Supported Rust Version
Rust 1.85 or higher.
§Overview
OPAQUE is a protocol between a client and a server. They must first agree on a collection of primitives to be kept consistent throughout protocol execution. These include:
- a finite cyclic group along with a point representation
- for the OPRF and
- for the key exchange
- a key exchange protocol,
- a hashing function, and
- a key stretching function.
We will use the following choices in this example:
use opaque_ke::CipherSuite;
struct Default;
impl CipherSuite for Default {
type OprfCs = opaque_ke::Ristretto255;
type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
type Ksf = opaque_ke::ksf::Identity;
}See examples/simple_login.rs for a working example of a simple password-based login using OPAQUE.
Note that our choice of key stretching function in this example, Identity,
is selected only to ensure that the tests execute quickly. A real
application should use an actual key stretching function, such as Argon2,
which can be enabled through the argon2 feature. See more details in
the features section.
§Setup
To set up the protocol, the server begins by creating a ServerSetup
object:
use rand::RngCore;
use rand::rngs::OsRng;
let mut rng = OsRng;
let server_setup = ServerSetup::<Default>::new(&mut rng);The server must persist an instance of ServerSetup for the registration
and login steps, and can use ServerSetup::serialize and
ServerSetup::deserialize to save and restore the instance.
§Registration
The registration protocol between the client and server consists of four
steps along with three messages: RegistrationRequest,
RegistrationResponse, and RegistrationUpload. A successful execution
of the registration protocol results in the server producing a password file
corresponding to a server-side identifier for the client, along with the
password provided by the client. This password file is typically stored in a
key-value database, where the keys consist of these server-side identifiers
for each client, and the values consist of their corresponding password
files, to be retrieved upon future login attempts made by the client.
It is your responsibility to ensure that the identifier used to form the
initial RegistrationRequest, typically supplied by the client, matches
the database key used in the final RegistrationUpload step.
Note that the RegistrationUpload message contains sensitive information
(about as sensitive as a hash of the password), and hence should be
protected with confidentiality guarantees by the consumer of this library.
§Client Registration Start
In the first step of registration, the client chooses as input a
registration password. The client runs ClientRegistration::start to
produce a ClientRegistrationStartResult, which consists of a
RegistrationRequest to be sent to the server and a
ClientRegistration which must be persisted on the client for the final
step of client registration.
use opaque_ke::ClientRegistration;
use rand::RngCore;
use rand::rngs::OsRng;
let mut client_rng = OsRng;
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut client_rng, b"password")?;§Server Registration Start
In the second step of registration, the server takes as input a persisted
instance of ServerSetup, a RegistrationRequest from the client, and
a server-side identifier for the client. The server runs
ServerRegistration::start to produce a
ServerRegistrationStartResult, which consists of a
RegistrationResponse to be returned to the client.
use opaque_ke::ServerRegistration;
let server_registration_start_result = ServerRegistration::<Default>::start(
&server_setup,
client_registration_start_result.message,
b"alice@example.com",
)?;§Client Registration Finish
In the third step of registration, the client takes as input a
RegistrationResponse from the server, and a ClientRegistration from
the first step of registration. The client runs
ClientRegistration::finish to
produce a ClientRegistrationFinishResult, which consists of a
RegistrationUpload to be sent to the server and an export_key field
which can be used optionally as described in the Export Key
section.
use opaque_ke::ClientRegistrationFinishParameters;
let client_registration_finish_result = client_registration_start_result.state.finish(
&mut client_rng,
b"password",
server_registration_start_result.message,
ClientRegistrationFinishParameters::default(),
)?;§Server Registration Finish
In the fourth step of registration, the server takes as input a
RegistrationUpload from the client, and a ServerRegistration from
the second step. The server runs ServerRegistration::finish to produce a
finalized ServerRegistration. At this point, the client can be
considered as successfully registered, and the server can invoke
ServerRegistration::serialize to store the password file for use during
the login protocol.
let password_file = ServerRegistration::<Default>::finish(
client_registration_finish_result.message,
);§Login
The login protocol between a client and server also consists of four steps
along with three messages: CredentialRequest, CredentialResponse,
CredentialFinalization. The server is expected to have access to the
password file corresponding to an output of the registration phase (see
Dummy Server Login for handling the scenario where no
password file is available). The login protocol will execute successfully
only if the same password was used in the registration phase that produced
the password file that the server is testing against.
§Client Login Start
In the first step of login, the client chooses as input a login password.
The client runs ClientLogin::start to produce an output consisting of a
CredentialRequest to be sent to the server, and a ClientLogin which
must be persisted on the client for the final step of client login.
use opaque_ke::ClientLogin;
let mut client_rng = OsRng;
let client_login_start_result = ClientLogin::<Default>::start(&mut client_rng, b"password")?;§Server Login Start
In the second step of login, the server takes as input a persisted instance
of ServerSetup, the password file output from registration, a
CredentialRequest from the client, and a server-side identifier for the
client. The server runs ServerLogin::start to produce an output
consisting of a CredentialResponse which is returned to the client, and
a ServerLogin which must be persisted on the server for the final step
of login.
use opaque_ke::{ServerLogin, ServerLoginParameters};
let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes)?;
let mut server_rng = OsRng;
let server_login_start_result = ServerLogin::start(
&mut server_rng,
&server_setup,
Some(password_file),
client_login_start_result.message,
b"alice@example.com",
ServerLoginParameters::default(),
)?;Note that if there is no corresponding password file found for the user, the
server can use None in place of Some(password_file) in order to generate
a CredentialResponse that is indistinguishable from a valid
CredentialResponse returned for a registered client. This allows the
server to prevent leaking information about whether or not a client has
previously registered with the server.
§Client Login Finish
In the third step of login, the client takes as input a
CredentialResponse from the server and runs ClientLogin::finish
on it.
If the authentication is successful, then the client obtains a
ClientLoginFinishResult. Otherwise, on failure, the
algorithm outputs an
InvalidLoginError error.
The resulting ClientLoginFinishResult obtained by client in this step
contains, among other things, a CredentialFinalization to be sent to the
server to complete the protocol, and a
session_key
which will match the server’s session key upon a successful login.
use opaque_ke::ClientLoginFinishParameters;
let client_login_finish_result = client_login_start_result.state.finish(
&mut client_rng,
b"password",
server_login_start_result.message,
ClientLoginFinishParameters::default(),
)?;§Server Login Finish
In the fourth step of login, the server takes as input a
CredentialFinalization from the client and runs ServerLogin::finish
to produce an output consisting of the session_key sequence of bytes which
will match the client’s session key upon a successful login.
let server_login_finish_result = server_login_start_result.state.finish(
client_login_finish_result.message,
ServerLoginParameters::default(),
)?;
assert_eq!(
client_login_finish_result.session_key,
server_login_finish_result.session_key,
);If the protocol completes successfully, then the server obtains a
server_login_finish_result.session_key which is guaranteed to match
client_login_finish_result.session_key (see the Session
Key section). Otherwise, on failure, the
ServerLogin::finish algorithm outputs the error
InvalidLoginError.
§Advanced Usage
This implementation offers support for several optional features of OPAQUE, described below. They are not critical to the execution of the main protocol, but can provide additional security benefits which can be suitable for various applications that rely on OPAQUE for authentication.
§Session Key
Upon a successful completion of the OPAQUE protocol (the client runs login
with the same password used during registration), the client and server have
access to a session key, which is a pseudorandomly distributed byte
string (of length equal to the output size of voprf::CipherSuite::Hash)
which only the client and server know. Multiple login runs using the
same password for the same client will produce different session keys,
distributed as uniformly random strings. Thus, the session key can be used
to establish a secure channel between the client and server.
The session key can be accessed from the session_key field of
ClientLoginFinishResult and ServerLoginFinishResult. See the
combination of Client Login Finish and Server Login
Finish for example usage.
§Checking Server Consistency
A ClientLoginFinishResult contains the server_s_pk field, which is
represents the static public key of the server that is established during
the setup phase. This can be used by the client to verify the authenticity
of the server it engages with during the login phase. In particular, the
client can check that the static public key of the server supplied during
registration (with the server_s_pk field of
ClientRegistrationFinishResult) matches this field during login.
// During registration, the client obtains a ClientRegistrationFinishResult with
// a server_s_pk field
let client_registration_finish_result = client_registration_start_result.state.finish(
&mut client_rng,
b"password",
server_registration_start_result.message,
ClientRegistrationFinishParameters::default(),
)?;
// And then later, during login...
let client_login_finish_result = client_login_start_result.state.finish(
&mut client_rng,
b"password",
server_login_start_result.message,
ClientLoginFinishParameters::default(),
)?;
// Check that the server's static public key obtained from login matches what
// was obtained during registration
assert_eq!(
&client_registration_finish_result.server_s_pk,
&client_login_finish_result.server_s_pk,
);Note that without this check over the consistency of the server’s static public key, a malicious actor could impersonate the registration server if it were able to copy the password file output during registration! Therefore, it is recommended to perform the following check in the application layer if the client can obtain a copy of the server’s static public key beforehand.
§Export Key
The export key is a pseudorandomly distributed byte string
(of length equal to the output size of voprf::CipherSuite::Hash) output
by both the Client Registration Finish and
Client Login Finish steps. The same export key
string will be output by both functions only if the exact same password is
passed to ClientRegistration::start and ClientLogin::start.
The export key retains as much secrecy as the password itself, and is similarly derived through an evaluation of the key stretching function. Hence, only the parties which know the password the client uses during registration and login can recover this secret, as it is never exposed to the server. As a result, the export key can be used (separately from the OPAQUE protocol) to provide confidentiality and integrity to other data which only the client should be able to process. For instance, if the server is expected to maintain any client-side secrets which require a password to access, then this export key can be used to encrypt these secrets so that they remain hidden from the server (see examples/digital_locker.rs for a working example).
You can access the export key from the export_key field of
ClientRegistrationFinishResult and ClientLoginFinishResult.
// During registration...
let client_registration_finish_result = client_registration_start_result.state.finish(
&mut client_rng,
b"password",
server_registration_start_result.message,
ClientRegistrationFinishParameters::default()
)?;
// And then later, during login...
let client_login_finish_result = client_login_start_result.state.finish(
&mut client_rng,
b"password",
server_login_start_result.message,
ClientLoginFinishParameters::default(),
)?;
assert_eq!(
client_registration_finish_result.export_key,
client_login_finish_result.export_key,
);§Custom Identifiers
Typically when applications use OPAQUE to authenticate a client to a server, the client has a registered username which is sent to the server to identify the corresponding password file established during registration. This username may or may not coincide with the server-side identifier; however, this username must be known to both the client and the server (whereas the server-side identifier does not need to be exposed to the client). The server may also have an identifier corresponding to an entity (e.g. Facebook). By default, neither of these public identifiers need to be supplied to the OPAQUE protocol.
But, for applications that wish to cryptographically bind these identities
to the registered password file as well as the session key output by the
login phase, these custom identifiers can be specified through
ClientRegistrationFinishParameters in Client Registration
Finish:
let client_registration_finish_result = client_registration_start_result.state.finish(
&mut client_rng,
b"password",
server_registration_start_result.message,
ClientRegistrationFinishParameters::new(
Identifiers {
client: Some(b"Alice_the_Cryptographer"),
server: Some(b"Facebook"),
},
None,
),
)?;The same identifiers must also be supplied using ServerLoginParameters
in Server Login Start:
let server_login_start_result = ServerLogin::start(
&mut server_rng,
&server_setup,
Some(password_file),
client_login_start_result.message,
b"alice@example.com",
ServerLoginParameters {
context: None,
identifiers: Identifiers {
client: Some(b"Alice_the_Cryptographer"),
server: Some(b"Facebook"),
},
},
)?;as well as ClientLoginFinishParameters in Client Login
Finish:
let client_login_finish_result = client_login_start_result.state.finish(
&mut client_rng,
b"password",
server_login_start_result.message,
ClientLoginFinishParameters::new(
None,
Identifiers {
client: Some(b"Alice_the_Cryptographer"),
server: Some(b"Facebook"),
},
None,
),
)?;
and in ServerLoginParameters in Server Login
Finish:
let server_login_finish_result = server_login_start_result.state.finish(
client_login_finish_result.message,
ServerLoginParameters { context: None, identifiers: Identifiers { client: Some(b"Alice_the_Cryptographer"), server: Some(b"Facebook") } },
)?;
Failing to supply the same pair of custom identifiers in any of the three steps above will result in an error in attempting to complete the protocol!
Note that if only one of the client and server identifiers are present, then Identifiers can be used to specify them individually.
§Key Exchange Context
A key exchange protocol typically allows for the specifying of shared “context” information between the two parties before the exchange is complete, so as to bind the integrity of application-specific data or configuration parameters to the security of the key exchange. During the login phase, the client and server can specify this context using:
- In Server Login Start, where the server can
populate
ServerLoginParameters::context. - In Client Login Finish, where the client can
populate
ClientLoginFinishParameters::context. - In Server Login Finish, where the server can
populate
ServerLoginParameters::context.
§Dummy Server Login
For applications in which the server does not wish to reveal to the client
whether an existing password file has been registered, the server can return
a “dummy” credential response message to the client for an unregistered
client, which is indistinguishable from the normal credential response
message that the server would return for a registered client. The dummy
message is created by passing a None to the password_file parameter for
ServerLogin::start.
§Remote Private Keys
Servers that want to store their private key in an external location (e.g.
in an HSM or vault) can do so with ServerLogin::builder() without
exposing the bytes of the private key to this library.
use opaque_ke::{ServerLogin, ServerLoginParameters, ServerSetup};
use opaque_ke::keypair::{KeyPair, PrivateKeySerialization};
use opaque_ke::errors::ProtocolError;
// Implement if you intend to use `ServerSetup::de/serialize` instead of `serde`.
impl PrivateKeySerialization<Ristretto255> for YourRemoteKey {
type Error = YourRemoteKeyError;
type Len = U0;
fn serialize_key_pair(_: &KeyPair<Ristretto255, Self>) -> GenericArray<u8, Self::Len> {
unimplemented!()
}
fn deserialize_take_key_pair(input: &mut &[u8]) -> Result<KeyPair<Ristretto255, Self>, ProtocolError<Self::Error>> {
unimplemented!()
}
}
let keypair = KeyPair::new(remote_key, public_key);
let server_setup = ServerSetup::<Default, YourRemoteKey>::new_with_key_pair(&mut server_rng, keypair);
// Use `ServerLogin::builder()` instead of `ServerLogin::start()`.
let server_login_builder = ServerLogin::builder(
&mut server_rng,
&server_setup,
Some(password_file),
client_login_start_result.message,
b"alice@example.com",
ServerLoginParameters::default(),
)?;
// Run Diffie-Hellman on your remote key.
let client_e_public_key = server_login_builder.data();
let shared_secret = server_login_builder.private_key().diffie_hellman(&client_e_public_key)?;
// Use the shared secret to build `ServerLogin`.
let server_login_start_result = server_login_builder.build(shared_secret)?;§Remote OPRF Seeds
In addition, the OPRF seed can be stored in an external location as well, by
using ServerRegistration::start_with_key_material() and
ServerLogin::builder_with_key_material() in combination with
ServerSetup::key_material_info().
use opaque_ke::{ServerLogin, ServerLoginParameters, ServerRegistration, ServerSetup};
use opaque_ke::keypair::{KeyPair, OprfSeedSerialization};
use opaque_ke::errors::ProtocolError;
// Implement if you intend to use `ServerSetup::de/serialize` instead of `serde`.
impl OprfSeedSerialization<sha2::Sha512, YourRemoteSecretsError> for YourRemoteSeed {
type Len = U0;
fn serialize(&self) -> GenericArray<u8, Self::Len> {
unimplemented!()
}
fn deserialize_take(input: &mut &[u8]) -> Result<YourRemoteSeed, ProtocolError<YourRemoteSecretsError>> {
unimplemented!()
}
}
let keypair = KeyPair::new(remote_key, public_key);
let server_setup = ServerSetup::<Default, YourRemoteKey, YourRemoteSeed>::new_with_key_pair_and_seed(&mut server_rng, keypair, oprf_seed);
// Incoming registration ...
// Run HKDF on your remote OPRF seed.
let info = server_setup.key_material_info(b"alice@example.com");
let key_material = info.ikm.hkdf(&info.info);
// Use `ServerRegistration::start_with_key_material()` instead of `ServerRegistration::start()`.
let server_registration_start_result = ServerRegistration::<Default>::start_with_key_material(
&server_setup,
key_material,
client_registration_start_result.message,
)?;
// Finish registration ...
// Incoming login ...
// Run HKDF on your remote OPRF seed.
let info = server_setup.key_material_info(b"alice@example.com");
let key_material = info.ikm.hkdf(&info.info);
// Use `ServerLogin::builder_with_key_material()` instead of `ServerLogin::start()`.
let server_login_builder = ServerLogin::builder_with_key_material(
&mut server_rng,
&server_setup,
key_material,
Some(password_file),
client_login_start_result.message,
ServerLoginParameters::default(),
)?;
// Run Diffie-Hellman on your remote key.
let client_e_public_key = server_login_builder.data();
let shared_secret = server_login_builder.private_key().diffie_hellman(&client_e_public_key)?;
// Use the shared secret to build `ServerLogin`.
let server_login_start_result = server_login_builder.build(shared_secret)?;§Custom KSF and Parameters
An application might want to use a custom KSF (Key Stretching Function)
that’s not supported directly by this crate. The maintainer of the said KSF
or of the application itself can implement the Ksf trait to
use it with opaque-ke. scrypt is used for this example, but any KSF
can be used.
use opaque_ke::ksf::Ksf;
#[derive(Default)]
struct CustomKsf(scrypt::Params);
// The Ksf trait must be implemented to be used in the ciphersuite.
impl Ksf for CustomKsf {
fn hash<L: generic_array::ArrayLength<u8>>(
&self,
input: GenericArray<u8, L>,
) -> Result<GenericArray<u8, L>, opaque_ke::errors::InternalError> {
let mut output = GenericArray::<u8, L>::default();
scrypt::scrypt(&input, &[], &self.0, &mut output)
.map_err(|_| opaque_ke::errors::InternalError::KsfError)?;
Ok(output)
}
}It is also possible to override the default derivation parameters that are
used by the KSF during registration and login. This can be especially
helpful if the Ksf trait is already implemented.
// Create an Argon2 instance with the specified parameters
let argon2_params = argon2::Params::new(131072, 2, 4, None).unwrap();
let argon2_params = argon2::Argon2::new(
argon2::Algorithm::Argon2id,
argon2::Version::V0x13,
argon2_params,
);
// Override the default parameters with the custom ones
let hash_params = ClientRegistrationFinishParameters {
ksf: Some(&argon2_params),
..Default::default()
};
let client_registration_finish_result = client_registration_start_result
.state
.finish(
&mut rng,
password,
server_registration_start_result.message,
hash_params,
)
.unwrap();§Features
-
The
argon2feature, when enabled, introduces a dependency onargon2and implements theKsftrait forArgon2with a set of default parameters. In general, secure instantiations should choose to invoke a memory-hard password hashing function when the client’s password is expected to have low entropy, instead of relying onksf::Identityas done in the above example. The more computationally intensive theKsffunction is, the more resistant the server’s password file records will be against offline dictionary and precomputation attacks; see the OPAQUE paper for more details. Theargon2feature requiresalloc. -
The
serdefeature, enabled by default, provides convenience functions for serializing and deserializing with serde. -
The
ristretto255feature enables usingRistretto255as aKeGroupandOprfCs. To select a specific backend see the curve25519-dalek documentation. -
The
curve25519feature enables Curve25519 as aKeGroup. To select a specific backend see the curve25519-dalek documentation. -
The
ecdsafeature enables usingelliptic_curves withEcdsaforSigmaIs signature algorithm. -
The
ed25519feature enables usingEd25519s withPureEddsaandHashEddsaforSigmaIs signature algorithm.
Re-exports§
pub use crate::ciphersuite::CipherSuite;pub use crate::key_exchange::group::curve25519::Curve25519;curve25519pub use crate::key_exchange::group::ed25519::Ed25519;ed25519pub use crate::key_exchange::group::ristretto255::Ristretto255;ristretto255pub use crate::key_exchange::sigma_i::SigmaI;pub use crate::key_exchange::sigma_i::ecdsa::Ecdsa;ecdsapub use crate::key_exchange::sigma_i::hash_eddsa::HashEddsa;pub use crate::key_exchange::sigma_i::pure_eddsa::PureEddsa;pub use crate::key_exchange::tripledh::TripleDh;pub use argon2;argon2pub use generic_array;pub use rand;
Modules§
- ciphersuite
- Defines the
CipherSuitetrait to specify the underlying primitives for OPAQUE - errors
- A list of error types which are produced during an execution of the protocol
- hash
- A convenience trait for digest bounds used throughout the library
- key_
exchange - Includes instantiations of key exchange protocols used in the login step for OPAQUE
- keypair
- Contains the keypair types that must be supplied for the OPAQUE API
- ksf
- Trait specifying a key stretching function
Structs§
- Client
Login - The state elements the client holds to perform a login
- Client
Login Finish Parameters - Optional parameters for client login finish
- Client
Login Finish Result - Contains the fields that are returned by a client login finish
- Client
Login Start Result - Contains the fields that are returned by a client login start
- Client
Registration - The state elements the client holds to register itself
- Client
Registration Finish Parameters - Optional parameters for client registration finish
- Client
Registration Finish Result - Contains the fields that are returned by a client registration finish
- Client
Registration Start Result - Contains the fields that are returned by a client registration start
- Credential
Finalization - The answer sent by the client to the server, upon reception of the sealed envelope
- Credential
Request - The message sent by the user to the server, to initiate registration
- Credential
Response - The answer sent by the server to the user, upon reception of the login attempt
- Identifiers
- Options for specifying custom identifiers
- KeyMaterial
Info - The information required to generate the key material for
ServerRegistration::start_with_key_material()andServerLogin::builder_with_key_material(). - Registration
Request - The message sent by the client to the server, to initiate registration
- Registration
Response - The answer sent by the server to the user, upon reception of the registration attempt
- Registration
Upload - The final message from the client, containing sealed cryptographic identifiers
- Server
Login - The state elements the server holds to record a login
- Server
Login Builder - Builder for
ServerLoginwhen using remote keys. - Server
Login Finish Result - Contains the fields that are returned by a server login finish
- Server
Login Parameters - Optional parameters for server login start and finish
- Server
Login Start Result - Contains the fields that are returned by a server login start
- Server
Registration - The state elements the server holds to record a registration
- Server
Registration Start Result - Contains the fields that are returned by a server registration start. Note that there is no state output in this step
- Server
Setup - The state elements the server holds upon setup
Type Aliases§
- Credential
Finalization Len - Length of
CredentialFinalizationin bytes for serialization. - Credential
Request Len - Length of
CredentialRequestin bytes for serialization. - Credential
Response Len - Length of
CredentialResponsein bytes for serialization. - Registration
Request Len - Length of
RegistrationRequestin bytes for serialization. - Registration
Response Len - Length of
RegistrationResponsein bytes for serialization. - Registration
Upload Len - Length of
RegistrationUploadin bytes for serialization. - Server
Registration Len - Length of
ServerRegistrationin bytes for serialization.