Merge pull request #21 from huitseeker/digest-on-group
Restructure password-hashing-to-the-curve as an extension trait of Group
This commit is contained in:
+9
-6
@@ -7,19 +7,22 @@
|
||||
|
||||
use crate::{
|
||||
errors::InternalPakeError,
|
||||
group::Group,
|
||||
keypair::{Key, KeyPair},
|
||||
map_to_curve::GroupWithMapToCurve,
|
||||
slow_hash::SlowHash,
|
||||
};
|
||||
use generic_array::typenum::{U32, U64};
|
||||
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
|
||||
/// Configures the underlying primitives used in OPAQUE
|
||||
/// * Group: a finite cyclic group along with a point representation
|
||||
/// * KeyFormat: a keypair type composed of public and private components
|
||||
/// * SlowHash: a slow hashing function, typically used for password hashing
|
||||
/// * `Group`: a finite cyclic group along with a point representation, along
|
||||
/// with an extension trait PasswordToCurve that allows some customization on
|
||||
/// how to hash a password to a curve point. See `group::Group` and
|
||||
/// `map_to_curve::GroupWithMapToCurve`.
|
||||
/// * `KeyFormat`: a keypair type composed of public and private components
|
||||
/// * `SlowHash`: a slow hashing function, typically used for password hashing
|
||||
pub trait CipherSuite {
|
||||
type Group: Group<ScalarLen = U32, UniformBytesLen = U64>;
|
||||
type Group: GroupWithMapToCurve;
|
||||
type KeyFormat: KeyPair<Repr = Key> + PartialEq;
|
||||
type SlowHash: SlowHash;
|
||||
|
||||
|
||||
+14
-22
@@ -11,14 +11,16 @@ use generic_array::{
|
||||
use hkdf::Hkdf;
|
||||
use hmac::{Hmac, Mac, NewMac};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use sha2::{Digest, Sha256};
|
||||
use sha2::Sha256;
|
||||
|
||||
// Constant string used as salt for HKDF computation
|
||||
const STR_ENVU: &[u8] = b"EnvU";
|
||||
|
||||
/// The length of the "export key" output by the client registration
|
||||
/// and login finish steps
|
||||
pub(crate) type ExportKeySize = <Sha256 as Digest>::OutputSize;
|
||||
pub(crate) type ExportKeySize = U32;
|
||||
|
||||
const NONCE_LEN: usize = 32;
|
||||
|
||||
/// This struct is an instantiation of the envelope as described in
|
||||
/// https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06#section-4
|
||||
@@ -33,42 +35,32 @@ pub(crate) type ExportKeySize = <Sha256 as Digest>::OutputSize;
|
||||
pub(crate) struct Envelope {
|
||||
nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
hmac: Vec<u8>,
|
||||
hmac: GenericArray<u8, U32>,
|
||||
}
|
||||
|
||||
type NonceLen = U32;
|
||||
|
||||
impl Envelope {
|
||||
/// The additional number of bytes added to the plaintext
|
||||
pub(crate) fn additional_size() -> usize {
|
||||
Self::nonce_size() + <Sha256 as Digest>::OutputSize::to_usize()
|
||||
NONCE_LEN + U32::to_usize()
|
||||
}
|
||||
|
||||
fn hmac_key_size() -> usize {
|
||||
<Sha256 as Digest>::OutputSize::to_usize()
|
||||
U32::to_usize()
|
||||
}
|
||||
|
||||
fn hmac_size() -> usize {
|
||||
<Sha256 as Digest>::OutputSize::to_usize()
|
||||
}
|
||||
|
||||
fn nonce_size() -> usize {
|
||||
NonceLen::to_usize()
|
||||
U32::to_usize()
|
||||
}
|
||||
|
||||
fn export_key_size() -> usize {
|
||||
ExportKeySize::to_usize()
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
hmac: &GenericArray<u8, <Sha256 as Digest>::OutputSize>,
|
||||
) -> Self {
|
||||
pub(crate) fn new(nonce: Vec<u8>, ciphertext: Vec<u8>, hmac: GenericArray<u8, U32>) -> Self {
|
||||
Self {
|
||||
nonce,
|
||||
ciphertext,
|
||||
hmac: hmac.to_vec(),
|
||||
hmac,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,13 +68,13 @@ impl Envelope {
|
||||
/// nonce | ciphertext | hmac
|
||||
/// nonce_size bytes | variable length | hmac_size bytes
|
||||
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self, InternalPakeError> {
|
||||
let ciphertext_start = Self::nonce_size();
|
||||
let ciphertext_start = NONCE_LEN;
|
||||
let ciphertext_end = bytes.len() - Self::hmac_size();
|
||||
|
||||
Ok(Self::new(
|
||||
bytes[..ciphertext_start].to_vec(),
|
||||
bytes[ciphertext_start..ciphertext_end].to_vec(),
|
||||
GenericArray::from_slice(&bytes[ciphertext_end..]),
|
||||
GenericArray::clone_from_slice(&bytes[ciphertext_end..]),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -98,7 +90,7 @@ impl Envelope {
|
||||
aad: &[u8],
|
||||
rng: &mut R,
|
||||
) -> Result<(Self, GenericArray<u8, ExportKeySize>), InternalPakeError> {
|
||||
let mut nonce = vec![0u8; Self::nonce_size()];
|
||||
let mut nonce = vec![0u8; NONCE_LEN];
|
||||
rng.fill_bytes(&mut nonce);
|
||||
|
||||
let h = Hkdf::<Sha256>::new(Some(&nonce), &key);
|
||||
@@ -121,7 +113,7 @@ impl Envelope {
|
||||
hmac.update(&aad);
|
||||
|
||||
Ok((
|
||||
Self::new(nonce, ciphertext.to_vec(), &hmac.finalize().into_bytes()),
|
||||
Self::new(nonce, ciphertext.to_vec(), hmac.finalize().into_bytes()),
|
||||
*GenericArray::from_slice(&export_key),
|
||||
))
|
||||
}
|
||||
|
||||
+1
-1
@@ -140,7 +140,7 @@ impl Group for EdwardsPoint {
|
||||
*GenericArray::from_slice(c.as_bytes())
|
||||
}
|
||||
|
||||
type UniformBytesLen = U64;
|
||||
type UniformBytesLen = U32;
|
||||
fn hash_to_curve(uniform_bytes: &GenericArray<u8, Self::UniformBytesLen>) -> Self {
|
||||
let mut result = [0u8; 32];
|
||||
let mut counter = 0;
|
||||
|
||||
+4
-4
@@ -9,7 +9,7 @@ use crate::{
|
||||
sized_bytes_using_constant_and_try_from,
|
||||
};
|
||||
use generic_array::{
|
||||
typenum::{U64, U96},
|
||||
typenum::{U32, U64, U96},
|
||||
GenericArray,
|
||||
};
|
||||
use hkdf::Hkdf;
|
||||
@@ -199,9 +199,9 @@ struct TripleDHComponents {
|
||||
|
||||
// Consists of a shared secret, followed by two mac keys
|
||||
type TripleDHDerivationResult = (
|
||||
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
|
||||
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
|
||||
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
|
||||
GenericArray<u8, U32>,
|
||||
GenericArray<u8, U32>,
|
||||
GenericArray<u8, U32>,
|
||||
);
|
||||
|
||||
// Internal function which takes the public and private components of the client and server keypairs, along
|
||||
|
||||
@@ -380,9 +380,13 @@ pub mod opaque;
|
||||
|
||||
pub mod ciphersuite;
|
||||
mod envelope;
|
||||
|
||||
mod group;
|
||||
mod map_to_curve;
|
||||
|
||||
mod key_exchange;
|
||||
pub mod keypair;
|
||||
|
||||
mod oprf;
|
||||
pub mod slow_hash;
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
use crate::group::Group;
|
||||
use curve25519_dalek::{edwards::EdwardsPoint, ristretto::RistrettoPoint};
|
||||
|
||||
use generic_array::GenericArray;
|
||||
use hkdf::Hkdf;
|
||||
|
||||
pub trait GroupWithMapToCurve: Group {
|
||||
fn map_to_curve(password: &[u8], pepper: Option<&[u8]>) -> Self;
|
||||
}
|
||||
|
||||
impl GroupWithMapToCurve for RistrettoPoint {
|
||||
fn map_to_curve(password: &[u8], pepper: Option<&[u8]>) -> Self {
|
||||
let (hashed_input, _) = Hkdf::<sha2::Sha512>::extract(pepper, password);
|
||||
<Self as Group>::hash_to_curve(GenericArray::from_slice(&hashed_input))
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupWithMapToCurve for EdwardsPoint {
|
||||
fn map_to_curve(password: &[u8], pepper: Option<&[u8]>) -> Self {
|
||||
let (hashed_input, _) = Hkdf::<sha2::Sha256>::extract(pepper, password);
|
||||
<Self as Group>::hash_to_curve(GenericArray::from_slice(&hashed_input))
|
||||
}
|
||||
}
|
||||
+13
-17
@@ -3,11 +3,10 @@
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
use crate::{errors::InternalPakeError, group::Group};
|
||||
use generic_array::{typenum::U64, GenericArray};
|
||||
use crate::{errors::InternalPakeError, group::Group, map_to_curve::GroupWithMapToCurve};
|
||||
use generic_array::{typenum::U32, GenericArray};
|
||||
use hkdf::Hkdf;
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
pub(crate) struct OprfClientBytes<Grp: Group> {
|
||||
pub(crate) alpha: Grp,
|
||||
@@ -18,15 +17,14 @@ pub(crate) struct OprfClientBytes<Grp: Group> {
|
||||
/// message is sent from the client (who holds the input) to the server (who holds the OPRF key).
|
||||
/// The client can also pass in an optional "pepper" string to be mixed in with the input through
|
||||
/// an HKDF computation.
|
||||
pub(crate) fn generate_oprf1<R: RngCore + CryptoRng, G: Group<UniformBytesLen = U64>>(
|
||||
pub(crate) fn generate_oprf1<R: RngCore + CryptoRng, G: GroupWithMapToCurve>(
|
||||
input: &[u8],
|
||||
pepper: Option<&[u8]>,
|
||||
blinding_factor_rng: &mut R,
|
||||
) -> Result<OprfClientBytes<G>, InternalPakeError> {
|
||||
let (hashed_input, _) = Hkdf::<Sha256>::extract(pepper, &input);
|
||||
let curve_input: Vec<u8> = [hashed_input.as_slice(), &[0u8; 32]].concat();
|
||||
let mapped_point = G::map_to_curve(input, pepper);
|
||||
let blinding_factor = G::random_scalar(blinding_factor_rng);
|
||||
let alpha = G::hash_to_curve(GenericArray::from_slice(&curve_input)) * &blinding_factor;
|
||||
let alpha = mapped_point * &blinding_factor;
|
||||
Ok(OprfClientBytes {
|
||||
alpha,
|
||||
blinding_factor,
|
||||
@@ -48,10 +46,10 @@ pub(crate) fn generate_oprf3<G: Group>(
|
||||
input: &[u8],
|
||||
point: G,
|
||||
blinding_factor: &G::Scalar,
|
||||
) -> Result<GenericArray<u8, <Sha256 as Digest>::OutputSize>, InternalPakeError> {
|
||||
) -> Result<GenericArray<u8, U32>, InternalPakeError> {
|
||||
let unblinded = point * &G::scalar_invert(&blinding_factor);
|
||||
let ikm: Vec<u8> = [&unblinded.to_arr()[..], input].concat();
|
||||
let (prk, _) = Hkdf::<Sha256>::extract(None, &ikm);
|
||||
let (prk, _) = Hkdf::<sha2::Sha256>::extract(None, &ikm);
|
||||
Ok(prk)
|
||||
}
|
||||
|
||||
@@ -66,14 +64,14 @@ mod tests {
|
||||
use generic_array::{arr, GenericArray};
|
||||
use hkdf::Hkdf;
|
||||
use rand_core::OsRng;
|
||||
use sha2::{Digest, Sha256, Sha512};
|
||||
|
||||
fn prf(
|
||||
input: &[u8],
|
||||
oprf_key: &[u8; 32],
|
||||
) -> GenericArray<u8, <RistrettoPoint as Group>::ElemLen> {
|
||||
let (hashed_input, _) = Hkdf::<Sha256>::extract(None, &input);
|
||||
let curve_input: Vec<u8> = [hashed_input.as_slice(), &[0u8; 32]].concat();
|
||||
let point = RistrettoPoint::hash_to_curve(GenericArray::from_slice(&curve_input));
|
||||
let (hashed_input, _) = Hkdf::<Sha512>::extract(None, &input);
|
||||
let point = RistrettoPoint::hash_to_curve(GenericArray::from_slice(&hashed_input));
|
||||
let scalar =
|
||||
RistrettoPoint::from_scalar_slice(GenericArray::from_slice(&oprf_key[..])).unwrap();
|
||||
let res = point * scalar;
|
||||
@@ -114,14 +112,12 @@ mod tests {
|
||||
} = generate_oprf1::<_, RistrettoPoint>(&input, None, &mut rng).unwrap();
|
||||
let res = generate_oprf3::<RistrettoPoint>(&input, alpha, &blinding_factor).unwrap();
|
||||
|
||||
let (hashed_input, _) = Hkdf::<Sha256>::extract(None, &input);
|
||||
let mut curve_input: Vec<u8> = Vec::new();
|
||||
curve_input.extend_from_slice(&hashed_input);
|
||||
curve_input.extend_from_slice(&[0u8; 32]);
|
||||
let (hashed_input, _) = Hkdf::<Sha512>::extract(None, &input);
|
||||
|
||||
// This is because RistrettoPoint is on an obsolete sha2 version
|
||||
let mut bits = [0u8; 64];
|
||||
let mut hasher = sha2::Sha512::new();
|
||||
hasher.update(&curve_input[..]);
|
||||
Digest::update(&mut hasher, &hashed_input[..]);
|
||||
bits.copy_from_slice(&hasher.finalize());
|
||||
|
||||
let point = RistrettoPoint::from_uniform_bytes(&bits);
|
||||
|
||||
+4
-11
@@ -7,33 +7,26 @@
|
||||
|
||||
use crate::errors::InternalPakeError;
|
||||
|
||||
use generic_array::GenericArray;
|
||||
use sha2::{Digest, Sha256};
|
||||
use generic_array::{typenum::U32, GenericArray};
|
||||
|
||||
/// Used for the slow hashing function in OPAQUE
|
||||
pub trait SlowHash {
|
||||
/// Computes the slow hashing function
|
||||
fn hash(
|
||||
input: GenericArray<u8, <Sha256 as Digest>::OutputSize>,
|
||||
) -> Result<Vec<u8>, InternalPakeError>;
|
||||
fn hash(input: GenericArray<u8, U32>) -> Result<Vec<u8>, InternalPakeError>;
|
||||
}
|
||||
|
||||
/// A no-op hash which simply returns its input
|
||||
pub struct NoOpHash;
|
||||
|
||||
impl SlowHash for NoOpHash {
|
||||
fn hash(
|
||||
input: GenericArray<u8, <Sha256 as Digest>::OutputSize>,
|
||||
) -> Result<Vec<u8>, InternalPakeError> {
|
||||
fn hash(input: GenericArray<u8, U32>) -> Result<Vec<u8>, InternalPakeError> {
|
||||
Ok(input.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "slow-hash")]
|
||||
impl SlowHash for scrypt::ScryptParams {
|
||||
fn hash(
|
||||
input: GenericArray<u8, <Sha256 as Digest>::OutputSize>,
|
||||
) -> Result<Vec<u8>, InternalPakeError> {
|
||||
fn hash(input: GenericArray<u8, U32>) -> Result<Vec<u8>, InternalPakeError> {
|
||||
let params = scrypt::ScryptParams::new(15, 8, 1).unwrap();
|
||||
let mut output = [0u8; 32];
|
||||
scrypt::scrypt(&input, &[], ¶ms, &mut output)
|
||||
|
||||
@@ -247,13 +247,14 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
|
||||
)
|
||||
.unwrap();
|
||||
let r1_bytes = r1.to_bytes().to_vec();
|
||||
let blinding_factor_bytes = *CS::Group::scalar_as_bytes(&client_registration.blinding_factor);
|
||||
let blinding_factor_bytes =
|
||||
CS::Group::scalar_as_bytes(&client_registration.blinding_factor).clone();
|
||||
let client_registration_state = client_registration.to_bytes().to_vec();
|
||||
|
||||
let mut oprf_key_rng = CycleRng::new(oprf_key_raw.to_vec());
|
||||
let (r2, server_registration) = ServerRegistration::<CS>::start(r1, &mut oprf_key_rng).unwrap();
|
||||
let r2_bytes = r2.to_bytes().to_vec();
|
||||
let oprf_key_bytes = *CS::Group::scalar_as_bytes(&server_registration.oprf_key);
|
||||
let oprf_key_bytes = CS::Group::scalar_as_bytes(&server_registration.oprf_key).clone();
|
||||
let server_registration_state = server_registration.to_bytes().to_vec();
|
||||
|
||||
let mut client_s_sk_and_nonce: Vec<u8> = Vec::new();
|
||||
|
||||
Reference in New Issue
Block a user