boringssl/tool/speed.cc
David Benjamin 6443173d03 Add an option to configure bssl speed chunk size.
bsaes, in its current incarnation, hits various pathological behaviors
at different input sizes. Make it easy to experiment around them.

Bug: 256
Change-Id: Ib6c6ca7d06a570dbf7d4d2ea81c1db0d94d3d0c4
Reviewed-on: https://boringssl-review.googlesource.com/c/34876
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
2019-02-25 20:25:58 +00:00

950 lines
30 KiB
C++

/* Copyright (c) 2014, Google Inc.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
#include <algorithm>
#include <string>
#include <functional>
#include <memory>
#include <vector>
#include <assert.h>
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/aead.h>
#include <openssl/bn.h>
#include <openssl/curve25519.h>
#include <openssl/digest.h>
#include <openssl/err.h>
#include <openssl/ec.h>
#include <openssl/ecdsa.h>
#include <openssl/ec_key.h>
#include <openssl/evp.h>
#include <openssl/hrss.h>
#include <openssl/nid.h>
#include <openssl/rand.h>
#include <openssl/rsa.h>
#if defined(OPENSSL_WINDOWS)
OPENSSL_MSVC_PRAGMA(warning(push, 3))
#include <windows.h>
OPENSSL_MSVC_PRAGMA(warning(pop))
#elif defined(OPENSSL_APPLE)
#include <sys/time.h>
#else
#include <time.h>
#endif
#include "../crypto/internal.h"
#include "internal.h"
// TimeResults represents the results of benchmarking a function.
struct TimeResults {
// num_calls is the number of function calls done in the time period.
unsigned num_calls;
// us is the number of microseconds that elapsed in the time period.
unsigned us;
void Print(const std::string &description) {
printf("Did %u %s operations in %uus (%.1f ops/sec)\n", num_calls,
description.c_str(), us,
(static_cast<double>(num_calls) / us) * 1000000);
}
void PrintWithBytes(const std::string &description, size_t bytes_per_call) {
printf("Did %u %s operations in %uus (%.1f ops/sec): %.1f MB/s\n",
num_calls, description.c_str(), us,
(static_cast<double>(num_calls) / us) * 1000000,
static_cast<double>(bytes_per_call * num_calls) / us);
}
};
#if defined(OPENSSL_WINDOWS)
static uint64_t time_now() { return GetTickCount64() * 1000; }
#elif defined(OPENSSL_APPLE)
static uint64_t time_now() {
struct timeval tv;
uint64_t ret;
gettimeofday(&tv, NULL);
ret = tv.tv_sec;
ret *= 1000000;
ret += tv.tv_usec;
return ret;
}
#else
static uint64_t time_now() {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
uint64_t ret = ts.tv_sec;
ret *= 1000000;
ret += ts.tv_nsec / 1000;
return ret;
}
#endif
static uint64_t g_timeout_seconds = 1;
static std::vector<size_t> g_chunk_lengths = {16, 256, 1350, 8192};
static bool TimeFunction(TimeResults *results, std::function<bool()> func) {
// total_us is the total amount of time that we'll aim to measure a function
// for.
const uint64_t total_us = g_timeout_seconds * 1000000;
uint64_t start = time_now(), now, delta;
unsigned done = 0, iterations_between_time_checks;
if (!func()) {
return false;
}
now = time_now();
delta = now - start;
if (delta == 0) {
iterations_between_time_checks = 250;
} else {
// Aim for about 100ms between time checks.
iterations_between_time_checks =
static_cast<double>(100000) / static_cast<double>(delta);
if (iterations_between_time_checks > 1000) {
iterations_between_time_checks = 1000;
} else if (iterations_between_time_checks < 1) {
iterations_between_time_checks = 1;
}
}
for (;;) {
for (unsigned i = 0; i < iterations_between_time_checks; i++) {
if (!func()) {
return false;
}
done++;
}
now = time_now();
if (now - start > total_us) {
break;
}
}
results->us = now - start;
results->num_calls = done;
return true;
}
static bool SpeedRSA(const std::string &selected) {
if (!selected.empty() && selected.find("RSA") == std::string::npos) {
return true;
}
static const struct {
const char *name;
const uint8_t *key;
const size_t key_len;
} kRSAKeys[] = {
{"RSA 2048", kDERRSAPrivate2048, kDERRSAPrivate2048Len},
{"RSA 4096", kDERRSAPrivate4096, kDERRSAPrivate4096Len},
};
for (unsigned i = 0; i < OPENSSL_ARRAY_SIZE(kRSAKeys); i++) {
const std::string name = kRSAKeys[i].name;
bssl::UniquePtr<RSA> key(
RSA_private_key_from_bytes(kRSAKeys[i].key, kRSAKeys[i].key_len));
if (key == nullptr) {
fprintf(stderr, "Failed to parse %s key.\n", name.c_str());
ERR_print_errors_fp(stderr);
return false;
}
std::unique_ptr<uint8_t[]> sig(new uint8_t[RSA_size(key.get())]);
const uint8_t fake_sha256_hash[32] = {0};
unsigned sig_len;
TimeResults results;
if (!TimeFunction(&results,
[&key, &sig, &fake_sha256_hash, &sig_len]() -> bool {
// Usually during RSA signing we're using a long-lived |RSA| that has
// already had all of its |BN_MONT_CTX|s constructed, so it makes
// sense to use |key| directly here.
return RSA_sign(NID_sha256, fake_sha256_hash, sizeof(fake_sha256_hash),
sig.get(), &sig_len, key.get());
})) {
fprintf(stderr, "RSA_sign failed.\n");
ERR_print_errors_fp(stderr);
return false;
}
results.Print(name + " signing");
if (!TimeFunction(&results,
[&key, &fake_sha256_hash, &sig, sig_len]() -> bool {
return RSA_verify(
NID_sha256, fake_sha256_hash, sizeof(fake_sha256_hash),
sig.get(), sig_len, key.get());
})) {
fprintf(stderr, "RSA_verify failed.\n");
ERR_print_errors_fp(stderr);
return false;
}
results.Print(name + " verify (same key)");
if (!TimeFunction(&results,
[&key, &fake_sha256_hash, &sig, sig_len]() -> bool {
// Usually during RSA verification we have to parse an RSA key from a
// certificate or similar, in which case we'd need to construct a new
// RSA key, with a new |BN_MONT_CTX| for the public modulus. If we
// were to use |key| directly instead, then these costs wouldn't be
// accounted for.
bssl::UniquePtr<RSA> verify_key(RSA_new());
if (!verify_key) {
return false;
}
verify_key->n = BN_dup(key->n);
verify_key->e = BN_dup(key->e);
if (!verify_key->n ||
!verify_key->e) {
return false;
}
return RSA_verify(NID_sha256, fake_sha256_hash,
sizeof(fake_sha256_hash), sig.get(), sig_len,
verify_key.get());
})) {
fprintf(stderr, "RSA_verify failed.\n");
ERR_print_errors_fp(stderr);
return false;
}
results.Print(name + " verify (fresh key)");
}
return true;
}
static bool SpeedRSAKeyGen(const std::string &selected) {
// Don't run this by default because it's so slow.
if (selected != "RSAKeyGen") {
return true;
}
bssl::UniquePtr<BIGNUM> e(BN_new());
if (!BN_set_word(e.get(), 65537)) {
return false;
}
const std::vector<int> kSizes = {2048, 3072, 4096};
for (int size : kSizes) {
const uint64_t start = time_now();
unsigned num_calls = 0;
unsigned us;
std::vector<unsigned> durations;
for (;;) {
bssl::UniquePtr<RSA> rsa(RSA_new());
const uint64_t iteration_start = time_now();
if (!RSA_generate_key_ex(rsa.get(), size, e.get(), nullptr)) {
fprintf(stderr, "RSA_generate_key_ex failed.\n");
ERR_print_errors_fp(stderr);
return false;
}
const uint64_t iteration_end = time_now();
num_calls++;
durations.push_back(iteration_end - iteration_start);
us = iteration_end - start;
if (us > 30 * 1000000 /* 30 secs */) {
break;
}
}
std::sort(durations.begin(), durations.end());
printf("Did %u RSA %d key-gen operations in %uus (%.1f ops/sec)\n",
num_calls, size, us,
(static_cast<double>(num_calls) / us) * 1000000);
const size_t n = durations.size();
assert(n > 0);
// |min| and |max| must be stored in temporary variables to avoid an MSVC
// bug on x86. There, size_t is a typedef for unsigned, but MSVC's printf
// warning tries to retain the distinction and suggest %zu for size_t
// instead of %u. It gets confused if std::vector<unsigned> and
// std::vector<size_t> are both instantiated. Being typedefs, the two
// instantiations are identical, which somehow breaks the size_t vs unsigned
// metadata.
unsigned min = durations[0];
unsigned median = n & 1 ? durations[n / 2]
: (durations[n / 2 - 1] + durations[n / 2]) / 2;
unsigned max = durations[n - 1];
printf(" min: %uus, median: %uus, max: %uus\n", min, median, max);
}
return true;
}
static uint8_t *align(uint8_t *in, unsigned alignment) {
return reinterpret_cast<uint8_t *>(
(reinterpret_cast<uintptr_t>(in) + alignment) &
~static_cast<size_t>(alignment - 1));
}
static std::string ChunkLenSuffix(size_t chunk_len) {
char buf[32];
snprintf(buf, sizeof(buf), " (%zu byte%s)", chunk_len,
chunk_len != 1 ? "s" : "");
return buf;
}
static bool SpeedAEADChunk(const EVP_AEAD *aead, std::string name,
size_t chunk_len, size_t ad_len,
evp_aead_direction_t direction) {
static const unsigned kAlignment = 16;
name += ChunkLenSuffix(chunk_len);
bssl::ScopedEVP_AEAD_CTX ctx;
const size_t key_len = EVP_AEAD_key_length(aead);
const size_t nonce_len = EVP_AEAD_nonce_length(aead);
const size_t overhead_len = EVP_AEAD_max_overhead(aead);
std::unique_ptr<uint8_t[]> key(new uint8_t[key_len]);
OPENSSL_memset(key.get(), 0, key_len);
std::unique_ptr<uint8_t[]> nonce(new uint8_t[nonce_len]);
OPENSSL_memset(nonce.get(), 0, nonce_len);
std::unique_ptr<uint8_t[]> in_storage(new uint8_t[chunk_len + kAlignment]);
// N.B. for EVP_AEAD_CTX_seal_scatter the input and output buffers may be the
// same size. However, in the direction == evp_aead_open case we still use
// non-scattering seal, hence we add overhead_len to the size of this buffer.
std::unique_ptr<uint8_t[]> out_storage(
new uint8_t[chunk_len + overhead_len + kAlignment]);
std::unique_ptr<uint8_t[]> in2_storage(
new uint8_t[chunk_len + overhead_len + kAlignment]);
std::unique_ptr<uint8_t[]> ad(new uint8_t[ad_len]);
OPENSSL_memset(ad.get(), 0, ad_len);
std::unique_ptr<uint8_t[]> tag_storage(
new uint8_t[overhead_len + kAlignment]);
uint8_t *const in = align(in_storage.get(), kAlignment);
OPENSSL_memset(in, 0, chunk_len);
uint8_t *const out = align(out_storage.get(), kAlignment);
OPENSSL_memset(out, 0, chunk_len + overhead_len);
uint8_t *const tag = align(tag_storage.get(), kAlignment);
OPENSSL_memset(tag, 0, overhead_len);
uint8_t *const in2 = align(in2_storage.get(), kAlignment);
if (!EVP_AEAD_CTX_init_with_direction(ctx.get(), aead, key.get(), key_len,
EVP_AEAD_DEFAULT_TAG_LENGTH,
evp_aead_seal)) {
fprintf(stderr, "Failed to create EVP_AEAD_CTX.\n");
ERR_print_errors_fp(stderr);
return false;
}
TimeResults results;
if (direction == evp_aead_seal) {
if (!TimeFunction(&results,
[chunk_len, nonce_len, ad_len, overhead_len, in, out, tag,
&ctx, &nonce, &ad]() -> bool {
size_t tag_len;
return EVP_AEAD_CTX_seal_scatter(
ctx.get(), out, tag, &tag_len, overhead_len,
nonce.get(), nonce_len, in, chunk_len, nullptr, 0,
ad.get(), ad_len);
})) {
fprintf(stderr, "EVP_AEAD_CTX_seal failed.\n");
ERR_print_errors_fp(stderr);
return false;
}
} else {
size_t out_len;
EVP_AEAD_CTX_seal(ctx.get(), out, &out_len, chunk_len + overhead_len,
nonce.get(), nonce_len, in, chunk_len, ad.get(), ad_len);
ctx.Reset();
if (!EVP_AEAD_CTX_init_with_direction(ctx.get(), aead, key.get(), key_len,
EVP_AEAD_DEFAULT_TAG_LENGTH,
evp_aead_open)) {
fprintf(stderr, "Failed to create EVP_AEAD_CTX.\n");
ERR_print_errors_fp(stderr);
return false;
}
if (!TimeFunction(&results,
[chunk_len, overhead_len, nonce_len, ad_len, in2, out,
out_len, &ctx, &nonce, &ad]() -> bool {
size_t in2_len;
// N.B. EVP_AEAD_CTX_open_gather is not implemented for
// all AEADs.
return EVP_AEAD_CTX_open(ctx.get(), in2, &in2_len,
chunk_len + overhead_len,
nonce.get(), nonce_len, out,
out_len, ad.get(), ad_len);
})) {
fprintf(stderr, "EVP_AEAD_CTX_open failed.\n");
ERR_print_errors_fp(stderr);
return false;
}
}
results.PrintWithBytes(
name + (direction == evp_aead_seal ? " seal" : " open"), chunk_len);
return true;
}
static bool SpeedAEAD(const EVP_AEAD *aead, const std::string &name,
size_t ad_len, const std::string &selected) {
if (!selected.empty() && name.find(selected) == std::string::npos) {
return true;
}
for (size_t chunk_len : g_chunk_lengths) {
if (!SpeedAEADChunk(aead, name, chunk_len, ad_len, evp_aead_seal)) {
return false;
}
}
return true;
}
static bool SpeedAEADOpen(const EVP_AEAD *aead, const std::string &name,
size_t ad_len, const std::string &selected) {
if (!selected.empty() && name.find(selected) == std::string::npos) {
return true;
}
for (size_t chunk_len : g_chunk_lengths) {
if (!SpeedAEADChunk(aead, name, chunk_len, ad_len, evp_aead_open)) {
return false;
}
}
return true;
}
static bool SpeedHashChunk(const EVP_MD *md, std::string name,
size_t chunk_len) {
bssl::ScopedEVP_MD_CTX ctx;
uint8_t scratch[8192];
if (chunk_len > sizeof(scratch)) {
return false;
}
name += ChunkLenSuffix(chunk_len);
TimeResults results;
if (!TimeFunction(&results, [&ctx, md, chunk_len, &scratch]() -> bool {
uint8_t digest[EVP_MAX_MD_SIZE];
unsigned int md_len;
return EVP_DigestInit_ex(ctx.get(), md, NULL /* ENGINE */) &&
EVP_DigestUpdate(ctx.get(), scratch, chunk_len) &&
EVP_DigestFinal_ex(ctx.get(), digest, &md_len);
})) {
fprintf(stderr, "EVP_DigestInit_ex failed.\n");
ERR_print_errors_fp(stderr);
return false;
}
results.PrintWithBytes(name, chunk_len);
return true;
}
static bool SpeedHash(const EVP_MD *md, const std::string &name,
const std::string &selected) {
if (!selected.empty() && name.find(selected) == std::string::npos) {
return true;
}
for (size_t chunk_len : g_chunk_lengths) {
if (!SpeedHashChunk(md, name, chunk_len)) {
return false;
}
}
return true;
}
static bool SpeedRandomChunk(std::string name, size_t chunk_len) {
uint8_t scratch[8192];
if (chunk_len > sizeof(scratch)) {
return false;
}
name += ChunkLenSuffix(chunk_len);
TimeResults results;
if (!TimeFunction(&results, [chunk_len, &scratch]() -> bool {
RAND_bytes(scratch, chunk_len);
return true;
})) {
return false;
}
results.PrintWithBytes(name, chunk_len);
return true;
}
static bool SpeedRandom(const std::string &selected) {
if (!selected.empty() && selected != "RNG") {
return true;
}
for (size_t chunk_len : g_chunk_lengths) {
if (!SpeedRandomChunk("RNG", chunk_len)) {
return false;
}
}
return true;
}
static bool SpeedECDHCurve(const std::string &name, int nid,
const std::string &selected) {
if (!selected.empty() && name.find(selected) == std::string::npos) {
return true;
}
bssl::UniquePtr<EC_KEY> peer_key(EC_KEY_new_by_curve_name(nid));
if (!peer_key ||
!EC_KEY_generate_key(peer_key.get())) {
return false;
}
size_t peer_value_len = EC_POINT_point2oct(
EC_KEY_get0_group(peer_key.get()), EC_KEY_get0_public_key(peer_key.get()),
POINT_CONVERSION_UNCOMPRESSED, nullptr, 0, nullptr);
if (peer_value_len == 0) {
return false;
}
std::unique_ptr<uint8_t[]> peer_value(new uint8_t[peer_value_len]);
peer_value_len = EC_POINT_point2oct(
EC_KEY_get0_group(peer_key.get()), EC_KEY_get0_public_key(peer_key.get()),
POINT_CONVERSION_UNCOMPRESSED, peer_value.get(), peer_value_len, nullptr);
if (peer_value_len == 0) {
return false;
}
TimeResults results;
if (!TimeFunction(&results, [nid, peer_value_len, &peer_value]() -> bool {
bssl::UniquePtr<EC_KEY> key(EC_KEY_new_by_curve_name(nid));
if (!key ||
!EC_KEY_generate_key(key.get())) {
return false;
}
const EC_GROUP *const group = EC_KEY_get0_group(key.get());
bssl::UniquePtr<EC_POINT> point(EC_POINT_new(group));
bssl::UniquePtr<EC_POINT> peer_point(EC_POINT_new(group));
bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new());
bssl::UniquePtr<BIGNUM> x(BN_new());
bssl::UniquePtr<BIGNUM> y(BN_new());
if (!point || !peer_point || !ctx || !x || !y ||
!EC_POINT_oct2point(group, peer_point.get(), peer_value.get(),
peer_value_len, ctx.get()) ||
!EC_POINT_mul(group, point.get(), NULL, peer_point.get(),
EC_KEY_get0_private_key(key.get()), ctx.get()) ||
!EC_POINT_get_affine_coordinates_GFp(group, point.get(), x.get(),
y.get(), ctx.get())) {
return false;
}
return true;
})) {
return false;
}
results.Print(name);
return true;
}
static bool SpeedECDSACurve(const std::string &name, int nid,
const std::string &selected) {
if (!selected.empty() && name.find(selected) == std::string::npos) {
return true;
}
bssl::UniquePtr<EC_KEY> key(EC_KEY_new_by_curve_name(nid));
if (!key ||
!EC_KEY_generate_key(key.get())) {
return false;
}
uint8_t signature[256];
if (ECDSA_size(key.get()) > sizeof(signature)) {
return false;
}
uint8_t digest[20];
OPENSSL_memset(digest, 42, sizeof(digest));
unsigned sig_len;
TimeResults results;
if (!TimeFunction(&results, [&key, &signature, &digest, &sig_len]() -> bool {
return ECDSA_sign(0, digest, sizeof(digest), signature, &sig_len,
key.get()) == 1;
})) {
return false;
}
results.Print(name + " signing");
if (!TimeFunction(&results, [&key, &signature, &digest, sig_len]() -> bool {
return ECDSA_verify(0, digest, sizeof(digest), signature, sig_len,
key.get()) == 1;
})) {
return false;
}
results.Print(name + " verify");
return true;
}
static bool SpeedECDH(const std::string &selected) {
return SpeedECDHCurve("ECDH P-224", NID_secp224r1, selected) &&
SpeedECDHCurve("ECDH P-256", NID_X9_62_prime256v1, selected) &&
SpeedECDHCurve("ECDH P-384", NID_secp384r1, selected) &&
SpeedECDHCurve("ECDH P-521", NID_secp521r1, selected);
}
static bool SpeedECDSA(const std::string &selected) {
return SpeedECDSACurve("ECDSA P-224", NID_secp224r1, selected) &&
SpeedECDSACurve("ECDSA P-256", NID_X9_62_prime256v1, selected) &&
SpeedECDSACurve("ECDSA P-384", NID_secp384r1, selected) &&
SpeedECDSACurve("ECDSA P-521", NID_secp521r1, selected);
}
static bool Speed25519(const std::string &selected) {
if (!selected.empty() && selected.find("25519") == std::string::npos) {
return true;
}
TimeResults results;
uint8_t public_key[32], private_key[64];
if (!TimeFunction(&results, [&public_key, &private_key]() -> bool {
ED25519_keypair(public_key, private_key);
return true;
})) {
return false;
}
results.Print("Ed25519 key generation");
static const uint8_t kMessage[] = {0, 1, 2, 3, 4, 5};
uint8_t signature[64];
if (!TimeFunction(&results, [&private_key, &signature]() -> bool {
return ED25519_sign(signature, kMessage, sizeof(kMessage),
private_key) == 1;
})) {
return false;
}
results.Print("Ed25519 signing");
if (!TimeFunction(&results, [&public_key, &signature]() -> bool {
return ED25519_verify(kMessage, sizeof(kMessage), signature,
public_key) == 1;
})) {
fprintf(stderr, "Ed25519 verify failed.\n");
return false;
}
results.Print("Ed25519 verify");
if (!TimeFunction(&results, []() -> bool {
uint8_t out[32], in[32];
OPENSSL_memset(in, 0, sizeof(in));
X25519_public_from_private(out, in);
return true;
})) {
fprintf(stderr, "Curve25519 base-point multiplication failed.\n");
return false;
}
results.Print("Curve25519 base-point multiplication");
if (!TimeFunction(&results, []() -> bool {
uint8_t out[32], in1[32], in2[32];
OPENSSL_memset(in1, 0, sizeof(in1));
OPENSSL_memset(in2, 0, sizeof(in2));
in1[0] = 1;
in2[0] = 9;
return X25519(out, in1, in2) == 1;
})) {
fprintf(stderr, "Curve25519 arbitrary point multiplication failed.\n");
return false;
}
results.Print("Curve25519 arbitrary point multiplication");
return true;
}
static bool SpeedSPAKE2(const std::string &selected) {
if (!selected.empty() && selected.find("SPAKE2") == std::string::npos) {
return true;
}
TimeResults results;
static const uint8_t kAliceName[] = {'A'};
static const uint8_t kBobName[] = {'B'};
static const uint8_t kPassword[] = "password";
bssl::UniquePtr<SPAKE2_CTX> alice(SPAKE2_CTX_new(spake2_role_alice,
kAliceName, sizeof(kAliceName), kBobName,
sizeof(kBobName)));
uint8_t alice_msg[SPAKE2_MAX_MSG_SIZE];
size_t alice_msg_len;
if (!SPAKE2_generate_msg(alice.get(), alice_msg, &alice_msg_len,
sizeof(alice_msg),
kPassword, sizeof(kPassword))) {
fprintf(stderr, "SPAKE2_generate_msg failed.\n");
return false;
}
if (!TimeFunction(&results, [&alice_msg, alice_msg_len]() -> bool {
bssl::UniquePtr<SPAKE2_CTX> bob(SPAKE2_CTX_new(spake2_role_bob,
kBobName, sizeof(kBobName), kAliceName,
sizeof(kAliceName)));
uint8_t bob_msg[SPAKE2_MAX_MSG_SIZE], bob_key[64];
size_t bob_msg_len, bob_key_len;
if (!SPAKE2_generate_msg(bob.get(), bob_msg, &bob_msg_len,
sizeof(bob_msg), kPassword,
sizeof(kPassword)) ||
!SPAKE2_process_msg(bob.get(), bob_key, &bob_key_len,
sizeof(bob_key), alice_msg, alice_msg_len)) {
return false;
}
return true;
})) {
fprintf(stderr, "SPAKE2 failed.\n");
}
results.Print("SPAKE2 over Ed25519");
return true;
}
static bool SpeedScrypt(const std::string &selected) {
if (!selected.empty() && selected.find("scrypt") == std::string::npos) {
return true;
}
TimeResults results;
static const char kPassword[] = "password";
static const uint8_t kSalt[] = "NaCl";
if (!TimeFunction(&results, [&]() -> bool {
uint8_t out[64];
return !!EVP_PBE_scrypt(kPassword, sizeof(kPassword) - 1, kSalt,
sizeof(kSalt) - 1, 1024, 8, 16, 0 /* max_mem */,
out, sizeof(out));
})) {
fprintf(stderr, "scrypt failed.\n");
return false;
}
results.Print("scrypt (N = 1024, r = 8, p = 16)");
if (!TimeFunction(&results, [&]() -> bool {
uint8_t out[64];
return !!EVP_PBE_scrypt(kPassword, sizeof(kPassword) - 1, kSalt,
sizeof(kSalt) - 1, 16384, 8, 1, 0 /* max_mem */,
out, sizeof(out));
})) {
fprintf(stderr, "scrypt failed.\n");
return false;
}
results.Print("scrypt (N = 16384, r = 8, p = 1)");
return true;
}
static bool SpeedHRSS(const std::string &selected) {
if (!selected.empty() && selected != "HRSS") {
return true;
}
TimeResults results;
if (!TimeFunction(&results, []() -> bool {
struct HRSS_public_key pub;
struct HRSS_private_key priv;
uint8_t entropy[HRSS_GENERATE_KEY_BYTES];
RAND_bytes(entropy, sizeof(entropy));
HRSS_generate_key(&pub, &priv, entropy);
return true;
})) {
fprintf(stderr, "Failed to time HRSS_generate_key.\n");
return false;
}
results.Print("HRSS generate");
struct HRSS_public_key pub;
struct HRSS_private_key priv;
uint8_t key_entropy[HRSS_GENERATE_KEY_BYTES];
RAND_bytes(key_entropy, sizeof(key_entropy));
HRSS_generate_key(&pub, &priv, key_entropy);
uint8_t ciphertext[HRSS_CIPHERTEXT_BYTES];
if (!TimeFunction(&results, [&pub, &ciphertext]() -> bool {
uint8_t entropy[HRSS_ENCAP_BYTES];
uint8_t shared_key[HRSS_KEY_BYTES];
RAND_bytes(entropy, sizeof(entropy));
HRSS_encap(ciphertext, shared_key, &pub, entropy);
return true;
})) {
fprintf(stderr, "Failed to time HRSS_encap.\n");
return false;
}
results.Print("HRSS encap");
if (!TimeFunction(&results, [&priv, &ciphertext]() -> bool {
uint8_t shared_key[HRSS_KEY_BYTES];
HRSS_decap(shared_key, &priv, ciphertext, sizeof(ciphertext));
return true;
})) {
fprintf(stderr, "Failed to time HRSS_encap.\n");
return false;
}
results.Print("HRSS decap");
return true;
}
static const struct argument kArguments[] = {
{
"-filter",
kOptionalArgument,
"A filter on the speed tests to run",
},
{
"-timeout",
kOptionalArgument,
"The number of seconds to run each test for (default is 1)",
},
{
"-chunks",
kOptionalArgument,
"A comma-separated list of input sizes to run tests at (default is "
"16,256,1350,8192)",
},
{
"",
kOptionalArgument,
"",
},
};
bool Speed(const std::vector<std::string> &args) {
std::map<std::string, std::string> args_map;
if (!ParseKeyValueArguments(&args_map, args, kArguments)) {
PrintUsage(kArguments);
return false;
}
std::string selected;
if (args_map.count("-filter") != 0) {
selected = args_map["-filter"];
}
if (args_map.count("-timeout") != 0) {
g_timeout_seconds = atoi(args_map["-timeout"].c_str());
}
if (args_map.count("-chunks") != 0) {
g_chunk_lengths.clear();
const char *start = args_map["-chunks"].data();
const char *end = start + args_map["-chunks"].size();
while (start != end) {
errno = 0;
char *ptr;
unsigned long long val = strtoull(start, &ptr, 10);
if (ptr == start /* no numeric characters found */ ||
errno == ERANGE /* overflow */ ||
static_cast<size_t>(val) != val) {
fprintf(stderr, "Error parsing -chunks argument\n");
return false;
}
g_chunk_lengths.push_back(static_cast<size_t>(val));
start = ptr;
if (start != end) {
if (*start != ',') {
fprintf(stderr, "Error parsing -chunks argument\n");
return false;
}
start++;
}
}
}
// kTLSADLen is the number of bytes of additional data that TLS passes to
// AEADs.
static const size_t kTLSADLen = 13;
// kLegacyADLen is the number of bytes that TLS passes to the "legacy" AEADs.
// These are AEADs that weren't originally defined as AEADs, but which we use
// via the AEAD interface. In order for that to work, they have some TLS
// knowledge in them and construct a couple of the AD bytes internally.
static const size_t kLegacyADLen = kTLSADLen - 2;
if (!SpeedRSA(selected) ||
!SpeedAEAD(EVP_aead_aes_128_gcm(), "AES-128-GCM", kTLSADLen, selected) ||
!SpeedAEAD(EVP_aead_aes_256_gcm(), "AES-256-GCM", kTLSADLen, selected) ||
!SpeedAEAD(EVP_aead_chacha20_poly1305(), "ChaCha20-Poly1305", kTLSADLen,
selected) ||
!SpeedAEAD(EVP_aead_des_ede3_cbc_sha1_tls(), "DES-EDE3-CBC-SHA1",
kLegacyADLen, selected) ||
!SpeedAEAD(EVP_aead_aes_128_cbc_sha1_tls(), "AES-128-CBC-SHA1",
kLegacyADLen, selected) ||
!SpeedAEAD(EVP_aead_aes_256_cbc_sha1_tls(), "AES-256-CBC-SHA1",
kLegacyADLen, selected) ||
!SpeedAEADOpen(EVP_aead_aes_128_cbc_sha1_tls(), "AES-128-CBC-SHA1",
kLegacyADLen, selected) ||
!SpeedAEADOpen(EVP_aead_aes_256_cbc_sha1_tls(), "AES-256-CBC-SHA1",
kLegacyADLen, selected) ||
!SpeedAEAD(EVP_aead_aes_128_gcm_siv(), "AES-128-GCM-SIV", kTLSADLen,
selected) ||
!SpeedAEAD(EVP_aead_aes_256_gcm_siv(), "AES-256-GCM-SIV", kTLSADLen,
selected) ||
!SpeedAEADOpen(EVP_aead_aes_128_gcm_siv(), "AES-128-GCM-SIV", kTLSADLen,
selected) ||
!SpeedAEADOpen(EVP_aead_aes_256_gcm_siv(), "AES-256-GCM-SIV", kTLSADLen,
selected) ||
!SpeedAEAD(EVP_aead_aes_128_ccm_bluetooth(), "AES-128-CCM-Bluetooth",
kTLSADLen, selected) ||
!SpeedHash(EVP_sha1(), "SHA-1", selected) ||
!SpeedHash(EVP_sha256(), "SHA-256", selected) ||
!SpeedHash(EVP_sha512(), "SHA-512", selected) ||
!SpeedRandom(selected) ||
!SpeedECDH(selected) ||
!SpeedECDSA(selected) ||
!Speed25519(selected) ||
!SpeedSPAKE2(selected) ||
!SpeedScrypt(selected) ||
!SpeedRSAKeyGen(selected) ||
!SpeedHRSS(selected)) {
return false;
}
return true;
}