Skip to content

Commit

Permalink
Merge pull request #3611 from Rohde-Schwarz/chore/pubkey_operators
Browse files Browse the repository at this point in the history
API modernization PK_KEM_Encryptor/Decryptor
  • Loading branch information
reneme authored Jul 17, 2023
2 parents e1a42ce + a9bdf73 commit 3f3b9e1
Show file tree
Hide file tree
Showing 16 changed files with 338 additions and 188 deletions.
46 changes: 45 additions & 1 deletion doc/api_ref/pubkey.rst
Original file line number Diff line number Diff line change
Expand Up @@ -826,13 +826,45 @@ encapulated key and returns the shared secret.

Create a KEM encryptor

.. cpp:function:: size_t shared_key_length(size_t desired_shared_key_len) const

Size in bytes of the shared key being produced by this PK_KEM_Encryptor.

.. cpp:function:: size_t encapsulated_key_length() const

Size in bytes of the encapsulated key being produced by this PK_KEM_Encryptor.

.. cpp:function:: KEM_Encapsulation encrypt(RandomNumberGenerator& rng,
size_t desired_shared_key_len = 32,
std::span<const uint8_t> salt = {})

Perform a key encapsulation operation with the result being returned
as a convenient struct.

.. cpp:function:: void encrypt(std::span<uint8_t> out_encapsulated_key,
std::span<uint8_t> out_shared_key,
RandomNumberGenerator& rng,
size_t desired_shared_key_len = 32,
std::span<const uint8_t> salt = {})

Perform a key encapsulation operation by passing in out-buffers of
the correct output length. Use encapsulated_key_length() and
shared_key_length() to pre-allocate the output buffers.

.. cpp:function:: void encrypt(secure_vector<uint8_t>& out_encapsulated_key, \
secure_vector<uint8_t>& out_shared_key, \
size_t desired_shared_key_len, \
RandomNumberGenerator& rng, \
std::span<const uint8_t> salt)

Perform a key encapsulation operation
Perform a key encapsulation operation by passing in out-vectors
that will be re-allocated to the correct output size.

.. cpp:class:: KEM_Encapsulation

.. cpp:function:: std::vector<uint8_t> encapsulated_shared_key() const

.. cpp:function:: secure_vector<uint8_t> shared_key() const

.. cpp:class:: PK_KEM_Decryptor

Expand All @@ -842,12 +874,24 @@ encapulated key and returns the shared secret.

Create a KEM decryptor

.. cpp:function:: size_t shared_key_length(size_t desired_shared_key_len) const

Size in bytes of the shared key being produced by this PK_KEM_Encryptor.

.. cpp:function:: secure_vector<uint8> decrypt(std::span<const uint8> encapsulated_key, \
size_t desired_shared_key_len, \
std::span<const uint8_t> salt)

Perform a key decapsulation operation

.. cpp:function:: void decrypt(std::span<uint8_t> out_shared_key,
std::span<const uint8_t> encap_key,
size_t desired_shared_key_len = 32,
std::span<const uint8_t> salt = {})

Perform a key decapsulation operation by passing in a pre-allocated
out-buffer. Use shared_key_length() to determine the byte-length required.

Botan implements the following KEM schemes:

