Key agreement protocols provide a secure way for two parties to share a secret without the need to transmit an encryption key. Instead, the encryption key can be derived by the receiver using their private key and the sender's public key.
By ensuring that no man-in-the-middle has access to the receiver's private key, the shared secret remains resistant to interception.
The process of sharing a secret between parties A and B using this scheme can be described as follows:
Both Alice (the sender) and Bob (the receiver) possess a keypair.
Alice derives a shared key_s by combining her private key_a with Bob's public key_b. This process is known as key agreement.
Alice encrypts the secret using the derived key_s.
Alice sends the encrypted secret to Bob, without needing to transmit the shared key_s.
Bob receives the encrypted secret and derives the same key_s using his private key_b and Alice's public key_a.
Bob decrypts the secret using the derived key_s.
To implement this scheme in Rust using the Ring cryptography library:
Generate Alice's keypair
let rng = rand::SystemRandom::new();
let alice_private_key = agreement::EphemeralPrivateKey::generate(&agreement::X25519, &rng).unwrap();
let alice_public_key = my_private_key.compute_public_key().unwrap();
Create a keypair for Bob and expose only the public key to Alice
let bob_public_key = {
let bob_public_key = {
let bob_private_key =
agreement::EphemeralPrivateKey::generate(&agreement::X25519, &rng).unwrap();
bob_private_key.compute_public_key().unwrap()
};
agreement::UnparsedPublicKey::new(&agreement::X25519, bob_public_key)
};
Alice uses her private key and Bob's public key to derive a shared key
agreement::agree_ephemeral(
my_private_key,
&peer_public_key,
ring::error::Unspecified,
|_key_material| {},
)
.unwrap();
Inside the closure, _key_material represents the shared key resulting from the key agreement. However, it is not recommended to use _key_material directly as the final key. Instead, it is advisable to derive another key by combining _key_material with the public keys of both Alice and Bob. This approach is recommended in RFC 7748 (Section 6.1).
Alice and Bob can then use a key-derivation function that includes K, K_A, and K_B to derive a symmetric key.
Here is a full implementation:
agreement::agree_ephemeral(
alice_private_key,
&bob_public_key,
ring::error::Unspecified,
|_key_material| {
let salt = ring::hkdf::Salt::new(ring::hkdf::HKDF_SHA256, b"test");
let public_keys = [my_public_key.as_ref(), peer_public_key.bytes().as_ref()];
let prk = salt.extract(_key_material);
let okm = prk.expand(&public_keys, ring::hkdf::HKDF_SHA256).unwrap();
let mut final_r : [u8;32] = [0; 32];
okm.fill(&mut final_r).unwrap();
Ok(final_r)
},
)
.unwrap();
First, we generate a salt with random data to enhance the security of the key derivation process.
Next, we utilize the key_material, which represents the shared key resulting from the key agreement, to create a pseudo-random key.
Finally, we combine the generated pseudo-random key with the public keys from both parties (Alice and Bob) and apply a key-derivation function to obtain the derived key.