Skip to content

Commit

Permalink
Don't inadvertently perform explicit rejection in Kyber.
Browse files Browse the repository at this point in the history
  • Loading branch information
xvzcf committed Sep 26, 2023
1 parent 21a26b7 commit 09a2604
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 40 deletions.
56 changes: 32 additions & 24 deletions src/kem/kyber768.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,16 @@ pub fn generate_keypair(
let ind_cpa_keypair_randomness = &randomness[0..parameters::CPA_PKE_KEY_GENERATION_SEED_SIZE];
let implicit_rejection_value = &randomness[parameters::CPA_PKE_KEY_GENERATION_SEED_SIZE..];

let ind_cpa_key_pair = ind_cpa::generate_keypair(ind_cpa_keypair_randomness)?;
let (ind_cpa_key_pair, sampling_a_error) =
ind_cpa::generate_keypair(ind_cpa_keypair_randomness);

let secret_key_serialized = ind_cpa_key_pair.serialize_secret_key(implicit_rejection_value);

Ok((ind_cpa_key_pair.pk(), secret_key_serialized))
if sampling_a_error.is_some() {
Err(sampling_a_error.unwrap())
} else {
Ok((ind_cpa_key_pair.pk(), secret_key_serialized))
}
}

pub fn encapsulate(
Expand All @@ -71,14 +76,19 @@ pub fn encapsulate(
let hashed = G(&to_hash);
let (k_not, pseudorandomness) = hashed.split_at(32);

let ciphertext = ind_cpa::encrypt(public_key, randomness_hashed, pseudorandomness)?;
let (ciphertext, sampling_a_error) =
ind_cpa::encrypt(public_key, randomness_hashed, pseudorandomness);

let mut to_hash: [u8; 2 * H_DIGEST_SIZE] = into_padded_array(&k_not);
to_hash[H_DIGEST_SIZE..].copy_from_slice(&H(&ciphertext));

let shared_secret: Kyber768SharedSecret = KDF(&to_hash);

Ok((ciphertext, shared_secret))
if sampling_a_error.is_some() {
Err(sampling_a_error.unwrap())
} else {
Ok((ciphertext, shared_secret))
}
}

pub fn decapsulate(
Expand All @@ -97,28 +107,26 @@ pub fn decapsulate(
let hashed = G(&to_hash);
let (k_not, pseudorandomness) = hashed.split_at(32);

let expected_ciphertext_result =
// If a ciphertext C is well-formed, setting aside the fact that a
// decryption failure could (with negligible probability) occur, it must hold that:
//
// Encrypt(pk, Decrypt(sk, C)) = C
//
// Therefore, if |ind_cpa::encrypt| returns an error,
// |expected_ciphertext| cannot equal |ciphertext|, thereby resulting in
// implicit rejection.
//
// If C is ill-formed, due to the use of hashing to obtain |pseudorandomness|
// as well as the fact that the Kyber CPA-PKE is sparse pseudo-random, it is
// highly likely that |expected_ciphertext| will not equal |ciphertext|, thereby
// also resulting in implicit rejection.
//
// Thus, we ignore the second return value of |ind_cpa::encrypt|.
let (expected_ciphertext, _) =
ind_cpa::encrypt(ind_cpa_public_key, decrypted, pseudorandomness);

// Since we decrypt the ciphertext and hash this decrypted value in
// to obtain the pseudorandomness, it is in theory possible that a modified
// ciphertext could result in a set of pseudorandom bytes that are insufficient
// to rejection-sample the ring elements we need.
//
// In that case, the 'else' branch of this if-else block will be taken; notice
// that it performs less operations than the 'if' branch. The resulting timing
// difference would let an observer know that implicit rejection has taken
// place. We do not think this poses a security issue since such information
// would be conveyed anyway at a higher level (e.g. a key-exchange protocol
// would no longer proceed).
let to_hash = if let Ok(expected_ciphertext) = expected_ciphertext_result {
let selector = compare_ciphertexts_in_constant_time(ciphertext, &expected_ciphertext);
select_shared_secret_in_constant_time(k_not, implicit_rejection_value, selector)
} else {
let mut out = [0u8; 32];
out[..].copy_from_slice(implicit_rejection_value);
out
};
let selector = compare_ciphertexts_in_constant_time(ciphertext, &expected_ciphertext);
let to_hash = select_shared_secret_in_constant_time(k_not, implicit_rejection_value, selector);

let mut to_hash: [u8; SHARED_SECRET_SIZE + H_DIGEST_SIZE] = into_padded_array(&to_hash);
to_hash[SHARED_SECRET_SIZE..].copy_from_slice(&H(ciphertext));
Expand Down
38 changes: 26 additions & 12 deletions src/kem/kyber768/ind_cpa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,16 @@ impl KeyPair {
}

#[inline(always)]
fn parse_a(
#[allow(non_snake_case)]
fn sample_matrix_A(
mut seed: [u8; 34],
transpose: bool,
) -> Result<[[KyberPolynomialRingElement; RANK]; RANK], BadRejectionSamplingRandomnessError> {
let mut a_transpose = [[KyberPolynomialRingElement::ZERO; RANK]; RANK];
) -> (
[[KyberPolynomialRingElement; RANK]; RANK],
Option<BadRejectionSamplingRandomnessError>,
) {
let mut A_transpose = [[KyberPolynomialRingElement::ZERO; RANK]; RANK];
let mut sampling_A_error = None;

for i in 0..RANK {
for j in 0..RANK {
Expand All @@ -71,15 +76,21 @@ fn parse_a(

let xof_bytes: [u8; REJECTION_SAMPLING_SEED_SIZE] = XOF(&seed);

let (sampled, error) = sample_from_uniform_distribution(xof_bytes);
if error.is_some() {
sampling_A_error = error;
}

// A[i][j] = A_transpose[j][i]
if transpose {
a_transpose[j][i] = sample_from_uniform_distribution(xof_bytes)?;
A_transpose[j][i] = sampled;
} else {
a_transpose[i][j] = sample_from_uniform_distribution(xof_bytes)?;
A_transpose[i][j] = sampled;
}
}
}
Ok(a_transpose)

(A_transpose, sampling_A_error)
}

#[inline(always)]
Expand Down Expand Up @@ -113,7 +124,7 @@ fn encode_12(input: [KyberPolynomialRingElement; RANK]) -> [u8; RANK * BYTES_PER
#[allow(non_snake_case)]
pub(crate) fn generate_keypair(
key_generation_seed: &[u8],
) -> Result<KeyPair, BadRejectionSamplingRandomnessError> {
) -> (KeyPair, Option<BadRejectionSamplingRandomnessError>) {
let mut prf_input: [u8; 33] = [0; 33];

let mut secret_as_ntt = [KyberPolynomialRingElement::ZERO; RANK];
Expand All @@ -126,7 +137,7 @@ pub(crate) fn generate_keypair(
let hashed = G(key_generation_seed);
let (seed_for_A, seed_for_secret_and_error) = hashed.split_at(32);

let A_transpose = parse_a(into_padded_array(seed_for_A), true)?;
let (A_transpose, sampling_A_error) = sample_matrix_A(into_padded_array(seed_for_A), true);

// for i from 0 to k−1 do
// s[i] := CBD_{η1}(PRF(σ, N))
Expand Down Expand Up @@ -177,7 +188,10 @@ pub(crate) fn generate_keypair(
// sk := Encode_12(sˆ mod^{+}q)
let secret_key_serialized = encode_12(secret_as_ntt);

Ok(KeyPair::new(secret_key_serialized, public_key_serialized))
(
KeyPair::new(secret_key_serialized, public_key_serialized),
sampling_A_error,
)
}

fn compress_then_encode_u(
Expand All @@ -200,7 +214,7 @@ pub(crate) fn encrypt(
public_key: &[u8],
message: [u8; CPA_PKE_MESSAGE_SIZE],
randomness: &[u8],
) -> Result<CiphertextCpa, BadRejectionSamplingRandomnessError> {
) -> (CiphertextCpa, Option<BadRejectionSamplingRandomnessError>) {
// tˆ := Decode_12(pk)
let mut t_as_ntt = [KyberPolynomialRingElement::ZERO; RANK];
for (i, t_as_ntt_bytes) in public_key[..T_AS_NTT_ENCODED_SIZE]
Expand All @@ -217,7 +231,7 @@ pub(crate) fn encrypt(
// end for
// end for
let seed = &public_key[T_AS_NTT_ENCODED_SIZE..];
let A_transpose = parse_a(into_padded_array(seed), false)?;
let (A_transpose, sampling_A_error) = sample_matrix_A(into_padded_array(seed), false);

// for i from 0 to k−1 do
// r[i] := CBD{η1}(PRF(r, N))
Expand Down Expand Up @@ -268,7 +282,7 @@ pub(crate) fn encrypt(
let mut ciphertext: CiphertextCpa = into_padded_array(&c1);
ciphertext[VECTOR_U_ENCODED_SIZE..].copy_from_slice(c2.as_slice());

Ok(ciphertext)
(ciphertext, sampling_A_error)
}

#[allow(non_snake_case)]
Expand Down
10 changes: 6 additions & 4 deletions src/kem/kyber768/sampling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ use crate::kem::kyber768::{

pub fn sample_from_uniform_distribution(
randomness: [u8; REJECTION_SAMPLING_SEED_SIZE],
) -> Result<KyberPolynomialRingElement, BadRejectionSamplingRandomnessError> {
) -> (
KyberPolynomialRingElement,
Option<BadRejectionSamplingRandomnessError>,
) {
let mut sampled_coefficients: usize = 0;
let mut out: KyberPolynomialRingElement = KyberPolynomialRingElement::ZERO;

Expand All @@ -26,13 +29,12 @@ pub fn sample_from_uniform_distribution(
out[sampled_coefficients] = d2;
sampled_coefficients += 1;
}

if sampled_coefficients == COEFFICIENTS_IN_RING_ELEMENT {
return Ok(out);
return (out, None);
}
}

Err(BadRejectionSamplingRandomnessError)
(out, Some(BadRejectionSamplingRandomnessError))
}

/// Given a series of uniformly random bytes in `|randomness|`, sample
Expand Down

0 comments on commit 09a2604

Please sign in to comment.