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:
François Garillot
2020-07-03 21:10:11 -04:00
committed by GitHub
9 changed files with 75 additions and 63 deletions
+9 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+4
View File
@@ -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;
+23
View File
@@ -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
View File
@@ -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
View File
@@ -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, &[], &params, &mut output)
+3 -2
View File
@@ -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();