diff options
Diffstat (limited to 'comm/third_party/botan/src/cli/speed.cpp')
-rw-r--r-- | comm/third_party/botan/src/cli/speed.cpp | 2342 |
1 files changed, 2342 insertions, 0 deletions
diff --git a/comm/third_party/botan/src/cli/speed.cpp b/comm/third_party/botan/src/cli/speed.cpp new file mode 100644 index 0000000000..b8454d2a7a --- /dev/null +++ b/comm/third_party/botan/src/cli/speed.cpp @@ -0,0 +1,2342 @@ +/* +* (C) 2009,2010,2014,2015,2017,2018 Jack Lloyd +* (C) 2015 Simon Warta (Kullo GmbH) +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" +#include "../tests/test_rng.h" // FIXME + +#include <sstream> +#include <iomanip> +#include <chrono> +#include <functional> +#include <algorithm> +#include <map> +#include <set> + +// Always available: +#include <botan/entropy_src.h> +#include <botan/parsing.h> +#include <botan/cpuid.h> +#include <botan/internal/os_utils.h> +#include <botan/internal/timer.h> +#include <botan/version.h> + +#if defined(BOTAN_HAS_BIGINT) + #include <botan/bigint.h> + #include <botan/divide.h> +#endif + +#if defined(BOTAN_HAS_BLOCK_CIPHER) + #include <botan/block_cipher.h> +#endif + +#if defined(BOTAN_HAS_STREAM_CIPHER) + #include <botan/stream_cipher.h> +#endif + +#if defined(BOTAN_HAS_HASH) + #include <botan/hash.h> +#endif + +#if defined(BOTAN_HAS_CIPHER_MODES) + #include <botan/cipher_mode.h> +#endif + +#if defined(BOTAN_HAS_MAC) + #include <botan/mac.h> +#endif + +#if defined(BOTAN_HAS_AUTO_SEEDING_RNG) + #include <botan/auto_rng.h> +#endif + +#if defined(BOTAN_HAS_SYSTEM_RNG) + #include <botan/system_rng.h> +#endif + +#if defined(BOTAN_HAS_HMAC_DRBG) + #include <botan/hmac_drbg.h> +#endif + +#if defined(BOTAN_HAS_PROCESSOR_RNG) + #include <botan/processor_rng.h> +#endif + +#if defined(BOTAN_HAS_CHACHA_RNG) + #include <botan/chacha_rng.h> +#endif + +#if defined(BOTAN_HAS_FPE_FE1) + #include <botan/fpe_fe1.h> +#endif + +#if defined(BOTAN_HAS_RFC3394_KEYWRAP) + #include <botan/rfc3394.h> +#endif + +#if defined(BOTAN_HAS_COMPRESSION) + #include <botan/compression.h> +#endif + +#if defined(BOTAN_HAS_POLY_DBL) + #include <botan/internal/poly_dbl.h> +#endif + +#if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) + #include <botan/pkcs8.h> + #include <botan/pubkey.h> + #include <botan/pk_algs.h> + #include <botan/x509_key.h> + #include <botan/workfactor.h> +#endif + +#if defined(BOTAN_HAS_NUMBERTHEORY) + #include <botan/numthry.h> + #include <botan/reducer.h> + #include <botan/curve_nistp.h> + #include <botan/internal/primality.h> +#endif + +#if defined(BOTAN_HAS_ECC_GROUP) + #include <botan/ec_group.h> +#endif + +#if defined(BOTAN_HAS_DL_GROUP) + #include <botan/dl_group.h> +#endif + +#if defined(BOTAN_HAS_MCELIECE) + #include <botan/mceliece.h> +#endif + +#if defined(BOTAN_HAS_ECDSA) + #include <botan/ecdsa.h> +#endif + +#if defined(BOTAN_HAS_NEWHOPE) + #include <botan/newhope.h> +#endif + +#if defined(BOTAN_HAS_SCRYPT) + #include <botan/scrypt.h> +#endif + +#if defined(BOTAN_HAS_ARGON2) + #include <botan/argon2.h> +#endif + +#if defined(BOTAN_HAS_BCRYPT) + #include <botan/bcrypt.h> +#endif + +#if defined(BOTAN_HAS_PASSHASH9) + #include <botan/passhash9.h> +#endif + +namespace Botan_CLI { + +using Botan::Timer; + +namespace { + +class JSON_Output final + { + public: + void add(const Timer& timer) { m_results.push_back(timer); } + + std::string print() const + { + std::ostringstream out; + + out << "[\n"; + + for(size_t i = 0; i != m_results.size(); ++i) + { + if(i != 0) + out << ","; + + const Timer& t = m_results[i]; + + out << '{'; + out << "\"algo\": \"" << t.get_name() << "\", "; + out << "\"op\": \"" << t.doing() << "\", "; + + out << "\"events\": " << t.events() << ", "; + if(t.cycles_consumed() > 0) + out << "\"cycles\": " << t.cycles_consumed() << ", "; + if(t.buf_size() > 0) + { + out << "\"bps\": " << static_cast<uint64_t>(t.events() / (t.value() / 1000000000.0)) << ", "; + out << "\"buf_size\": " << t.buf_size() << ", "; + } + + out << "\"nanos\": " << t.value(); + + out << "}\n"; + } + out << "]\n"; + + return out.str(); + } + private: + std::vector<Timer> m_results; + }; + +class Summary final + { + public: + Summary() {} + + void add(const Timer& t) + { + if(t.buf_size() == 0) + { + m_ops_entries.push_back(t); + } + else + { + m_bps_entries[std::make_pair(t.doing(), t.get_name())].push_back(t); + } + } + + std::string print() + { + const size_t name_padding = 35; + const size_t op_name_padding = 16; + const size_t op_padding = 16; + + std::ostringstream result_ss; + result_ss << std::fixed; + + if(m_bps_entries.size() > 0) + { + result_ss << "\n"; + + // add table header + result_ss << std::setw(name_padding) << std::left << "algo" + << std::setw(op_name_padding) << std::left << "operation"; + + for(const Timer& t : m_bps_entries.begin()->second) + { + result_ss << std::setw(op_padding) << std::right << (std::to_string(t.buf_size()) + " bytes"); + } + result_ss << "\n"; + + // add table entries + for(const auto& entry : m_bps_entries) + { + if(entry.second.empty()) + continue; + + result_ss << std::setw(name_padding) << std::left << (entry.first.second) + << std::setw(op_name_padding) << std::left << (entry.first.first); + + for(const Timer& t : entry.second) + { + + if(t.events() == 0) + { + result_ss << std::setw(op_padding) << std::right << "N/A"; + } + else + { + result_ss << std::setw(op_padding) << std::right + << std::setprecision(2) << (t.bytes_per_second() / 1000.0); + } + } + + result_ss << "\n"; + } + + result_ss << "\n[results are the number of 1000s bytes processed per second]\n"; + } + + if(m_ops_entries.size() > 0) + { + result_ss << std::setprecision(6) << "\n"; + + // sort entries + std::sort(m_ops_entries.begin(), m_ops_entries.end()); + + // add table header + result_ss << std::setw(name_padding) << std::left << "algo" + << std::setw(op_name_padding) << std::left << "operation" + << std::setw(op_padding) << std::right << "sec/op" + << std::setw(op_padding) << std::right << "op/sec" + << "\n"; + + // add table entries + for(const Timer& entry : m_ops_entries) + { + result_ss << std::setw(name_padding) << std::left << entry.get_name() + << std::setw(op_name_padding) << std::left << entry.doing() + << std::setw(op_padding) << std::right << entry.seconds_per_event() + << std::setw(op_padding) << std::right << entry.events_per_second() + << "\n"; + } + } + + return result_ss.str(); + } + + private: + std::map<std::pair<std::string, std::string>, std::vector<Timer>> m_bps_entries; + std::vector<Timer> m_ops_entries; + }; + +std::vector<size_t> unique_buffer_sizes(const std::string& cmdline_arg) + { + const size_t MAX_BUF_SIZE = 64*1024*1024; + + std::set<size_t> buf; + for(std::string size_str : Botan::split_on(cmdline_arg, ',')) + { + size_t x = 0; + try + { + size_t converted = 0; + x = static_cast<size_t>(std::stoul(size_str, &converted, 0)); + + if(converted != size_str.size()) + throw CLI_Usage_Error("Invalid integer"); + } + catch(std::exception&) + { + throw CLI_Usage_Error("Invalid integer value '" + size_str + "' for option buf-size"); + } + + if(x == 0) + throw CLI_Usage_Error("Cannot have a zero-sized buffer"); + + if(x > MAX_BUF_SIZE) + throw CLI_Usage_Error("Specified buffer size is too large"); + + buf.insert(x); + } + + return std::vector<size_t>(buf.begin(), buf.end()); + } + +} + +class Speed final : public Command + { + public: + Speed() + : Command("speed --msec=500 --format=default --ecc-groups= --provider= --buf-size=1024 --clear-cpuid= --cpu-clock-speed=0 --cpu-clock-ratio=1.0 *algos") {} + + std::vector<std::string> default_benchmark_list() + { + /* + This is not intended to be exhaustive: it just hits the high + points of the most interesting or widely used algorithms. + */ + + return { + /* Block ciphers */ + "AES-128", + "AES-192", + "AES-256", + "ARIA-128", + "ARIA-192", + "ARIA-256", + "Blowfish", + "CAST-128", + "CAST-256", + "Camellia-128", + "Camellia-192", + "Camellia-256", + "DES", + "TripleDES", + "GOST-28147-89", + "IDEA", + "KASUMI", + "MISTY1", + "Noekeon", + "SHACAL2", + "SM4", + "Serpent", + "Threefish-512", + "Twofish", + "XTEA", + + /* Cipher modes */ + "AES-128/CBC", + "AES-128/CTR-BE", + "AES-128/EAX", + "AES-128/OCB", + "AES-128/GCM", + "AES-128/XTS", + "AES-128/SIV", + + "Serpent/CBC", + "Serpent/CTR-BE", + "Serpent/EAX", + "Serpent/OCB", + "Serpent/GCM", + "Serpent/XTS", + "Serpent/SIV", + + "ChaCha20Poly1305", + + /* Stream ciphers */ + "RC4", + "Salsa20", + "ChaCha20", + + /* Hashes */ + "SHA-160", + "SHA-256", + "SHA-512", + "SHA-3(256)", + "SHA-3(512)", + "RIPEMD-160", + "Skein-512", + "Blake2b", + "Tiger", + "Whirlpool", + + /* MACs */ + "CMAC(AES-128)", + "HMAC(SHA-256)", + + /* pubkey */ + "RSA", + "DH", + "ECDH", + "ECDSA", + "Ed25519", + "Curve25519", + "NEWHOPE", + "McEliece", + }; + } + + std::string group() const override + { + return "misc"; + } + + std::string description() const override + { + return "Measures the speed of algorithms"; + } + + void go() override + { + std::chrono::milliseconds msec(get_arg_sz("msec")); + const std::string provider = get_arg("provider"); + std::vector<std::string> ecc_groups = Botan::split_on(get_arg("ecc-groups"), ','); + const std::string format = get_arg("format"); + const std::string clock_ratio = get_arg("cpu-clock-ratio"); + m_clock_speed = get_arg_sz("cpu-clock-speed"); + + m_clock_cycle_ratio = std::strtod(clock_ratio.c_str(), nullptr); + + /* + * This argument is intended to be the ratio between the cycle counter + * and the actual machine cycles. It is extremely unlikely that there is + * any machine where the cycle counter increments faster than the actual + * clock. + */ + if(m_clock_cycle_ratio < 0.0 || m_clock_cycle_ratio > 1.0) + throw CLI_Usage_Error("Unlikely CPU clock ratio of " + clock_ratio); + + m_clock_cycle_ratio = 1.0 / m_clock_cycle_ratio; + + if(m_clock_speed != 0 && Botan::OS::get_cpu_cycle_counter() != 0) + { + error_output() << "The --cpu-clock-speed option is only intended to be used on " + "platforms without access to a cycle counter.\n" + "Expected incorrect results\n\n"; + } + + if(format == "table") + m_summary.reset(new Summary); + else if(format == "json") + m_json.reset(new JSON_Output); + else if(format != "default") + throw CLI_Usage_Error("Unknown --format type '" + format + "'"); + +#if defined(BOTAN_HAS_ECC_GROUP) + if(ecc_groups.empty()) + { + ecc_groups = { "secp256r1", "brainpool256r1", + "secp384r1", "brainpool384r1", + "secp521r1", "brainpool512r1" }; + } + else if(ecc_groups.size() == 1 && ecc_groups[0] == "all") + { + auto all = Botan::EC_Group::known_named_groups(); + ecc_groups.assign(all.begin(), all.end()); + } +#endif + + std::vector<std::string> algos = get_arg_list("algos"); + + const std::vector<size_t> buf_sizes = unique_buffer_sizes(get_arg("buf-size")); + + for(std::string cpuid_to_clear : Botan::split_on(get_arg("clear-cpuid"), ',')) + { + auto bits = Botan::CPUID::bit_from_string(cpuid_to_clear); + if(bits.empty()) + { + error_output() << "Warning don't know CPUID flag '" << cpuid_to_clear << "'\n"; + } + + for(auto bit : bits) + { + Botan::CPUID::clear_cpuid_bit(bit); + } + } + + if(verbose() || m_summary) + { + output() << Botan::version_string() << "\n" + << "CPUID: " << Botan::CPUID::to_string() << "\n\n"; + } + + const bool using_defaults = (algos.empty()); + if(using_defaults) + { + algos = default_benchmark_list(); + } + + for(auto algo : algos) + { + using namespace std::placeholders; + + if(false) + { + // Since everything might be disabled, need a block to else if from + } +#if defined(BOTAN_HAS_HASH) + else if(Botan::HashFunction::providers(algo).size() > 0) + { + bench_providers_of<Botan::HashFunction>( + algo, provider, msec, buf_sizes, + std::bind(&Speed::bench_hash, this, _1, _2, _3, _4)); + } +#endif +#if defined(BOTAN_HAS_BLOCK_CIPHER) + else if(Botan::BlockCipher::providers(algo).size() > 0) + { + bench_providers_of<Botan::BlockCipher>( + algo, provider, msec, buf_sizes, + std::bind(&Speed::bench_block_cipher, this, _1, _2, _3, _4)); + } +#endif +#if defined(BOTAN_HAS_STREAM_CIPHER) + else if(Botan::StreamCipher::providers(algo).size() > 0) + { + bench_providers_of<Botan::StreamCipher>( + algo, provider, msec, buf_sizes, + std::bind(&Speed::bench_stream_cipher, this, _1, _2, _3, _4)); + } +#endif +#if defined(BOTAN_HAS_CIPHER_MODES) + else if(auto enc = Botan::Cipher_Mode::create(algo, Botan::ENCRYPTION, provider)) + { + auto dec = Botan::Cipher_Mode::create_or_throw(algo, Botan::DECRYPTION, provider); + bench_cipher_mode(*enc, *dec, msec, buf_sizes); + } +#endif +#if defined(BOTAN_HAS_MAC) + else if(Botan::MessageAuthenticationCode::providers(algo).size() > 0) + { + bench_providers_of<Botan::MessageAuthenticationCode>( + algo, provider, msec, buf_sizes, + std::bind(&Speed::bench_mac, this, _1, _2, _3, _4)); + } +#endif +#if defined(BOTAN_HAS_RSA) + else if(algo == "RSA") + { + bench_rsa(provider, msec); + } + else if(algo == "RSA_keygen") + { + bench_rsa_keygen(provider, msec); + } +#endif +#if defined(BOTAN_HAS_ECDSA) + else if(algo == "ECDSA") + { + bench_ecdsa(ecc_groups, provider, msec); + } + else if(algo == "ecdsa_recovery") + { + bench_ecdsa_recovery(ecc_groups, provider, msec); + } +#endif +#if defined(BOTAN_HAS_SM2) + else if(algo == "SM2") + { + bench_sm2(ecc_groups, provider, msec); + } +#endif +#if defined(BOTAN_HAS_ECKCDSA) + else if(algo == "ECKCDSA") + { + bench_eckcdsa(ecc_groups, provider, msec); + } +#endif +#if defined(BOTAN_HAS_GOST_34_10_2001) + else if(algo == "GOST-34.10") + { + bench_gost_3410(provider, msec); + } +#endif +#if defined(BOTAN_HAS_ECGDSA) + else if(algo == "ECGDSA") + { + bench_ecgdsa(ecc_groups, provider, msec); + } +#endif +#if defined(BOTAN_HAS_ED25519) + else if(algo == "Ed25519") + { + bench_ed25519(provider, msec); + } +#endif +#if defined(BOTAN_HAS_DIFFIE_HELLMAN) + else if(algo == "DH") + { + bench_dh(provider, msec); + } +#endif +#if defined(BOTAN_HAS_DSA) + else if(algo == "DSA") + { + bench_dsa(provider, msec); + } +#endif +#if defined(BOTAN_HAS_ELGAMAL) + else if(algo == "ElGamal") + { + bench_elgamal(provider, msec); + } +#endif +#if defined(BOTAN_HAS_ECDH) + else if(algo == "ECDH") + { + bench_ecdh(ecc_groups, provider, msec); + } +#endif +#if defined(BOTAN_HAS_CURVE_25519) + else if(algo == "Curve25519") + { + bench_curve25519(provider, msec); + } +#endif +#if defined(BOTAN_HAS_MCELIECE) + else if(algo == "McEliece") + { + bench_mceliece(provider, msec); + } +#endif +#if defined(BOTAN_HAS_XMSS_RFC8391) + else if(algo == "XMSS") + { + bench_xmss(provider, msec); + } +#endif +#if defined(BOTAN_HAS_NEWHOPE) && defined(BOTAN_HAS_CHACHA_RNG) + else if(algo == "NEWHOPE") + { + bench_newhope(provider, msec); + } +#endif +#if defined(BOTAN_HAS_SCRYPT) + else if(algo == "scrypt") + { + bench_scrypt(provider, msec); + } +#endif +#if defined(BOTAN_HAS_ARGON2) + else if(algo == "argon2") + { + bench_argon2(provider, msec); + } +#endif +#if defined(BOTAN_HAS_BCRYPT) + else if(algo == "bcrypt") + { + bench_bcrypt(); + } +#endif +#if defined(BOTAN_HAS_PASSHASH9) + else if(algo == "passhash9") + { + bench_passhash9(); + } +#endif +#if defined(BOTAN_HAS_POLY_DBL) + else if(algo == "poly_dbl") + { + bench_poly_dbl(msec); + } +#endif + +#if defined(BOTAN_HAS_DL_GROUP) + else if(algo == "modexp") + { + bench_modexp(msec); + } +#endif + +#if defined(BOTAN_HAS_BIGINT) + else if(algo == "mp_mul") + { + bench_mp_mul(msec); + } + else if(algo == "mp_div") + { + bench_mp_div(msec); + } + else if(algo == "mp_div10") + { + bench_mp_div10(msec); + } +#endif + +#if defined(BOTAN_HAS_NUMBERTHEORY) + else if(algo == "primality_test") + { + bench_primality_tests(msec); + } + else if(algo == "random_prime") + { + bench_random_prime(msec); + } + else if(algo == "inverse_mod") + { + bench_inverse_mod(msec); + } + else if(algo == "bn_redc") + { + bench_bn_redc(msec); + } + else if(algo == "nistp_redc") + { + bench_nistp_redc(msec); + } +#endif + +#if defined(BOTAN_HAS_FPE_FE1) + else if(algo == "fpe_fe1") + { + bench_fpe_fe1(msec); + } +#endif + +#if defined(BOTAN_HAS_RFC3394_KEYWRAP) + else if(algo == "rfc3394") + { + bench_rfc3394(msec); + } +#endif + +#if defined(BOTAN_HAS_ECC_GROUP) + else if(algo == "ecc_mult") + { + bench_ecc_mult(ecc_groups, msec); + } + else if(algo == "ecc_ops") + { + bench_ecc_ops(ecc_groups, msec); + } + else if(algo == "ecc_init") + { + bench_ecc_init(ecc_groups, msec); + } + else if(algo == "os2ecp") + { + bench_os2ecp(ecc_groups, msec); + } +#endif + else if(algo == "RNG") + { +#if defined(BOTAN_HAS_AUTO_SEEDING_RNG) + Botan::AutoSeeded_RNG auto_rng; + bench_rng(auto_rng, "AutoSeeded_RNG (with reseed)", msec, buf_sizes); +#endif + +#if defined(BOTAN_HAS_SYSTEM_RNG) + bench_rng(Botan::system_rng(), "System_RNG", msec, buf_sizes); +#endif + +#if defined(BOTAN_HAS_PROCESSOR_RNG) + if(Botan::Processor_RNG::available()) + { + Botan::Processor_RNG hwrng; + bench_rng(hwrng, "Processor_RNG", msec, buf_sizes); + } +#endif + +#if defined(BOTAN_HAS_HMAC_DRBG) + for(std::string hash : { "SHA-256", "SHA-384", "SHA-512" }) + { + Botan::HMAC_DRBG hmac_drbg(hash); + bench_rng(hmac_drbg, hmac_drbg.name(), msec, buf_sizes); + } +#endif + +#if defined(BOTAN_HAS_CHACHA_RNG) + // Provide a dummy seed + Botan::ChaCha_RNG chacha_rng(Botan::secure_vector<uint8_t>(32)); + bench_rng(chacha_rng, "ChaCha_RNG", msec, buf_sizes); +#endif + + } + else if(algo == "entropy") + { + bench_entropy_sources(msec); + } + else + { + if(verbose() || !using_defaults) + { + error_output() << "Unknown algorithm '" << algo << "'\n"; + } + } + } + + if(m_json) + { + output() << m_json->print(); + } + if(m_summary) + { + output() << m_summary->print() << "\n"; + } + + if(verbose() && m_clock_speed == 0 && m_cycles_consumed > 0 && m_ns_taken > 0) + { + const double seconds = static_cast<double>(m_ns_taken) / 1000000000; + const double Hz = static_cast<double>(m_cycles_consumed) / seconds; + const double MHz = Hz / 1000000; + output() << "\nEstimated clock speed " << MHz << " MHz\n"; + } + } + + private: + + size_t m_clock_speed = 0; + double m_clock_cycle_ratio = 0.0; + uint64_t m_cycles_consumed = 0; + uint64_t m_ns_taken = 0; + std::unique_ptr<Summary> m_summary; + std::unique_ptr<JSON_Output> m_json; + + void record_result(const std::unique_ptr<Timer>& t) + { + m_ns_taken += t->value(); + m_cycles_consumed += t->cycles_consumed(); + if(m_json) + { + m_json->add(*t); + } + else + { + output() << t->to_string() << std::flush; + if(m_summary) + m_summary->add(*t); + } + } + + template<typename T> + using bench_fn = std::function<void (T&, + std::string, + std::chrono::milliseconds, + const std::vector<size_t>&)>; + + template<typename T> + void bench_providers_of(const std::string& algo, + const std::string& provider, /* user request, if any */ + const std::chrono::milliseconds runtime, + const std::vector<size_t>& buf_sizes, + bench_fn<T> bench_one) + { + for(auto const& prov : T::providers(algo)) + { + if(provider.empty() || provider == prov) + { + auto p = T::create(algo, prov); + + if(p) + { + bench_one(*p, prov, runtime, buf_sizes); + } + } + } + } + + std::unique_ptr<Timer> make_timer(const std::string& name, + uint64_t event_mult = 1, + const std::string& what = "", + const std::string& provider = "", + size_t buf_size = 0) + { + return std::unique_ptr<Timer>( + new Timer(name, provider, what, event_mult, buf_size, + m_clock_cycle_ratio, m_clock_speed)); + } + + std::unique_ptr<Timer> make_timer(const std::string& algo, + const std::string& provider, + const std::string& what) + { + return make_timer(algo, 1, what, provider, 0); + } + +#if defined(BOTAN_HAS_BLOCK_CIPHER) + void bench_block_cipher(Botan::BlockCipher& cipher, + const std::string& provider, + std::chrono::milliseconds runtime, + const std::vector<size_t>& buf_sizes) + { + std::unique_ptr<Timer> ks_timer = make_timer(cipher.name(), provider, "key schedule"); + + const Botan::SymmetricKey key(rng(), cipher.maximum_keylength()); + ks_timer->run([&]() { cipher.set_key(key); }); + + const size_t bs = cipher.block_size(); + std::set<size_t> buf_sizes_in_blocks; + for(size_t buf_size : buf_sizes) + { + if(buf_size % bs == 0) + buf_sizes_in_blocks.insert(buf_size); + else + buf_sizes_in_blocks.insert(buf_size + bs - (buf_size % bs)); + } + + for(size_t buf_size : buf_sizes_in_blocks) + { + std::vector<uint8_t> buffer(buf_size); + const size_t blocks = buf_size / bs; + + std::unique_ptr<Timer> encrypt_timer = make_timer(cipher.name(), buffer.size(), "encrypt", provider, buf_size); + std::unique_ptr<Timer> decrypt_timer = make_timer(cipher.name(), buffer.size(), "decrypt", provider, buf_size); + + encrypt_timer->run_until_elapsed(runtime, [&]() { cipher.encrypt_n(&buffer[0], &buffer[0], blocks); }); + record_result(encrypt_timer); + + decrypt_timer->run_until_elapsed(runtime, [&]() { cipher.decrypt_n(&buffer[0], &buffer[0], blocks); }); + record_result(decrypt_timer); + } + } +#endif + +#if defined(BOTAN_HAS_STREAM_CIPHER) + void bench_stream_cipher( + Botan::StreamCipher& cipher, + const std::string& provider, + const std::chrono::milliseconds runtime, + const std::vector<size_t>& buf_sizes) + { + for(auto buf_size : buf_sizes) + { + Botan::secure_vector<uint8_t> buffer = rng().random_vec(buf_size); + + std::unique_ptr<Timer> encrypt_timer = make_timer(cipher.name(), buffer.size(), "encrypt", provider, buf_size); + + const Botan::SymmetricKey key(rng(), cipher.maximum_keylength()); + cipher.set_key(key); + + if(cipher.valid_iv_length(12)) + { + const Botan::InitializationVector iv(rng(), 12); + cipher.set_iv(iv.begin(), iv.size()); + } + + while(encrypt_timer->under(runtime)) + { + encrypt_timer->run([&]() { cipher.encipher(buffer); }); + } + + record_result(encrypt_timer); + } + } +#endif + +#if defined(BOTAN_HAS_HASH) + void bench_hash( + Botan::HashFunction& hash, + const std::string& provider, + const std::chrono::milliseconds runtime, + const std::vector<size_t>& buf_sizes) + { + std::vector<uint8_t> output(hash.output_length()); + + for(auto buf_size : buf_sizes) + { + Botan::secure_vector<uint8_t> buffer = rng().random_vec(buf_size); + + std::unique_ptr<Timer> timer = make_timer(hash.name(), buffer.size(), "hash", provider, buf_size); + timer->run_until_elapsed(runtime, [&]() { hash.update(buffer); hash.final(output.data()); }); + record_result(timer); + } + } +#endif + +#if defined(BOTAN_HAS_MAC) + void bench_mac( + Botan::MessageAuthenticationCode& mac, + const std::string& provider, + const std::chrono::milliseconds runtime, + const std::vector<size_t>& buf_sizes) + { + std::vector<uint8_t> output(mac.output_length()); + + for(auto buf_size : buf_sizes) + { + Botan::secure_vector<uint8_t> buffer = rng().random_vec(buf_size); + + const Botan::SymmetricKey key(rng(), mac.maximum_keylength()); + mac.set_key(key); + mac.start(nullptr, 0); + + std::unique_ptr<Timer> timer = make_timer(mac.name(), buffer.size(), "mac", provider, buf_size); + timer->run_until_elapsed(runtime, [&]() { mac.update(buffer); }); + timer->run([&]() { mac.final(output.data()); }); + record_result(timer); + } + } +#endif + +#if defined(BOTAN_HAS_CIPHER_MODES) + void bench_cipher_mode( + Botan::Cipher_Mode& enc, + Botan::Cipher_Mode& dec, + const std::chrono::milliseconds runtime, + const std::vector<size_t>& buf_sizes) + { + std::unique_ptr<Timer> ks_timer = make_timer(enc.name(), enc.provider(), "key schedule"); + + const Botan::SymmetricKey key(rng(), enc.key_spec().maximum_keylength()); + + ks_timer->run([&]() { enc.set_key(key); }); + ks_timer->run([&]() { dec.set_key(key); }); + + record_result(ks_timer); + + for(auto buf_size : buf_sizes) + { + Botan::secure_vector<uint8_t> buffer = rng().random_vec(buf_size); + + std::unique_ptr<Timer> encrypt_timer = make_timer(enc.name(), buffer.size(), "encrypt", enc.provider(), buf_size); + std::unique_ptr<Timer> decrypt_timer = make_timer(dec.name(), buffer.size(), "decrypt", dec.provider(), buf_size); + + Botan::secure_vector<uint8_t> iv = rng().random_vec(enc.default_nonce_length()); + + if(buf_size >= enc.minimum_final_size()) + { + while(encrypt_timer->under(runtime) && decrypt_timer->under(runtime)) + { + // Must run in this order, or AEADs will reject the ciphertext + encrypt_timer->run([&]() { enc.start(iv); enc.finish(buffer); }); + + decrypt_timer->run([&]() { dec.start(iv); dec.finish(buffer); }); + + if(iv.size() > 0) + { + iv[iv.size()-1] += 1; + } + } + } + + record_result(encrypt_timer); + record_result(decrypt_timer); + } + } +#endif + + void bench_rng( + Botan::RandomNumberGenerator& rng, + const std::string& rng_name, + const std::chrono::milliseconds runtime, + const std::vector<size_t>& buf_sizes) + { + for(auto buf_size : buf_sizes) + { + Botan::secure_vector<uint8_t> buffer(buf_size); + +#if defined(BOTAN_HAS_SYSTEM_RNG) + rng.reseed_from_rng(Botan::system_rng(), 256); +#endif + + std::unique_ptr<Timer> timer = make_timer(rng_name, buffer.size(), "generate", "", buf_size); + timer->run_until_elapsed(runtime, [&]() { rng.randomize(buffer.data(), buffer.size()); }); + record_result(timer); + } + } + + void bench_entropy_sources(const std::chrono::milliseconds) + { + Botan::Entropy_Sources& srcs = Botan::Entropy_Sources::global_sources(); + + for(auto src : srcs.enabled_sources()) + { + size_t entropy_bits = 0; + Botan_Tests::SeedCapturing_RNG rng; + + std::unique_ptr<Timer> timer = make_timer(src, "", "bytes"); + timer->run([&]() { entropy_bits = srcs.poll_just(rng, src); }); + + size_t compressed_size = 0; + +#if defined(BOTAN_HAS_ZLIB) + std::unique_ptr<Botan::Compression_Algorithm> comp(Botan::make_compressor("zlib")); + + if(comp) + { + Botan::secure_vector<uint8_t> compressed; + compressed.assign(rng.seed_material().begin(), rng.seed_material().end()); + comp->start(9); + comp->finish(compressed); + + compressed_size = compressed.size(); + } +#endif + + std::ostringstream msg; + + msg << "Entropy source " << src << " output " << rng.seed_material().size() << " bytes" + << " estimated entropy " << entropy_bits << " in " << timer->milliseconds() << " ms"; + + if(compressed_size > 0) + { + msg << " output compressed to " << compressed_size << " bytes"; + } + + msg << " total samples " << rng.samples() << "\n"; + + timer->set_custom_msg(msg.str()); + + record_result(timer); + } + } + +#if defined(BOTAN_HAS_ECC_GROUP) + void bench_ecc_ops(const std::vector<std::string>& groups, const std::chrono::milliseconds runtime) + { + for(std::string group_name : groups) + { + const Botan::EC_Group ec_group(group_name); + + std::unique_ptr<Timer> add_timer = make_timer(group_name + " add"); + std::unique_ptr<Timer> addf_timer = make_timer(group_name + " addf"); + std::unique_ptr<Timer> dbl_timer = make_timer(group_name + " dbl"); + + const Botan::PointGFp& base_point = ec_group.get_base_point(); + Botan::PointGFp non_affine_pt = ec_group.get_base_point() * 1776; // create a non-affine point + Botan::PointGFp pt = ec_group.get_base_point(); + + std::vector<Botan::BigInt> ws(Botan::PointGFp::WORKSPACE_SIZE); + + while(add_timer->under(runtime) && addf_timer->under(runtime) && dbl_timer->under(runtime)) + { + dbl_timer->run([&]() { pt.mult2(ws); }); + add_timer->run([&]() { pt.add(non_affine_pt, ws); }); + addf_timer->run([&]() { pt.add_affine(base_point, ws); }); + } + + record_result(dbl_timer); + record_result(add_timer); + record_result(addf_timer); + } + } + + void bench_ecc_init(const std::vector<std::string>& groups, const std::chrono::milliseconds runtime) + { + for(std::string group_name : groups) + { + std::unique_ptr<Timer> timer = make_timer(group_name + " initialization"); + + while(timer->under(runtime)) + { + Botan::EC_Group::clear_registered_curve_data(); + timer->run([&]() { Botan::EC_Group group(group_name); }); + } + + record_result(timer); + } + } + + void bench_ecc_mult(const std::vector<std::string>& groups, const std::chrono::milliseconds runtime) + { + for(std::string group_name : groups) + { + const Botan::EC_Group ec_group(group_name); + + std::unique_ptr<Timer> mult_timer = make_timer(group_name + " Montgomery ladder"); + std::unique_ptr<Timer> blinded_mult_timer = make_timer(group_name + " blinded comb"); + std::unique_ptr<Timer> blinded_var_mult_timer = make_timer(group_name + " blinded window"); + + const Botan::PointGFp& base_point = ec_group.get_base_point(); + + std::vector<Botan::BigInt> ws; + + while(mult_timer->under(runtime) && + blinded_mult_timer->under(runtime) && + blinded_var_mult_timer->under(runtime)) + { + const Botan::BigInt scalar(rng(), ec_group.get_p_bits()); + + const Botan::PointGFp r1 = mult_timer->run([&]() { return base_point * scalar; }); + + const Botan::PointGFp r2 = blinded_mult_timer->run( + [&]() { return ec_group.blinded_base_point_multiply(scalar, rng(), ws); }); + + const Botan::PointGFp r3 = blinded_var_mult_timer->run( + [&]() { return ec_group.blinded_var_point_multiply(base_point, scalar, rng(), ws); }); + + BOTAN_ASSERT_EQUAL(r1, r2, "Same point computed by Montgomery and comb"); + BOTAN_ASSERT_EQUAL(r1, r3, "Same point computed by Montgomery and window"); + } + + record_result(mult_timer); + record_result(blinded_mult_timer); + record_result(blinded_var_mult_timer); + } + } + + void bench_os2ecp(const std::vector<std::string>& groups, const std::chrono::milliseconds runtime) + { + std::unique_ptr<Timer> uncmp_timer = make_timer("OS2ECP uncompressed"); + std::unique_ptr<Timer> cmp_timer = make_timer("OS2ECP compressed"); + + for(std::string group_name : groups) + { + const Botan::EC_Group ec_group(group_name); + + while(uncmp_timer->under(runtime) && cmp_timer->under(runtime)) + { + const Botan::BigInt k(rng(), 256); + const Botan::PointGFp p = ec_group.get_base_point() * k; + const std::vector<uint8_t> os_cmp = p.encode(Botan::PointGFp::COMPRESSED); + const std::vector<uint8_t> os_uncmp = p.encode(Botan::PointGFp::UNCOMPRESSED); + + uncmp_timer->run([&]() { ec_group.OS2ECP(os_uncmp); }); + cmp_timer->run([&]() { ec_group.OS2ECP(os_cmp); }); + } + + record_result(uncmp_timer); + record_result(cmp_timer); + } + } + +#endif + +#if defined(BOTAN_HAS_FPE_FE1) + + void bench_fpe_fe1(const std::chrono::milliseconds runtime) + { + const Botan::BigInt n = 1000000000000000; + + std::unique_ptr<Timer> enc_timer = make_timer("FPE_FE1 encrypt"); + std::unique_ptr<Timer> dec_timer = make_timer("FPE_FE1 decrypt"); + + const Botan::SymmetricKey key(rng(), 32); + const std::vector<uint8_t> tweak(8); // 8 zeros + + Botan::BigInt x = 1; + + Botan::FPE_FE1 fpe_fe1(n); + fpe_fe1.set_key(key); + + while(enc_timer->under(runtime)) + { + enc_timer->start(); + x = fpe_fe1.encrypt(x, tweak.data(), tweak.size()); + enc_timer->stop(); + } + + for(size_t i = 0; i != enc_timer->events(); ++i) + { + dec_timer->start(); + x = fpe_fe1.decrypt(x, tweak.data(), tweak.size()); + dec_timer->stop(); + } + + BOTAN_ASSERT(x == 1, "FPE works"); + + record_result(enc_timer); + record_result(dec_timer); + } +#endif + +#if defined(BOTAN_HAS_RFC3394_KEYWRAP) + + void bench_rfc3394(const std::chrono::milliseconds runtime) + { + std::unique_ptr<Timer> wrap_timer = make_timer("RFC3394 AES-256 key wrap"); + std::unique_ptr<Timer> unwrap_timer = make_timer("RFC3394 AES-256 key unwrap"); + + const Botan::SymmetricKey kek(rng(), 32); + Botan::secure_vector<uint8_t> key(64, 0); + + while(wrap_timer->under(runtime)) + { + wrap_timer->start(); + key = Botan::rfc3394_keywrap(key, kek); + wrap_timer->stop(); + + unwrap_timer->start(); + key = Botan::rfc3394_keyunwrap(key, kek); + unwrap_timer->stop(); + + key[0] += 1; + } + + record_result(wrap_timer); + record_result(unwrap_timer); + } +#endif + +#if defined(BOTAN_HAS_BIGINT) + + void bench_mp_mul(const std::chrono::milliseconds runtime) + { + std::chrono::milliseconds runtime_per_size = runtime; + for(size_t bits : { 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096 }) + { + std::unique_ptr<Timer> mul_timer = make_timer("BigInt mul " + std::to_string(bits)); + std::unique_ptr<Timer> sqr_timer = make_timer("BigInt sqr " + std::to_string(bits)); + + const Botan::BigInt y(rng(), bits); + Botan::secure_vector<Botan::word> ws; + + while(mul_timer->under(runtime_per_size)) + { + Botan::BigInt x(rng(), bits); + + sqr_timer->start(); + x.square(ws); + sqr_timer->stop(); + + x.mask_bits(bits); + + mul_timer->start(); + x.mul(y, ws); + mul_timer->stop(); + } + + record_result(mul_timer); + record_result(sqr_timer); + } + + } + + void bench_mp_div(const std::chrono::milliseconds runtime) + { + std::chrono::milliseconds runtime_per_size = runtime; + + for(size_t n_bits : { 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096 }) + { + const size_t q_bits = n_bits / 2; + const std::string bit_descr = std::to_string(n_bits) + "/" + std::to_string(q_bits); + + std::unique_ptr<Timer> div_timer = make_timer("BigInt div " + bit_descr); + std::unique_ptr<Timer> ct_div_timer = make_timer("BigInt ct_div " + bit_descr); + + Botan::BigInt y; + Botan::BigInt x; + Botan::secure_vector<Botan::word> ws; + + Botan::BigInt q1, r1, q2, r2; + + while(ct_div_timer->under(runtime_per_size)) + { + x.randomize(rng(), n_bits); + y.randomize(rng(), q_bits); + + div_timer->start(); + Botan::vartime_divide(x, y, q1, r1); + div_timer->stop(); + + ct_div_timer->start(); + Botan::ct_divide(x, y, q2, r2); + ct_div_timer->stop(); + + BOTAN_ASSERT_EQUAL(q1, q2, "Quotient ok"); + BOTAN_ASSERT_EQUAL(r1, r2, "Remainder ok"); + } + + record_result(div_timer); + record_result(ct_div_timer); + } + } + + void bench_mp_div10(const std::chrono::milliseconds runtime) + { + std::chrono::milliseconds runtime_per_size = runtime; + + for(size_t n_bits : { 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096 }) + { + const std::string bit_descr = std::to_string(n_bits) + "/10"; + + std::unique_ptr<Timer> div_timer = make_timer("BigInt div " + bit_descr); + std::unique_ptr<Timer> ct_div_timer = make_timer("BigInt ct_div " + bit_descr); + + Botan::BigInt x; + Botan::secure_vector<Botan::word> ws; + + const Botan::BigInt ten(10); + Botan::BigInt q1, r1, q2; + uint8_t r2; + + while(ct_div_timer->under(runtime_per_size)) + { + x.randomize(rng(), n_bits); + + div_timer->start(); + Botan::vartime_divide(x, ten, q1, r1); + div_timer->stop(); + + ct_div_timer->start(); + Botan::ct_divide_u8(x, 10, q2, r2); + ct_div_timer->stop(); + + BOTAN_ASSERT_EQUAL(q1, q2, "Quotient ok"); + BOTAN_ASSERT_EQUAL(r1, r2, "Remainder ok"); + } + + record_result(div_timer); + record_result(ct_div_timer); + } + } + +#endif + +#if defined(BOTAN_HAS_DL_GROUP) + + void bench_modexp(const std::chrono::milliseconds runtime) + { + for(size_t group_bits : { 1024, 1536, 2048, 3072, 4096 }) + { + const std::string group_bits_str = std::to_string(group_bits); + const Botan::DL_Group group("modp/srp/" + group_bits_str); + + const size_t e_bits = Botan::dl_exponent_size(group_bits); + const size_t f_bits = group_bits - 1; + + const Botan::BigInt random_e(rng(), e_bits); + const Botan::BigInt random_f(rng(), f_bits); + + std::unique_ptr<Timer> e_timer = make_timer(group_bits_str + " short exponent"); + std::unique_ptr<Timer> f_timer = make_timer(group_bits_str + " full exponent"); + + while(f_timer->under(runtime)) + { + e_timer->run([&]() { group.power_g_p(random_e); }); + f_timer->run([&]() { group.power_g_p(random_f); }); + } + + record_result(e_timer); + record_result(f_timer); + } + } +#endif + +#if defined(BOTAN_HAS_NUMBERTHEORY) + void bench_nistp_redc(const std::chrono::milliseconds runtime) + { + Botan::secure_vector<Botan::word> ws; + + std::unique_ptr<Timer> p192_timer = make_timer("P-192 redc"); + Botan::BigInt r192(rng(), 192*2 - 1); + while(p192_timer->under(runtime)) + { + Botan::BigInt r = r192; + p192_timer->run([&]() { Botan::redc_p192(r, ws); }); + r192 += 1; + } + record_result(p192_timer); + + std::unique_ptr<Timer> p224_timer = make_timer("P-224 redc"); + Botan::BigInt r224(rng(), 224*2 - 1); + while(p224_timer->under(runtime)) + { + Botan::BigInt r = r224; + p224_timer->run([&]() { Botan::redc_p224(r, ws); }); + r224 += 1; + } + record_result(p224_timer); + + std::unique_ptr<Timer> p256_timer = make_timer("P-256 redc"); + Botan::BigInt r256(rng(), 256*2 - 1); + while(p256_timer->under(runtime)) + { + Botan::BigInt r = r256; + p256_timer->run([&]() { Botan::redc_p256(r, ws); }); + r256 += 1; + } + record_result(p256_timer); + + std::unique_ptr<Timer> p384_timer = make_timer("P-384 redc"); + Botan::BigInt r384(rng(), 384*2 - 1); + while(p384_timer->under(runtime)) + { + Botan::BigInt r = r384; + p384_timer->run([&]() { Botan::redc_p384(r384, ws); }); + r384 += 1; + } + record_result(p384_timer); + + std::unique_ptr<Timer> p521_timer = make_timer("P-521 redc"); + Botan::BigInt r521(rng(), 521*2 - 1); + while(p521_timer->under(runtime)) + { + Botan::BigInt r = r521; + p521_timer->run([&]() { Botan::redc_p521(r521, ws); }); + r521 += 1; + } + record_result(p521_timer); + } + + void bench_bn_redc(const std::chrono::milliseconds runtime) + { + for(size_t bitsize : { 512, 1024, 2048, 4096 }) + { + Botan::BigInt p(rng(), bitsize); + + std::string bit_str = std::to_string(bitsize); + std::unique_ptr<Timer> barrett_timer = make_timer("Barrett-" + bit_str); + std::unique_ptr<Timer> schoolbook_timer = make_timer("Schoolbook-" + bit_str); + + Botan::Modular_Reducer mod_p(p); + + while(schoolbook_timer->under(runtime)) + { + const Botan::BigInt x(rng(), p.bits() * 2 - 2); + + const Botan::BigInt r1 = barrett_timer->run( + [&] { return mod_p.reduce(x); }); + const Botan::BigInt r2 = schoolbook_timer->run( + [&] { return x % p; }); + + BOTAN_ASSERT(r1 == r2, "Computed different results"); + } + + record_result(barrett_timer); + record_result(schoolbook_timer); + } + } + + void bench_inverse_mod(const std::chrono::milliseconds runtime) + { + for(size_t bits : { 256, 384, 512, 1024, 2048 }) + { + const std::string bit_str = std::to_string(bits); + + std::unique_ptr<Timer> timer = make_timer("inverse_mod-" + bit_str); + + while(timer->under(runtime)) + { + const Botan::BigInt x(rng(), bits - 1); + Botan::BigInt mod(rng(), bits); + + const Botan::BigInt x_inv = timer->run( + [&] { return Botan::inverse_mod(x, mod); }); + + if(x_inv == 0) + { + const Botan::BigInt g = gcd(x, mod); + BOTAN_ASSERT(g != 1, "Inversion only fails if gcd(x, mod) > 1"); + } + else + { + const Botan::BigInt check = (x_inv*x) % mod; + BOTAN_ASSERT_EQUAL(check, 1, "Const time inversion correct"); + } + } + + record_result(timer); + } + } + + void bench_primality_tests(const std::chrono::milliseconds runtime) + { + for(size_t bits : { 256, 512, 1024 }) + { + std::unique_ptr<Timer> mr_timer = make_timer("Miller-Rabin-" + std::to_string(bits)); + std::unique_ptr<Timer> bpsw_timer = make_timer("Bailie-PSW-" + std::to_string(bits)); + std::unique_ptr<Timer> lucas_timer = make_timer("Lucas-" + std::to_string(bits)); + + Botan::BigInt n = Botan::random_prime(rng(), bits); + + while(lucas_timer->under(runtime)) + { + Botan::Modular_Reducer mod_n(n); + + mr_timer->run([&]() { + return Botan::is_miller_rabin_probable_prime(n, mod_n, rng(), 2); }); + + bpsw_timer->run([&]() { + return Botan::is_bailie_psw_probable_prime(n, mod_n); }); + + lucas_timer->run([&]() { + return Botan::is_lucas_probable_prime(n, mod_n); }); + + n += 2; + } + + record_result(mr_timer); + record_result(bpsw_timer); + record_result(lucas_timer); + } + } + + void bench_random_prime(const std::chrono::milliseconds runtime) + { + const size_t coprime = 65537; // simulates RSA key gen + + for(size_t bits : { 256, 384, 512, 768, 1024, 1536 }) + { + std::unique_ptr<Timer> genprime_timer = make_timer("random_prime " + std::to_string(bits)); + std::unique_ptr<Timer> gensafe_timer = make_timer("random_safe_prime " + std::to_string(bits)); + std::unique_ptr<Timer> is_prime_timer = make_timer("is_prime " + std::to_string(bits)); + + while(gensafe_timer->under(runtime)) + { + const Botan::BigInt p = genprime_timer->run([&] + { + return Botan::random_prime(rng(), bits, coprime); + }); + + if(!is_prime_timer->run([&] { return Botan::is_prime(p, rng(), 64, true); })) + { + error_output() << "Generated prime " << p << " which failed a primality test"; + } + + const Botan::BigInt sg = gensafe_timer->run([&] + { + return Botan::random_safe_prime(rng(), bits); + }); + + if(!is_prime_timer->run([&] { return Botan::is_prime(sg, rng(), 64, true); })) + { + error_output() << "Generated safe prime " << sg << " which failed a primality test"; + } + + if(!is_prime_timer->run([&] { return Botan::is_prime(sg / 2, rng(), 64, true); })) + { + error_output() << "Generated prime " << sg/2 << " which failed a primality test"; + } + + // Now test p+2, p+4, ... which may or may not be prime + for(size_t i = 2; i <= 64; i += 2) + { + is_prime_timer->run([&]() { Botan::is_prime(p + i, rng(), 64, true); }); + } + } + + record_result(genprime_timer); + record_result(gensafe_timer); + record_result(is_prime_timer); + } + } +#endif + +#if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) + void bench_pk_enc( + const Botan::Private_Key& key, + const std::string& nm, + const std::string& provider, + const std::string& padding, + std::chrono::milliseconds msec) + { + std::vector<uint8_t> plaintext, ciphertext; + + Botan::PK_Encryptor_EME enc(key, rng(), padding, provider); + Botan::PK_Decryptor_EME dec(key, rng(), padding, provider); + + std::unique_ptr<Timer> enc_timer = make_timer(nm + " " + padding, provider, "encrypt"); + std::unique_ptr<Timer> dec_timer = make_timer(nm + " " + padding, provider, "decrypt"); + + while(enc_timer->under(msec) || dec_timer->under(msec)) + { + // Generate a new random ciphertext to decrypt + if(ciphertext.empty() || enc_timer->under(msec)) + { + rng().random_vec(plaintext, enc.maximum_input_size()); + ciphertext = enc_timer->run([&]() { return enc.encrypt(plaintext, rng()); }); + } + + if(dec_timer->under(msec)) + { + const auto dec_pt = dec_timer->run([&]() { return dec.decrypt(ciphertext); }); + + if(!(dec_pt == plaintext)) // sanity check + { + error_output() << "Bad roundtrip in PK encrypt/decrypt bench\n"; + } + } + } + + record_result(enc_timer); + record_result(dec_timer); + } + + void bench_pk_ka(const std::string& algo, + const std::string& nm, + const std::string& params, + const std::string& provider, + std::chrono::milliseconds msec) + { + const std::string kdf = "KDF2(SHA-256)"; // arbitrary choice + + std::unique_ptr<Timer> keygen_timer = make_timer(nm, provider, "keygen"); + + std::unique_ptr<Botan::Private_Key> key1(keygen_timer->run([&] + { + return Botan::create_private_key(algo, rng(), params); + })); + std::unique_ptr<Botan::Private_Key> key2(keygen_timer->run([&] + { + return Botan::create_private_key(algo, rng(), params); + })); + + record_result(keygen_timer); + + const Botan::PK_Key_Agreement_Key& ka_key1 = dynamic_cast<const Botan::PK_Key_Agreement_Key&>(*key1); + const Botan::PK_Key_Agreement_Key& ka_key2 = dynamic_cast<const Botan::PK_Key_Agreement_Key&>(*key2); + + Botan::PK_Key_Agreement ka1(ka_key1, rng(), kdf, provider); + Botan::PK_Key_Agreement ka2(ka_key2, rng(), kdf, provider); + + const std::vector<uint8_t> ka1_pub = ka_key1.public_value(); + const std::vector<uint8_t> ka2_pub = ka_key2.public_value(); + + std::unique_ptr<Timer> ka_timer = make_timer(nm, provider, "key agreements"); + + while(ka_timer->under(msec)) + { + Botan::SymmetricKey symkey1 = ka_timer->run([&]() { return ka1.derive_key(32, ka2_pub); }); + Botan::SymmetricKey symkey2 = ka_timer->run([&]() { return ka2.derive_key(32, ka1_pub); }); + + if(symkey1 != symkey2) + { + error_output() << "Key agreement mismatch in PK bench\n"; + } + } + + record_result(ka_timer); + } + + void bench_pk_kem(const Botan::Private_Key& key, + const std::string& nm, + const std::string& provider, + const std::string& kdf, + std::chrono::milliseconds msec) + { + Botan::PK_KEM_Decryptor dec(key, rng(), kdf, provider); + Botan::PK_KEM_Encryptor enc(key, rng(), kdf, provider); + + std::unique_ptr<Timer> kem_enc_timer = make_timer(nm, provider, "KEM encrypt"); + std::unique_ptr<Timer> 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); + kem_enc_timer->stop(); + + kem_dec_timer->start(); + Botan::secure_vector<uint8_t> dec_shared_key = dec.decrypt(encap_key, 64, salt); + kem_dec_timer->stop(); + + if(enc_shared_key != dec_shared_key) + { + error_output() << "KEM mismatch in PK bench\n"; + } + } + + record_result(kem_enc_timer); + record_result(kem_dec_timer); + } + + void bench_pk_sig_ecc(const std::string& algo, + const std::string& emsa, + const std::string& provider, + const std::vector<std::string>& params, + std::chrono::milliseconds msec) + { + for(std::string grp : params) + { + const std::string nm = grp.empty() ? algo : (algo + "-" + grp); + + std::unique_ptr<Timer> keygen_timer = make_timer(nm, provider, "keygen"); + + std::unique_ptr<Botan::Private_Key> key(keygen_timer->run([&] + { + return Botan::create_private_key(algo, rng(), grp); + })); + + record_result(keygen_timer); + bench_pk_sig(*key, nm, provider, emsa, msec); + } + } + + size_t bench_pk_sig(const Botan::Private_Key& key, + const std::string& nm, + const std::string& provider, + const std::string& padding, + std::chrono::milliseconds msec) + { + std::vector<uint8_t> message, signature, bad_signature; + + Botan::PK_Signer sig(key, rng(), padding, Botan::IEEE_1363, provider); + Botan::PK_Verifier ver(key, padding, Botan::IEEE_1363, provider); + + std::unique_ptr<Timer> sig_timer = make_timer(nm + " " + padding, provider, "sign"); + std::unique_ptr<Timer> ver_timer = make_timer(nm + " " + padding, provider, "verify"); + + size_t invalid_sigs = 0; + + while(ver_timer->under(msec) || sig_timer->under(msec)) + { + if(signature.empty() || sig_timer->under(msec)) + { + /* + Length here is kind of arbitrary, but 48 bytes fits into a single + hash block so minimizes hashing overhead versus the PK op itself. + */ + rng().random_vec(message, 48); + + signature = sig_timer->run([&]() { return sig.sign_message(message, rng()); }); + + bad_signature = signature; + bad_signature[rng().next_byte() % bad_signature.size()] ^= rng().next_nonzero_byte(); + } + + if(ver_timer->under(msec)) + { + const bool verified = ver_timer->run([&] + { + return ver.verify_message(message, signature); + }); + + if(!verified) + { + invalid_sigs += 1; + } + + const bool verified_bad = ver_timer->run([&] + { + return ver.verify_message(message, bad_signature); + }); + + if(verified_bad) + { + error_output() << "Bad signature accepted in PK signature bench\n"; + } + } + } + + if(invalid_sigs > 0) + error_output() << invalid_sigs << " generated signatures rejected in PK signature bench\n"; + + const size_t events = static_cast<size_t>(std::min(sig_timer->events(), ver_timer->events())); + + record_result(sig_timer); + record_result(ver_timer); + + return events; + } +#endif + +#if defined(BOTAN_HAS_RSA) + void bench_rsa_keygen(const std::string& provider, + std::chrono::milliseconds msec) + { + for(size_t keylen : { 1024, 2048, 3072, 4096 }) + { + const std::string nm = "RSA-" + std::to_string(keylen); + std::unique_ptr<Timer> keygen_timer = make_timer(nm, provider, "keygen"); + + while(keygen_timer->under(msec)) + { + std::unique_ptr<Botan::Private_Key> key(keygen_timer->run([&] { + return Botan::create_private_key("RSA", rng(), std::to_string(keylen)); + })); + + BOTAN_ASSERT(key->check_key(rng(), true), "Key is ok"); + } + + record_result(keygen_timer); + } + } + + void bench_rsa(const std::string& provider, + std::chrono::milliseconds msec) + { + for(size_t keylen : { 1024, 2048, 3072, 4096 }) + { + const std::string nm = "RSA-" + std::to_string(keylen); + + std::unique_ptr<Timer> keygen_timer = make_timer(nm, provider, "keygen"); + + std::unique_ptr<Botan::Private_Key> key(keygen_timer->run([&] + { + return Botan::create_private_key("RSA", rng(), std::to_string(keylen)); + })); + + record_result(keygen_timer); + + // Using PKCS #1 padding so OpenSSL provider can play along + bench_pk_sig(*key, nm, provider, "EMSA-PKCS1-v1_5(SHA-256)", msec); + + //bench_pk_sig(*key, nm, provider, "PSSR(SHA-256)", msec); + //bench_pk_enc(*key, nm, provider, "EME-PKCS1-v1_5", msec); + //bench_pk_enc(*key, nm, provider, "OAEP(SHA-1)", msec); + } + } +#endif + +#if defined(BOTAN_HAS_ECDSA) + void bench_ecdsa(const std::vector<std::string>& groups, + const std::string& provider, + std::chrono::milliseconds msec) + { + return bench_pk_sig_ecc("ECDSA", "EMSA1(SHA-256)", provider, groups, msec); + } + + void bench_ecdsa_recovery(const std::vector<std::string>& groups, + const std::string&, + std::chrono::milliseconds msec) + { + for(std::string group_name : groups) + { + Botan::EC_Group group(group_name); + std::unique_ptr<Timer> recovery_timer = make_timer("ECDSA recovery " + group_name); + + while(recovery_timer->under(msec)) + { + Botan::ECDSA_PrivateKey key(rng(), group); + + std::vector<uint8_t> message(group.get_order_bits() / 8); + rng().randomize(message.data(), message.size()); + + Botan::PK_Signer signer(key, rng(), "Raw"); + signer.update(message); + std::vector<uint8_t> signature = signer.signature(rng()); + + Botan::PK_Verifier verifier(key, "Raw", Botan::IEEE_1363, "base"); + verifier.update(message); + BOTAN_ASSERT(verifier.check_signature(signature), "Valid signature"); + + Botan::BigInt r(signature.data(), signature.size()/2); + Botan::BigInt s(signature.data() + signature.size()/2, signature.size()/2); + + const uint8_t v = key.recovery_param(message, r, s); + + recovery_timer->run([&]() { + Botan::ECDSA_PublicKey pubkey(group, message, r, s, v); + BOTAN_ASSERT(pubkey.public_point() == key.public_point(), "Recovered public key"); + }); + } + + record_result(recovery_timer); + } + + } + +#endif + +#if defined(BOTAN_HAS_ECKCDSA) + void bench_eckcdsa(const std::vector<std::string>& groups, + const std::string& provider, + std::chrono::milliseconds msec) + { + return bench_pk_sig_ecc("ECKCDSA", "EMSA1(SHA-256)", provider, groups, msec); + } +#endif + +#if defined(BOTAN_HAS_GOST_34_10_2001) + void bench_gost_3410(const std::string& provider, + std::chrono::milliseconds msec) + { + return bench_pk_sig_ecc("GOST-34.10", "EMSA1(GOST-34.11)", provider, {"gost_256A"}, msec); + } +#endif + +#if defined(BOTAN_HAS_SM2) + void bench_sm2(const std::vector<std::string>& groups, + const std::string& provider, + std::chrono::milliseconds msec) + { + return bench_pk_sig_ecc("SM2_Sig", "SM3", provider, groups, msec); + } +#endif + +#if defined(BOTAN_HAS_ECGDSA) + void bench_ecgdsa(const std::vector<std::string>& groups, + const std::string& provider, + std::chrono::milliseconds msec) + { + return bench_pk_sig_ecc("ECGDSA", "EMSA1(SHA-256)", provider, groups, msec); + } +#endif + +#if defined(BOTAN_HAS_ED25519) + void bench_ed25519(const std::string& provider, + std::chrono::milliseconds msec) + { + return bench_pk_sig_ecc("Ed25519", "Pure", provider, std::vector<std::string>{""}, msec); + } +#endif + +#if defined(BOTAN_HAS_DIFFIE_HELLMAN) + void bench_dh(const std::string& provider, + std::chrono::milliseconds msec) + { + for(size_t bits : { 1024, 1536, 2048, 3072, 4096, 6144, 8192 }) + { + bench_pk_ka("DH", + "DH-" + std::to_string(bits), + "modp/ietf/" + std::to_string(bits), + provider, msec); + } + } +#endif + +#if defined(BOTAN_HAS_DSA) + void bench_dsa(const std::string& provider, std::chrono::milliseconds msec) + { + for(size_t bits : { 1024, 2048, 3072 }) + { + const std::string nm = "DSA-" + std::to_string(bits); + + const std::string params = + (bits == 1024) ? "dsa/jce/1024" : ("dsa/botan/" + std::to_string(bits)); + + std::unique_ptr<Timer> keygen_timer = make_timer(nm, provider, "keygen"); + + std::unique_ptr<Botan::Private_Key> key(keygen_timer->run([&] + { + return Botan::create_private_key("DSA", rng(), params); + })); + + record_result(keygen_timer); + + bench_pk_sig(*key, nm, provider, "EMSA1(SHA-256)", msec); + } + } +#endif + +#if defined(BOTAN_HAS_ELGAMAL) + void bench_elgamal(const std::string& provider, std::chrono::milliseconds msec) + { + for(size_t keylen : { 1024, 2048, 3072, 4096 }) + { + const std::string nm = "ElGamal-" + std::to_string(keylen); + + const std::string params = "modp/ietf/" + std::to_string(keylen); + + std::unique_ptr<Timer> keygen_timer = make_timer(nm, provider, "keygen"); + + std::unique_ptr<Botan::Private_Key> key(keygen_timer->run([&] + { + return Botan::create_private_key("ElGamal", rng(), params); + })); + + record_result(keygen_timer); + + bench_pk_enc(*key, nm, provider, "EME-PKCS1-v1_5", msec); + } + } +#endif + +#if defined(BOTAN_HAS_ECDH) + void bench_ecdh(const std::vector<std::string>& groups, + const std::string& provider, + std::chrono::milliseconds msec) + { + for(std::string grp : groups) + { + bench_pk_ka("ECDH", "ECDH-" + grp, grp, provider, msec); + } + } +#endif + +#if defined(BOTAN_HAS_CURVE_25519) + void bench_curve25519(const std::string& provider, + std::chrono::milliseconds msec) + { + bench_pk_ka("Curve25519", "Curve25519", "", provider, msec); + } +#endif + +#if defined(BOTAN_HAS_MCELIECE) + void bench_mceliece(const std::string& provider, + std::chrono::milliseconds msec) + { + /* + SL=80 n=1632 t=33 - 59 KB pubkey 140 KB privkey + SL=107 n=2480 t=45 - 128 KB pubkey 300 KB privkey + SL=128 n=2960 t=57 - 195 KB pubkey 459 KB privkey + SL=147 n=3408 t=67 - 265 KB pubkey 622 KB privkey + SL=191 n=4624 t=95 - 516 KB pubkey 1234 KB privkey + SL=256 n=6624 t=115 - 942 KB pubkey 2184 KB privkey + */ + + const std::vector<std::pair<size_t, size_t>> mce_params = + { + { 2480, 45 }, + { 2960, 57 }, + { 3408, 67 }, + { 4624, 95 }, + { 6624, 115 } + }; + + for(auto params : mce_params) + { + size_t n = params.first; + size_t t = params.second; + + const std::string nm = "McEliece-" + std::to_string(n) + "," + std::to_string(t) + + " (WF=" + std::to_string(Botan::mceliece_work_factor(n, t)) + ")"; + + std::unique_ptr<Timer> keygen_timer = make_timer(nm, provider, "keygen"); + + std::unique_ptr<Botan::Private_Key> key(keygen_timer->run([&] + { + return new Botan::McEliece_PrivateKey(rng(), n, t); + })); + + record_result(keygen_timer); + bench_pk_kem(*key, nm, provider, "KDF2(SHA-256)", msec); + } + } +#endif + +#if defined(BOTAN_HAS_XMSS_RFC8391) + void bench_xmss(const std::string& provider, + std::chrono::milliseconds msec) + { + /* + We only test H10 signatures here since already they are quite slow (a + few seconds per signature). On a fast machine, H16 signatures take 1-2 + minutes to generate and H20 signatures take 5-10 minutes to generate + */ + std::vector<std::string> xmss_params + { + "XMSS-SHA2_10_256", + "XMSS-SHAKE_10_256", + "XMSS-SHA2_10_512", + "XMSS-SHAKE_10_512", + }; + + for(std::string params : xmss_params) + { + std::unique_ptr<Timer> keygen_timer = make_timer(params, provider, "keygen"); + + std::unique_ptr<Botan::Private_Key> key(keygen_timer->run([&] + { + return Botan::create_private_key("XMSS", rng(), params); + })); + + record_result(keygen_timer); + if(bench_pk_sig(*key, params, provider, "", msec) == 1) + break; + } + } +#endif + +#if defined(BOTAN_HAS_POLY_DBL) + void bench_poly_dbl(std::chrono::milliseconds msec) + { + for(size_t sz : { 8, 16, 24, 32, 64, 128 }) + { + std::unique_ptr<Timer> be_timer = make_timer("poly_dbl_be_" + std::to_string(sz)); + std::unique_ptr<Timer> le_timer = make_timer("poly_dbl_le_" + std::to_string(sz)); + + std::vector<uint8_t> buf(sz); + rng().randomize(buf.data(), sz); + + be_timer->run_until_elapsed(msec, [&]() { Botan::poly_double_n(buf.data(), buf.data(), sz); }); + le_timer->run_until_elapsed(msec, [&]() { Botan::poly_double_n_le(buf.data(), buf.data(), sz); }); + + record_result(be_timer); + record_result(le_timer); + } + } +#endif + +#if defined(BOTAN_HAS_BCRYPT) + + void bench_bcrypt() + { + const std::string password = "not a very good password"; + + for(uint8_t work_factor = 4; work_factor <= 14; ++work_factor) + { + std::unique_ptr<Timer> timer = make_timer("bcrypt wf=" + std::to_string(work_factor)); + + timer->run([&] { + Botan::generate_bcrypt(password, rng(), work_factor); + }); + + record_result(timer); + } + } +#endif + +#if defined(BOTAN_HAS_PASSHASH9) + + void bench_passhash9() + { + const std::string password = "not a very good password"; + + for(uint8_t alg = 0; alg <= 4; ++alg) + { + if(Botan::is_passhash9_alg_supported(alg) == false) + continue; + + for(auto work_factor : { 10, 15 }) + { + std::unique_ptr<Timer> timer = make_timer("passhash9 alg=" + std::to_string(alg) + + " wf=" + std::to_string(work_factor)); + + timer->run([&] { + Botan::generate_passhash9(password, rng(), static_cast<uint8_t>(work_factor), alg); + }); + + record_result(timer); + } + } + } +#endif + +#if defined(BOTAN_HAS_SCRYPT) + + void bench_scrypt(const std::string& /*provider*/, + std::chrono::milliseconds msec) + { + + for(size_t N : { 8192, 16384, 32768, 65536 }) + { + for(size_t r : { 1, 8, 16 }) + { + for(size_t p : { 1, 4 }) + { + std::unique_ptr<Timer> scrypt_timer = make_timer( + "scrypt-" + std::to_string(N) + "-" + + std::to_string(r) + "-" + std::to_string(p) + + " (" + std::to_string(Botan::scrypt_memory_usage(N, r, p) / (1024*1024)) + " MiB)"); + + uint8_t out[64]; + uint8_t salt[8]; + rng().randomize(salt, sizeof(salt)); + + while(scrypt_timer->under(msec)) + { + scrypt_timer->run([&] { + Botan::scrypt(out, sizeof(out), "password", + salt, sizeof(salt), N, r, p); + }); + } + + record_result(scrypt_timer); + + if(scrypt_timer->events() == 1) + break; + } + } + } + + } + +#endif + +#if defined(BOTAN_HAS_ARGON2) + + void bench_argon2(const std::string& /*provider*/, + std::chrono::milliseconds msec) + { + const uint8_t mode = 2; // Argon2id + + for(size_t M : { 8*1024, 64*1024, 256*1024 }) + { + for(size_t t : { 1, 2, 4 }) + { + for(size_t p : { 1 }) + { + std::unique_ptr<Timer> timer = make_timer( + "Argon2id M=" + std::to_string(M) + " t=" + std::to_string(t) + " p=" + std::to_string(p)); + + uint8_t out[64]; + uint8_t salt[16]; + rng().randomize(salt, sizeof(salt)); + + while(timer->under(msec)) + { + timer->run([&] { + Botan::argon2(out, sizeof(out), "password", 8, + salt, sizeof(salt), nullptr, 0, nullptr, 0, + mode, p, M, t); + }); + } + + record_result(timer); + } + } + } + } + +#endif + +#if defined(BOTAN_HAS_NEWHOPE) && defined(BOTAN_HAS_CHACHA_RNG) + void bench_newhope(const std::string& /*provider*/, + std::chrono::milliseconds msec) + { + const std::string nm = "NEWHOPE"; + + std::unique_ptr<Timer> keygen_timer = make_timer(nm, "", "keygen"); + std::unique_ptr<Timer> shareda_timer = make_timer(nm, "", "shareda"); + std::unique_ptr<Timer> sharedb_timer = make_timer(nm, "", "sharedb"); + + Botan::ChaCha_RNG nh_rng(Botan::secure_vector<uint8_t>(32)); + + while(sharedb_timer->under(msec)) + { + std::vector<uint8_t> send_a(Botan::NEWHOPE_SENDABYTES), send_b(Botan::NEWHOPE_SENDBBYTES); + std::vector<uint8_t> shared_a(32), shared_b(32); + + Botan::newhope_poly sk_a; + + keygen_timer->start(); + Botan::newhope_keygen(send_a.data(), &sk_a, nh_rng); + keygen_timer->stop(); + + sharedb_timer->start(); + Botan::newhope_sharedb(shared_b.data(), send_b.data(), send_a.data(), nh_rng); + sharedb_timer->stop(); + + shareda_timer->start(); + Botan::newhope_shareda(shared_a.data(), &sk_a, send_b.data()); + shareda_timer->stop(); + + BOTAN_ASSERT(shared_a == shared_b, "Same derived key"); + } + + record_result(keygen_timer); + record_result(shareda_timer); + record_result(sharedb_timer); + } +#endif + + }; + +BOTAN_REGISTER_COMMAND("speed", Speed); + +} |