1. RSA
Expand Down
7 changes: 3 additions & 4 deletions src/cli/speed.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1593,18 +1593,17 @@ class Speed final : public Command {
auto kem_dec_timer = make_timer(nm, provider, "KEM decrypt");

while(kem_enc_timer->under(msec) && kem_dec_timer->under(msec)) {
Botan::secure_vector<uint8_t> encap_key, enc_shared_key;
Botan::secure_vector<uint8_t> salt = rng().random_vec(16);

kem_enc_timer->start();
enc.encrypt(encap_key, enc_shared_key, 64, rng(), salt);
const auto kem_result = enc.encrypt(rng(), 64, salt);
kem_enc_timer->stop();

kem_dec_timer->start();
Botan::secure_vector<uint8_t> dec_shared_key = dec.decrypt(encap_key, 64, salt);
Botan::secure_vector<uint8_t> dec_shared_key = dec.decrypt(kem_result.encapsulated_shared_key(), 64, salt);
kem_dec_timer->stop();

if(enc_shared_key != dec_shared_key) {
if(kem_result.shared_key() != dec_shared_key) {
error_output() << "KEM mismatch in PK bench\n";
}
}
Expand Down
8 changes: 3 additions & 5 deletions src/examples/kyber.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@ int main() {

Botan::PK_KEM_Encryptor enc(*pub_key, kdf);

Botan::secure_vector<uint8_t> encapsulated_key;
Botan::secure_vector<uint8_t> enc_shared_key;
enc.encrypt(encapsulated_key, enc_shared_key, shared_key_len, rng, salt);
const auto kem_result = enc.encrypt(rng, shared_key_len, salt);

Botan::PK_KEM_Decryptor dec(priv_key, rng, kdf);

auto dec_shared_key = dec.decrypt(encapsulated_key, shared_key_len, salt);
auto dec_shared_key = dec.decrypt(kem_result.encapsulated_shared_key(), shared_key_len, salt);

if(dec_shared_key != enc_shared_key) {
if(dec_shared_key != kem_result.shared_key()) {
std::cerr << "Shared keys differ\n";
return 1;
}
Expand Down
9 changes: 3 additions & 6 deletions src/lib/ffi/ffi_pk_op.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -295,17 +295,14 @@ int botan_pk_op_kem_encrypt_create_shared_key(botan_pk_op_kem_encrypt_t op,
uint8_t encapsulated_key_out[],
size_t* encapsulated_key_len) {
return BOTAN_FFI_VISIT(op, [=](auto& kem) {
Botan::secure_vector<uint8_t> encapsulated_key;
Botan::secure_vector<uint8_t> shared_key;
const auto result = kem.encrypt(safe_get(rng), desired_shared_key_len, {salt, salt_len});

kem.encrypt(encapsulated_key, shared_key, desired_shared_key_len, safe_get(rng), salt, salt_len);

int rc = write_vec_output(encapsulated_key_out, encapsulated_key_len, encapsulated_key);
int rc = write_vec_output(encapsulated_key_out, encapsulated_key_len, result.encapsulated_shared_key());

if(rc != 0)
return rc;

return write_vec_output(shared_key_out, shared_key_len, shared_key);
return write_vec_output(shared_key_out, shared_key_len, result.shared_key());
});
}

Expand Down
15 changes: 15 additions & 0 deletions src/lib/kdf/kdf.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,21 @@ class BOTAN_PUBLIC_API(2, 0) KDF {
label.length());
}

/**
* Derive a key
* @param key the output buffer for the to-be-derived key
* @param secret the secret input
* @param salt a diversifier
* @param label purpose for the derived keying material
*/
void derive_key(std::span<uint8_t> key,
std::span<const uint8_t> secret,
std::span<const uint8_t> salt,
std::span<const uint8_t> label) const {
return kdf(
key.data(), key.size(), secret.data(), secret.size(), salt.data(), salt.size(), label.data(), label.size());
}

/**
* Derive a key
* @param key_len the desired output length in bytes
Expand Down
30 changes: 17 additions & 13 deletions src/lib/pubkey/kyber/kyber_common/kyber.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1092,8 +1092,8 @@ class Kyber_KEM_Encryptor final : public PK_Ops::KEM_Encryption_with_KDF,
}
}

void raw_kem_encrypt(secure_vector<uint8_t>& out_encapsulated_key,
secure_vector<uint8_t>& out_shared_key,
void raw_kem_encrypt(std::span<uint8_t> out_encapsulated_key,
std::span<uint8_t> out_shared_key,
RandomNumberGenerator& rng) override {
// naming from kyber spec
auto H = mode().H();
Expand All @@ -1113,11 +1113,15 @@ class Kyber_KEM_Encryptor final : public PK_Ops::KEM_Encryption_with_KDF,
const auto lower_g_out = std::span(g_out).subspan(0, 32);
const auto upper_g_out = std::span(g_out).subspan(32, 32);

out_encapsulated_key = indcpa_enc(shared_secret, upper_g_out);
const auto encapsulation = indcpa_enc(shared_secret, upper_g_out);

// TODO: avoid copy by letting Ciphertext write straight into std::span<>
BOTAN_ASSERT_NOMSG(encapsulation.size() == out_encapsulated_key.size());
std::copy(encapsulation.begin(), encapsulation.end(), out_encapsulated_key.begin());

KDF->update(lower_g_out.data(), lower_g_out.size());
KDF->update(H->process(out_encapsulated_key));
out_shared_key = KDF->final();
KDF->final(out_shared_key);
}

private:
Expand All @@ -1132,13 +1136,13 @@ class Kyber_KEM_Decryptor final : public PK_Ops::KEM_Decryption_with_KDF,

size_t raw_kem_shared_key_length() const override { return 32; }

secure_vector<uint8_t> raw_kem_decrypt(const uint8_t encap_key[], size_t len_encap_key) override {
void raw_kem_decrypt(std::span<uint8_t> out_shared_key, std::span<const uint8_t> encapsulated_key) override {
// naming from kyber spec
auto H = mode().H();
auto G = mode().G();
auto KDF = mode().KDF();

const auto shared_secret = indcpa_dec(encap_key, len_encap_key);
const auto shared_secret = indcpa_dec(encapsulated_key);

// Multitarget countermeasure for coins + contributory KEM
G->update(shared_secret);
Expand All @@ -1151,14 +1155,15 @@ class Kyber_KEM_Decryptor final : public PK_Ops::KEM_Decryption_with_KDF,
const auto lower_g_out = std::span(g_out).subspan(0, 32);
const auto upper_g_out = std::span(g_out).subspan(32, 32);

H->update(encap_key, len_encap_key);
H->update(encapsulated_key);

const auto cmp = indcpa_enc(shared_secret, upper_g_out);
BOTAN_ASSERT(len_encap_key == cmp.size(), "output of indcpa_enc has unexpected length");
BOTAN_ASSERT(encapsulated_key.size() == cmp.size(), "output of indcpa_enc has unexpected length");

// Overwrite pre-k with z on re-encryption failure (constant time)
secure_vector<uint8_t> lower_g_out_final(lower_g_out.size());
const uint8_t reencrypt_success = constant_time_compare(encap_key, cmp.data(), len_encap_key);
const uint8_t reencrypt_success =
constant_time_compare(encapsulated_key.data(), cmp.data(), encapsulated_key.size());
BOTAN_ASSERT_NOMSG(lower_g_out.size() == m_key.m_private->z().size());
CT::conditional_copy_mem(reencrypt_success,
lower_g_out_final.data(),
Expand All @@ -1168,13 +1173,12 @@ class Kyber_KEM_Decryptor final : public PK_Ops::KEM_Decryption_with_KDF,

KDF->update(lower_g_out_final);
KDF->update(H->final());

return KDF->final();
KDF->final(out_shared_key);
}

private:
secure_vector<uint8_t> indcpa_dec(const uint8_t c[], size_t c_len) {
auto ct = Ciphertext::from_bytes(std::span(c, c_len), mode());
secure_vector<uint8_t> indcpa_dec(std::span<const uint8_t> c) {
auto ct = Ciphertext::from_bytes(c, mode());
return ct.indcpa_decrypt(m_key.m_private->polynomials());
}

Expand Down
30 changes: 17 additions & 13 deletions src/lib/pubkey/mce/mceliece_key.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <botan/internal/mce_internal.h>
#include <botan/internal/pk_ops_impl.h>
#include <botan/internal/polyn_gf2m.h>
#include <botan/internal/stl_util.h>

namespace Botan {

Expand Down Expand Up @@ -289,19 +290,22 @@ class MCE_KEM_Encryptor final : public PK_Ops::KEM_Encryption_with_KDF {

size_t encapsulated_key_length() const override { return (m_key.get_code_length() + 7) / 8; }

void raw_kem_encrypt(secure_vector<uint8_t>& out_encapsulated_key,
secure_vector<uint8_t>& raw_shared_key,
void raw_kem_encrypt(std::span<uint8_t> out_encapsulated_key,
std::span<uint8_t> raw_shared_key,
RandomNumberGenerator& rng) override {
secure_vector<uint8_t> plaintext = m_key.random_plaintext_element(rng);

secure_vector<uint8_t> ciphertext, error_mask;
mceliece_encrypt(ciphertext, error_mask, plaintext, m_key, rng);

raw_shared_key.clear();
raw_shared_key += plaintext;
raw_shared_key += error_mask;
// TODO: Perhaps avoid the copies below
BOTAN_ASSERT_NOMSG(out_encapsulated_key.size() == ciphertext.size());
std::copy(ciphertext.begin(), ciphertext.end(), out_encapsulated_key.begin());

out_encapsulated_key.swap(ciphertext);
BOTAN_ASSERT_NOMSG(raw_shared_key.size() == plaintext.size() + error_mask.size());
BufferStuffer bs(raw_shared_key);
bs.append(plaintext);
bs.append(error_mask);
}

const McEliece_PublicKey& m_key;
Expand All @@ -319,15 +323,15 @@ class MCE_KEM_Decryptor final : public PK_Ops::KEM_Decryption_with_KDF {
return ptext_sz + err_sz;
}

secure_vector<uint8_t> raw_kem_decrypt(const uint8_t encap_key[], size_t len) override {
void raw_kem_decrypt(std::span<uint8_t> out_shared_key, std::span<const uint8_t> encapsulated_key) override {
secure_vector<uint8_t> plaintext, error_mask;
mceliece_decrypt(plaintext, error_mask, encap_key, len, m_key);
mceliece_decrypt(plaintext, error_mask, encapsulated_key.data(), encapsulated_key.size(), m_key);

secure_vector<uint8_t> output;
output.reserve(plaintext.size() + error_mask.size());
output.insert(output.end(), plaintext.begin(), plaintext.end());
output.insert(output.end(), error_mask.begin(), error_mask.end());
return output;
// TODO: perhaps avoid the copies below
BOTAN_ASSERT_NOMSG(out_shared_key.size() == plaintext.size() + error_mask.size());
BufferStuffer bs(out_shared_key);
bs.append(plaintext);
bs.append(error_mask);
}

const McEliece_PrivateKey& m_key;
Expand Down
64 changes: 30 additions & 34 deletions src/lib/pubkey/pk_ops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,30 +166,25 @@ size_t PK_Ops::KEM_Encryption_with_KDF::shared_key_length(size_t desired_shared_
}
}

void PK_Ops::KEM_Encryption_with_KDF::kem_encrypt(secure_vector<uint8_t>& out_encapsulated_key,
secure_vector<uint8_t>& out_shared_key,
size_t desired_shared_key_len,
void PK_Ops::KEM_Encryption_with_KDF::kem_encrypt(std::span<uint8_t> out_encapsulated_key,
std::span<uint8_t> out_shared_key,
RandomNumberGenerator& rng,
const uint8_t salt[],
size_t salt_len) {
if(salt_len > 0 && m_kdf == nullptr) {
throw Invalid_Argument("PK_KEM_Encryptor::encrypt requires a KDF to use a salt");
}

secure_vector<uint8_t> raw_shared;
this->raw_kem_encrypt(out_encapsulated_key, raw_shared, rng);

BOTAN_ASSERT_EQUAL(out_encapsulated_key.size(),
this->encapsulated_key_length(),
"KEM produced encapsulated key with different length than expected");
size_t desired_shared_key_len,
std::span<const uint8_t> salt) {
BOTAN_ARG_CHECK(salt.empty() || m_kdf, "PK_KEM_Encryptor::encrypt requires a KDF to use a salt");
BOTAN_ASSERT_NOMSG(out_encapsulated_key.size() == encapsulated_key_length());

BOTAN_ASSERT_EQUAL(raw_shared.size(),
this->raw_kem_shared_key_length(),
"KEM produced shared key with different length than expected");
if(m_kdf) {
BOTAN_ASSERT_EQUAL(
out_shared_key.size(), desired_shared_key_len, "KDF output length and shared key length match");

out_shared_key = (m_kdf)
? m_kdf->derive_key(desired_shared_key_len, raw_shared.data(), raw_shared.size(), salt, salt_len)
: raw_shared;
secure_vector<uint8_t> raw_shared(raw_kem_shared_key_length());
this->raw_kem_encrypt(out_encapsulated_key, raw_shared, rng);
m_kdf->derive_key(out_shared_key, raw_shared, salt, {});
} else {
BOTAN_ASSERT_EQUAL(out_shared_key.size(), raw_kem_shared_key_length(), "Shared key has raw KEM output length");
this->raw_kem_encrypt(out_encapsulated_key, out_shared_key, rng);
}
}

PK_Ops::KEM_Encryption_with_KDF::KEM_Encryption_with_KDF(std::string_view kdf) {
Expand All @@ -206,22 +201,23 @@ size_t PK_Ops::KEM_Decryption_with_KDF::shared_key_length(size_t desired_shared_
}
}

secure_vector<uint8_t> PK_Ops::KEM_Decryption_with_KDF::kem_decrypt(
const uint8_t encap_key[], size_t len, size_t desired_shared_key_len, const uint8_t salt[], size_t salt_len) {
if(salt_len > 0 && m_kdf == nullptr) {
throw Invalid_Argument("PK_KEM_Decryptor::decrypt requires a KDF to use a salt");
}

secure_vector<uint8_t> raw_shared = this->raw_kem_decrypt(encap_key, len);

BOTAN_ASSERT_EQUAL(raw_shared.size(),
this->raw_kem_shared_key_length(),
"KEM produced shared key with different length than expected");
void PK_Ops::KEM_Decryption_with_KDF::kem_decrypt(std::span<uint8_t> out_shared_key,
std::span<const uint8_t> encapsulated_key,
size_t desired_shared_key_len,
std::span<const uint8_t> salt) {
BOTAN_ARG_CHECK(salt.empty() || m_kdf, "PK_KEM_Decryptor::decrypt requires a KDF to use a salt");

if(m_kdf) {
return m_kdf->derive_key(desired_shared_key_len, raw_shared.data(), raw_shared.size(), salt, salt_len);
BOTAN_ASSERT_EQUAL(
out_shared_key.size(), desired_shared_key_len, "KDF output length and shared key length match");

secure_vector<uint8_t> raw_shared(raw_kem_shared_key_length());
this->raw_kem_decrypt(raw_shared, encapsulated_key);
m_kdf->derive_key(out_shared_key, raw_shared, salt, {});
} else {
BOTAN_ASSERT_EQUAL(out_shared_key.size(), raw_kem_shared_key_length(), "Shared key has raw KEM output length");
this->raw_kem_decrypt(out_shared_key, encapsulated_key);
}
return raw_shared;
}

PK_Ops::KEM_Decryption_with_KDF::KEM_Decryption_with_KDF(std::string_view kdf) {
Expand Down
Loading

0 comments on commit 3f3b9e1

Please sign in to comment.