aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/ffi/ffi.h57
-rw-r--r--src/lib/ffi/ffi_kdf.cpp94
-rw-r--r--src/lib/pbkdf/info.txt3
-rw-r--r--src/lib/pbkdf/pbkdf.h3
-rw-r--r--src/lib/pbkdf/pbkdf2/info.txt2
-rw-r--r--src/lib/pbkdf/pbkdf2/pbkdf2.cpp224
-rw-r--r--src/lib/pbkdf/pbkdf2/pbkdf2.h78
-rw-r--r--src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp212
-rw-r--r--src/lib/pbkdf/pgp_s2k/pgp_s2k.h90
-rw-r--r--src/lib/pbkdf/pwdhash.cpp88
-rw-r--r--src/lib/pbkdf/pwdhash.h162
-rw-r--r--src/lib/pbkdf/scrypt/info.txt2
-rw-r--r--src/lib/pbkdf/scrypt/scrypt.cpp258
-rw-r--r--src/lib/pbkdf/scrypt/scrypt.h98
-rw-r--r--src/lib/pubkey/pbes2/pbes2.cpp64
-rw-r--r--src/lib/utils/info.txt3
-rw-r--r--src/lib/utils/timer.cpp118
-rw-r--r--src/lib/utils/timer.h216
18 files changed, 1492 insertions, 280 deletions
diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h
index 57631bc28..ff7086488 100644
--- a/src/lib/ffi/ffi.h
+++ b/src/lib/ffi/ffi.h
@@ -514,7 +514,7 @@ BOTAN_PUBLIC_API(2,0) int botan_cipher_destroy(botan_cipher_t cipher);
/*
* Derive a key from a passphrase for a number of iterations
-* @param pbkdf_algo PBKDF algorithm, e.g., "PBKDF2"
+* @param pbkdf_algo PBKDF algorithm, e.g., "PBKDF2(SHA-256)"
* @param out buffer to store the derived key, must be of out_len bytes
* @param out_len the desired length of the key to produce
* @param passphrase the password to derive the key from
@@ -531,7 +531,7 @@ BOTAN_PUBLIC_API(2,0) int botan_pbkdf(const char* pbkdf_algo,
/**
* Derive a key from a passphrase, running until msec time has elapsed.
-* @param pbkdf_algo PBKDF algorithm, e.g., "PBKDF2"
+* @param pbkdf_algo PBKDF algorithm, e.g., "PBKDF2(SHA-256)"
* @param out buffer to store the derived key, must be of out_len bytes
* @param out_len the desired length of the key to produce
* @param passphrase the password to derive the key from
@@ -550,6 +550,58 @@ BOTAN_PUBLIC_API(2,0) int botan_pbkdf_timed(const char* pbkdf_algo,
size_t* out_iterations_used);
+/*
+* Derive a key from a passphrase
+* @param algo PBKDF algorithm, e.g., "PBKDF2(SHA-256)" or "Scrypt"
+* @param param1 the first PBKDF algorithm parameter
+* @param param2 the second PBKDF algorithm parameter (may be zero if unneeded)
+* @param param3 the third PBKDF algorithm parameter (may be zero if unneeded)
+* @param out buffer to store the derived key, must be of out_len bytes
+* @param out_len the desired length of the key to produce
+* @param passphrase the password to derive the key from
+* @param salt a randomly chosen salt
+* @param salt_len length of salt in bytes
+* @return 0 on success, a negative value on failure
+*/
+int BOTAN_PUBLIC_API(2,8) botan_pwdhash(
+ const char* algo,
+ size_t param1,
+ size_t param2,
+ size_t param3,
+ uint8_t out[],
+ size_t out_len,
+ const char* passphrase,
+ size_t passphrase_len,
+ const uint8_t salt[],
+ size_t salt_len);
+
+/*
+* Derive a key from a passphrase
+* @param pbkdf_algo PBKDF algorithm, e.g., "Scrypt" or "PBKDF2(SHA-256)"
+* @param msec the desired runtime in milliseconds
+* @param param1 will be set to the first password hash parameter
+* @param param2 will be set to the second password hash parameter
+* @param param3 will be set to the third password hash parameter
+* @param out buffer to store the derived key, must be of out_len bytes
+* @param out_len the desired length of the key to produce
+* @param passphrase the password to derive the key from
+* @param salt a randomly chosen salt
+* @param salt_len length of salt in bytes
+* @return 0 on success, a negative value on failure
+*/
+int BOTAN_PUBLIC_API(2,8) botan_pwdhash_timed(
+ const char* algo,
+ uint32_t msec,
+ size_t* param1,
+ size_t* param2,
+ size_t* param3,
+ uint8_t out[],
+ size_t out_len,
+ const char* passphrase,
+ size_t passphrase_len,
+ const uint8_t salt[],
+ size_t salt_len);
+
/**
* Derive a key using scrypt
*/
@@ -557,6 +609,7 @@ BOTAN_PUBLIC_API(2,8) int botan_scrypt(uint8_t out[], size_t out_len,
const char* passphrase,
const uint8_t salt[], size_t salt_len,
size_t N, size_t r, size_t p);
+
/**
* Derive a key
* @param kdf_algo KDF algorithm, e.g., "SP800-56C"
diff --git a/src/lib/ffi/ffi_kdf.cpp b/src/lib/ffi/ffi_kdf.cpp
index c63406625..b72fe935e 100644
--- a/src/lib/ffi/ffi_kdf.cpp
+++ b/src/lib/ffi/ffi_kdf.cpp
@@ -22,29 +22,97 @@ extern "C" {
using namespace Botan_FFI;
-int botan_pbkdf(const char* pbkdf_algo, uint8_t out[], size_t out_len,
+int botan_pbkdf(const char* algo, uint8_t out[], size_t out_len,
const char* pass, const uint8_t salt[], size_t salt_len,
size_t iterations)
{
- return ffi_guard_thunk(BOTAN_CURRENT_FUNCTION, [=]() -> int {
- std::unique_ptr<Botan::PBKDF> pbkdf(Botan::get_pbkdf(pbkdf_algo));
- pbkdf->pbkdf_iterations(out, out_len, pass, salt, salt_len, iterations);
- return BOTAN_FFI_SUCCESS;
- });
+ return botan_pwdhash(algo,
+ iterations,
+ 0,
+ 0,
+ out, out_len,
+ pass, std::strlen(pass),
+ salt, salt_len);
}
-int botan_pbkdf_timed(const char* pbkdf_algo,
+int botan_pbkdf_timed(const char* algo,
uint8_t out[], size_t out_len,
const char* password,
const uint8_t salt[], size_t salt_len,
size_t ms_to_run,
size_t* iterations_used)
{
+ return botan_pwdhash_timed(algo,
+ static_cast<uint32_t>(ms_to_run),
+ iterations_used,
+ nullptr,
+ nullptr,
+ out, out_len,
+ password, std::strlen(password),
+ salt, salt_len);
+ }
+
+int botan_pwdhash(
+ const char* algo,
+ size_t param1,
+ size_t param2,
+ size_t param3,
+ uint8_t out[],
+ size_t out_len,
+ const char* password,
+ size_t password_len,
+ const uint8_t salt[],
+ size_t salt_len)
+ {
+ return ffi_guard_thunk(BOTAN_CURRENT_FUNCTION, [=]() -> int {
+ auto pwdhash_fam = Botan::PasswordHashFamily::create(algo);
+
+ if(!pwdhash_fam)
+ return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
+
+ auto pwdhash = pwdhash_fam->from_params(param1, param2, param3);
+
+ pwdhash->derive_key(out, out_len,
+ password, password_len,
+ salt, salt_len);
+
+ return BOTAN_FFI_SUCCESS;
+ });
+ }
+
+int botan_pwdhash_timed(
+ const char* algo,
+ uint32_t msec,
+ size_t* param1,
+ size_t* param2,
+ size_t* param3,
+ uint8_t out[],
+ size_t out_len,
+ const char* password,
+ size_t password_len,
+ const uint8_t salt[],
+ size_t salt_len)
+ {
return ffi_guard_thunk(BOTAN_CURRENT_FUNCTION, [=]() -> int {
- std::unique_ptr<Botan::PBKDF> pbkdf(Botan::get_pbkdf(pbkdf_algo));
- pbkdf->pbkdf_timed(out, out_len, password, salt, salt_len,
- std::chrono::milliseconds(ms_to_run),
- *iterations_used);
+
+ auto pwdhash_fam = Botan::PasswordHashFamily::create(algo);
+
+ if(!pwdhash_fam)
+ return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
+
+ auto pwdhash = pwdhash_fam->tune(out_len, std::chrono::milliseconds(msec));
+
+ if(param1)
+ *param1 = pwdhash->iterations();
+ if(param2)
+ *param2 = pwdhash->parallelism();
+ if(param3)
+ *param3 = pwdhash->memory_param();
+
+ pwdhash->derive_key(out, out_len,
+ password, password_len,
+ salt, salt_len);
+
return BOTAN_FFI_SUCCESS;
});
}
@@ -63,13 +131,13 @@ int botan_kdf(const char* kdf_algo,
}
int botan_scrypt(uint8_t out[], size_t out_len,
- const char* passphrase,
+ const char* password,
const uint8_t salt[], size_t salt_len,
size_t N, size_t r, size_t p)
{
#if defined(BOTAN_HAS_SCRYPT)
return ffi_guard_thunk(BOTAN_CURRENT_FUNCTION, [=]() -> int {
- Botan::scrypt(out, out_len, passphrase, salt, salt_len, N, r, p);
+ Botan::scrypt(out, out_len, password, strlen(password), salt, salt_len, N, r, p);
return BOTAN_FFI_SUCCESS;
});
#else
diff --git a/src/lib/pbkdf/info.txt b/src/lib/pbkdf/info.txt
index 48c6b56e6..650414f41 100644
--- a/src/lib/pbkdf/info.txt
+++ b/src/lib/pbkdf/info.txt
@@ -1,5 +1,5 @@
<defines>
-PBKDF -> 20150626
+PBKDF -> 20180902
</defines>
<requires>
@@ -8,5 +8,6 @@ hash
</requires>
<header:public>
+pwdhash.h
pbkdf.h
</header:public>
diff --git a/src/lib/pbkdf/pbkdf.h b/src/lib/pbkdf/pbkdf.h
index 7d3bceffc..9692b2546 100644
--- a/src/lib/pbkdf/pbkdf.h
+++ b/src/lib/pbkdf/pbkdf.h
@@ -17,6 +17,9 @@ namespace Botan {
* Base class for PBKDF (password based key derivation function)
* implementations. Converts a password into a key using a salt
* and iterated hashing to make brute force attacks harder.
+*
+* Starting in 2.8 this functionality is also offered by PasswordHash.
+* The PBKDF interface may be removed in a future release.
*/
class BOTAN_PUBLIC_API(2,0) PBKDF
{
diff --git a/src/lib/pbkdf/pbkdf2/info.txt b/src/lib/pbkdf/pbkdf2/info.txt
index bc5c2e491..b6c6fb4e6 100644
--- a/src/lib/pbkdf/pbkdf2/info.txt
+++ b/src/lib/pbkdf/pbkdf2/info.txt
@@ -1,5 +1,5 @@
<defines>
-PBKDF2 -> 20131128
+PBKDF2 -> 20180902
</defines>
<requires>
diff --git a/src/lib/pbkdf/pbkdf2/pbkdf2.cpp b/src/lib/pbkdf/pbkdf2/pbkdf2.cpp
index cc2982f6e..65e8a9503 100644
--- a/src/lib/pbkdf/pbkdf2/pbkdf2.cpp
+++ b/src/lib/pbkdf/pbkdf2/pbkdf2.cpp
@@ -1,6 +1,7 @@
/*
* PBKDF2
* (C) 1999-2007 Jack Lloyd
+* (C) 2018 Ribose Inc
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
@@ -8,41 +9,115 @@
#include <botan/pbkdf2.h>
#include <botan/exceptn.h>
#include <botan/internal/rounding.h>
+#include <botan/internal/timer.h>
namespace Botan {
+namespace {
+
+void pbkdf2_set_key(MessageAuthenticationCode& prf,
+ const char* password,
+ size_t password_len)
+ {
+ try
+ {
+ prf.set_key(cast_char_ptr_to_uint8(password), password_len);
+ }
+ catch(Invalid_Key_Length&)
+ {
+ throw Exception("PBKDF2 cannot accept passphrase of the given size");
+ }
+ }
+
+}
+
size_t
pbkdf2(MessageAuthenticationCode& prf,
uint8_t out[],
size_t out_len,
- const std::string& passphrase,
+ const std::string& password,
const uint8_t salt[], size_t salt_len,
size_t iterations,
std::chrono::milliseconds msec)
{
- clear_mem(out, out_len);
-
- if(out_len == 0)
- return 0;
-
- try
+ if(iterations == 0)
{
- prf.set_key(cast_char_ptr_to_uint8(passphrase.data()), passphrase.size());
- }
- catch(Invalid_Key_Length&)
- {
- throw Exception("PBKDF2 with " + prf.name() +
- " cannot accept passphrases of length " +
- std::to_string(passphrase.size()));
+ iterations = PBKDF2(prf, out_len, msec).iterations();
}
+ PBKDF2 pbkdf2(prf, iterations);
+
+ pbkdf2.derive_key(out, out_len,
+ password.c_str(), password.size(),
+ salt, salt_len);
+
+ return iterations;
+ }
+
+namespace {
+
+size_t tune_pbkdf2(MessageAuthenticationCode& prf,
+ size_t output_length,
+ uint32_t msec)
+ {
const size_t prf_sz = prf.output_length();
+ BOTAN_ASSERT_NOMSG(prf_sz > 0);
secure_vector<uint8_t> U(prf_sz);
- const size_t blocks_needed = round_up(out_len, prf_sz) / prf_sz;
+ const size_t trial_iterations = 10000;
+
+ // Short output ensures we only need a single PBKDF2 block
+
+ Timer timer("PBKDF2");
+
+ const std::chrono::milliseconds tune_msec(30);
+
+ prf.set_key(nullptr, 0);
+
+ timer.run_until_elapsed(tune_msec, [&]() {
+ uint8_t out[16] = { 0 };
+ uint8_t salt[16] = { 0 };
+ pbkdf2(prf, out, sizeof(out), salt, sizeof(salt), trial_iterations);
+ });
+
+ if(timer.events() == 0)
+ return trial_iterations;
+
+ const uint64_t duration_nsec = timer.value() / timer.events();
+
+ const uint64_t desired_nsec = static_cast<uint64_t>(msec) * 1000000;
+
+ if(duration_nsec > desired_nsec)
+ return trial_iterations;
+
+ const size_t blocks_needed = (output_length + prf_sz - 1) / prf_sz;
+
+ const size_t multiplier = (desired_nsec / duration_nsec / blocks_needed);
+
+ if(multiplier == 0)
+ return trial_iterations;
+ else
+ return trial_iterations * multiplier;
+ }
+
+}
+
+void pbkdf2(MessageAuthenticationCode& prf,
+ uint8_t out[],
+ size_t out_len,
+ const uint8_t salt[],
+ size_t salt_len,
+ size_t iterations)
+ {
+ clear_mem(out, out_len);
+
+ if(out_len == 0)
+ return;
+
+ const size_t prf_sz = prf.output_length();
+ BOTAN_ASSERT_NOMSG(prf_sz > 0);
- std::chrono::microseconds usec_per_block =
- std::chrono::duration_cast<std::chrono::microseconds>(msec) / blocks_needed;
+ secure_vector<uint8_t> U(prf_sz);
uint32_t counter = 1;
while(out_len)
@@ -55,64 +130,93 @@ pbkdf2(MessageAuthenticationCode& prf,
xor_buf(out, U.data(), prf_output);
- if(iterations == 0)
+ for(size_t i = 1; i != iterations; ++i)
{
- /*
- If no iterations set, run the first block to calibrate based
- on how long hashing takes on whatever machine we're running on.
- */
-
- const auto start = std::chrono::high_resolution_clock::now();
-
- iterations = 1; // the first iteration we did above
-
- while(true)
- {
- prf.update(U);
- prf.final(U.data());
- xor_buf(out, U.data(), prf_output);
- iterations++;
-
- /*
- Only break on relatively 'even' iterations. For one it
- avoids confusion, and likely some broken implementations
- break on getting completely randomly distributed values
- */
- if(iterations % 10000 == 0)
- {
- auto time_taken = std::chrono::high_resolution_clock::now() - start;
- auto usec_taken = std::chrono::duration_cast<std::chrono::microseconds>(time_taken);
- if(usec_taken > usec_per_block)
- break;
- }
- }
- }
- else
- {
- for(size_t i = 1; i != iterations; ++i)
- {
- prf.update(U);
- prf.final(U.data());
- xor_buf(out, U.data(), prf_output);
- }
+ prf.update(U);
+ prf.final(U.data());
+ xor_buf(out, U.data(), prf_output);
}
out_len -= prf_output;
out += prf_output;
}
-
- return iterations;
}
+// PBKDF interface
size_t
PKCS5_PBKDF2::pbkdf(uint8_t key[], size_t key_len,
- const std::string& passphrase,
+ const std::string& password,
const uint8_t salt[], size_t salt_len,
size_t iterations,
std::chrono::milliseconds msec) const
{
- return pbkdf2(*m_mac.get(), key, key_len, passphrase, salt, salt_len, iterations, msec);
+ if(iterations == 0)
+ {
+ iterations = PBKDF2(*m_mac, key_len, msec).iterations();
+ }
+
+ PBKDF2 pbkdf2(*m_mac, iterations);
+
+ pbkdf2.derive_key(key, key_len,
+ password.c_str(), password.size(),
+ salt, salt_len);
+
+ return iterations;
+ }
+
+std::string PKCS5_PBKDF2::name() const
+ {
+ return "PBKDF2(" + m_mac->name() + ")";
+ }
+
+PBKDF* PKCS5_PBKDF2::clone() const
+ {
+ return new PKCS5_PBKDF2(m_mac->clone());
}
+// PasswordHash interface
+
+PBKDF2::PBKDF2(const MessageAuthenticationCode& prf, size_t olen, std::chrono::milliseconds msec) :
+ m_prf(prf.clone()),
+ m_iterations(tune_pbkdf2(*m_prf, olen, msec.count()))
+ {}
+
+std::string PBKDF2::to_string() const
+ {
+ return "PBKDF2(" + m_prf->name() + "," + std::to_string(m_iterations) + ")";
+ }
+
+void PBKDF2::derive_key(uint8_t out[], size_t out_len,
+ const char* password, const size_t password_len,
+ const uint8_t salt[], size_t salt_len) const
+ {
+ pbkdf2_set_key(*m_prf, password, password_len);
+ pbkdf2(*m_prf, out, out_len, salt, salt_len, m_iterations);
+ }
+
+std::string PBKDF2_Family::name() const
+ {
+ return "PBKDF2(" + m_prf->name() + ")";
+ }
+
+std::unique_ptr<PasswordHash> PBKDF2_Family::tune(size_t output_len, std::chrono::milliseconds msec, size_t) const
+ {
+ return std::unique_ptr<PasswordHash>(new PBKDF2(*m_prf, output_len, msec));
+ }
+
+std::unique_ptr<PasswordHash> PBKDF2_Family::default_params() const
+ {
+ return std::unique_ptr<PasswordHash>(new PBKDF2(*m_prf, 150000));
+ }
+
+std::unique_ptr<PasswordHash> PBKDF2_Family::from_params(size_t iter, size_t, size_t) const
+ {
+ return std::unique_ptr<PasswordHash>(new PBKDF2(*m_prf, iter));
+ }
+
+std::unique_ptr<PasswordHash> PBKDF2_Family::from_iterations(size_t iter) const
+ {
+ return std::unique_ptr<PasswordHash>(new PBKDF2(*m_prf, iter));
+ }
}
diff --git a/src/lib/pbkdf/pbkdf2/pbkdf2.h b/src/lib/pbkdf/pbkdf2/pbkdf2.h
index ea357cac0..72637bc30 100644
--- a/src/lib/pbkdf/pbkdf2/pbkdf2.h
+++ b/src/lib/pbkdf/pbkdf2/pbkdf2.h
@@ -1,6 +1,7 @@
/*
* PBKDF2
* (C) 1999-2007,2012 Jack Lloyd
+* (C) 2018 Ribose Inc
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
@@ -9,6 +10,7 @@
#define BOTAN_PBKDF2_H_
#include <botan/pbkdf.h>
+#include <botan/pwdhash.h>
#include <botan/mac.h>
namespace Botan {
@@ -22,20 +24,76 @@ BOTAN_PUBLIC_API(2,0) size_t pbkdf2(MessageAuthenticationCode& prf,
std::chrono::milliseconds msec);
/**
-* PKCS #5 PBKDF2
+* Perform PBKDF2. The prf is assumed to be keyed already.
+*/
+BOTAN_PUBLIC_API(2,8) void pbkdf2(MessageAuthenticationCode& prf,
+ uint8_t out[], size_t out_len,
+ const uint8_t salt[], size_t salt_len,
+ size_t iterations);
+
+/**
+* PBKDF2
+*/
+class BOTAN_PUBLIC_API(2,8) PBKDF2 final : public PasswordHash
+ {
+ public:
+ PBKDF2(const MessageAuthenticationCode& prf, size_t iter) :
+ m_prf(prf.clone()),
+ m_iterations(iter)
+ {}
+
+ PBKDF2(const MessageAuthenticationCode& prf, size_t olen, std::chrono::milliseconds msec);
+
+ size_t iterations() const override { return m_iterations; }
+
+ std::string to_string() const override;
+
+ void derive_key(uint8_t out[], size_t out_len,
+ const char* password, const size_t password_len,
+ const uint8_t salt[], size_t salt_len) const override;
+ private:
+ std::unique_ptr<MessageAuthenticationCode> m_prf;
+ size_t m_iterations;
+ };
+
+/**
+* Family of PKCS #5 PBKDF2 operations
+*/
+class BOTAN_PUBLIC_API(2,8) PBKDF2_Family final : public PasswordHashFamily
+ {
+ public:
+ PBKDF2_Family(MessageAuthenticationCode* prf) : m_prf(prf) {}
+
+ std::string name() const override;
+
+ std::unique_ptr<PasswordHash> tune(size_t output_len,
+ std::chrono::milliseconds msec,
+ size_t max_memory) const override;
+
+ /**
+ * Return some default parameter set for this PBKDF that should be good
+ * enough for most users. The value returned may change over time as
+ * processing power and attacks improve.
+ */
+ std::unique_ptr<PasswordHash> default_params() const override;
+
+ std::unique_ptr<PasswordHash> from_iterations(size_t iter) const override;
+
+ std::unique_ptr<PasswordHash> from_params(
+ size_t iter, size_t, size_t) const override;
+ private:
+ std::unique_ptr<MessageAuthenticationCode> m_prf;
+ };
+
+/**
+* PKCS #5 PBKDF2 (old interface)
*/
class BOTAN_PUBLIC_API(2,0) PKCS5_PBKDF2 final : public PBKDF
{
public:
- std::string name() const override
- {
- return "PBKDF2(" + m_mac->name() + ")";
- }
-
- PBKDF* clone() const override
- {
- return new PKCS5_PBKDF2(m_mac->clone());
- }
+ std::string name() const override;
+
+ PBKDF* clone() const override;
size_t pbkdf(uint8_t output_buf[], size_t output_len,
const std::string& passphrase,
diff --git a/src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp b/src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp
index df659de3c..3f2c564c8 100644
--- a/src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp
+++ b/src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp
@@ -1,64 +1,26 @@
/*
* OpenPGP S2K
* (C) 1999-2007,2017 Jack Lloyd
+* (C) 2018 Ribose Inc
*
* Distributed under the terms of the Botan license
*/
#include <botan/pgp_s2k.h>
#include <botan/exceptn.h>
+#include <botan/internal/timer.h>
namespace Botan {
-/*
-PGP stores the iteration count as a single byte
-Thus it can only actually take on one of 256 values, based on the
-formula in RFC 4880 section 3.6.1.3
-*/
-static const uint32_t OPENPGP_S2K_ITERS[256] = {
- 1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600,
- 1664, 1728, 1792, 1856, 1920, 1984, 2048, 2176, 2304, 2432,
- 2560, 2688, 2816, 2944, 3072, 3200, 3328, 3456, 3584, 3712,
- 3840, 3968, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888,
- 6144, 6400, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8704,
- 9216, 9728, 10240, 10752, 11264, 11776, 12288, 12800, 13312,
- 13824, 14336, 14848, 15360, 15872, 16384, 17408, 18432, 19456,
- 20480, 21504, 22528, 23552, 24576, 25600, 26624, 27648, 28672,
- 29696, 30720, 31744, 32768, 34816, 36864, 38912, 40960, 43008,
- 45056, 47104, 49152, 51200, 53248, 55296, 57344, 59392, 61440,
- 63488, 65536, 69632, 73728, 77824, 81920, 86016, 90112, 94208,
- 98304, 102400, 106496, 110592, 114688, 118784, 122880, 126976,
- 131072, 139264, 147456, 155648, 163840, 172032, 180224, 188416,
- 196608, 204800, 212992, 221184, 229376, 237568, 245760, 253952,
- 262144, 278528, 294912, 311296, 327680, 344064, 360448, 376832,
- 393216, 409600, 425984, 442368, 458752, 475136, 491520, 507904,
- 524288, 557056, 589824, 622592, 655360, 688128, 720896, 753664,
- 786432, 819200, 851968, 884736, 917504, 950272, 983040, 1015808,
- 1048576, 1114112, 1179648, 1245184, 1310720, 1376256, 1441792,
- 1507328, 1572864, 1638400, 1703936, 1769472, 1835008, 1900544,
- 1966080, 2031616, 2097152, 2228224, 2359296, 2490368, 2621440,
- 2752512, 2883584, 3014656, 3145728, 3276800, 3407872, 3538944,
- 3670016, 3801088, 3932160, 4063232, 4194304, 4456448, 4718592,
- 4980736, 5242880, 5505024, 5767168, 6029312, 6291456, 6553600,
- 6815744, 7077888, 7340032, 7602176, 7864320, 8126464, 8388608,
- 8912896, 9437184, 9961472, 10485760, 11010048, 11534336,
- 12058624, 12582912, 13107200, 13631488, 14155776, 14680064,
- 15204352, 15728640, 16252928, 16777216, 17825792, 18874368,
- 19922944, 20971520, 22020096, 23068672, 24117248, 25165824,
- 26214400, 27262976, 28311552, 29360128, 30408704, 31457280,
- 32505856, 33554432, 35651584, 37748736, 39845888, 41943040,
- 44040192, 46137344, 48234496, 50331648, 52428800, 54525952,
- 56623104, 58720256, 60817408, 62914560, 65011712 };
-
-//static
-uint8_t OpenPGP_S2K::encode_count(size_t desired_iterations)
+uint8_t RFC4880_encode_count(size_t desired_iterations)
{
/*
Only 256 different iterations are actually representable in OpenPGP format ...
*/
for(size_t c = 0; c < 256; ++c)
{
- const uint32_t decoded_iter = OPENPGP_S2K_ITERS[c];
+ // TODO could binary search
+ const uint32_t decoded_iter = RFC4880_decode_count(c);
if(decoded_iter >= desired_iterations)
return static_cast<uint8_t>(c);
}
@@ -66,37 +28,75 @@ uint8_t OpenPGP_S2K::encode_count(size_t desired_iterations)
return 255;
}
-//static
-size_t OpenPGP_S2K::decode_count(uint8_t iter)
+size_t RFC4880_decode_count(uint8_t iter)
{
+ /*
+ PGP stores the iteration count as a single byte
+ Thus it can only actually take on one of 256 values, based on the
+ formula in RFC 4880 section 3.6.1.3
+ */
+ static const uint32_t OPENPGP_S2K_ITERS[256] = {
+ 1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600,
+ 1664, 1728, 1792, 1856, 1920, 1984, 2048, 2176, 2304, 2432,
+ 2560, 2688, 2816, 2944, 3072, 3200, 3328, 3456, 3584, 3712,
+ 3840, 3968, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888,
+ 6144, 6400, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8704,
+ 9216, 9728, 10240, 10752, 11264, 11776, 12288, 12800, 13312,
+ 13824, 14336, 14848, 15360, 15872, 16384, 17408, 18432, 19456,
+ 20480, 21504, 22528, 23552, 24576, 25600, 26624, 27648, 28672,
+ 29696, 30720, 31744, 32768, 34816, 36864, 38912, 40960, 43008,
+ 45056, 47104, 49152, 51200, 53248, 55296, 57344, 59392, 61440,
+ 63488, 65536, 69632, 73728, 77824, 81920, 86016, 90112, 94208,
+ 98304, 102400, 106496, 110592, 114688, 118784, 122880, 126976,
+ 131072, 139264, 147456, 155648, 163840, 172032, 180224, 188416,
+ 196608, 204800, 212992, 221184, 229376, 237568, 245760, 253952,
+ 262144, 278528, 294912, 311296, 327680, 344064, 360448, 376832,
+ 393216, 409600, 425984, 442368, 458752, 475136, 491520, 507904,
+ 524288, 557056, 589824, 622592, 655360, 688128, 720896, 753664,
+ 786432, 819200, 851968, 884736, 917504, 950272, 983040, 1015808,
+ 1048576, 1114112, 1179648, 1245184, 1310720, 1376256, 1441792,
+ 1507328, 1572864, 1638400, 1703936, 1769472, 1835008, 1900544,
+ 1966080, 2031616, 2097152, 2228224, 2359296, 2490368, 2621440,
+ 2752512, 2883584, 3014656, 3145728, 3276800, 3407872, 3538944,
+ 3670016, 3801088, 3932160, 4063232, 4194304, 4456448, 4718592,
+ 4980736, 5242880, 5505024, 5767168, 6029312, 6291456, 6553600,
+ 6815744, 7077888, 7340032, 7602176, 7864320, 8126464, 8388608,
+ 8912896, 9437184, 9961472, 10485760, 11010048, 11534336,
+ 12058624, 12582912, 13107200, 13631488, 14155776, 14680064,
+ 15204352, 15728640, 16252928, 16777216, 17825792, 18874368,
+ 19922944, 20971520, 22020096, 23068672, 24117248, 25165824,
+ 26214400, 27262976, 28311552, 29360128, 30408704, 31457280,
+ 32505856, 33554432, 35651584, 37748736, 39845888, 41943040,
+ 44040192, 46137344, 48234496, 50331648, 52428800, 54525952,
+ 56623104, 58720256, 60817408, 62914560, 65011712 };
+
return OPENPGP_S2K_ITERS[iter];
}
-size_t OpenPGP_S2K::pbkdf(uint8_t output_buf[], size_t output_len,
- const std::string& passphrase,
- const uint8_t salt[], size_t salt_len,
- size_t iterations,
- std::chrono::milliseconds msec) const
- {
- if(iterations == 0 && msec.count() > 0) // FIXME
- throw Not_Implemented("OpenPGP_S2K does not implemented timed KDF");
+namespace {
+void pgp_s2k(HashFunction& hash,
+ uint8_t output_buf[], size_t output_len,
+ const char* password, const size_t password_size,
+ const uint8_t salt[], size_t salt_len,
+ size_t iterations)
+ {
if(iterations > 1 && salt_len == 0)
- throw Invalid_Argument("OpenPGP_S2K requires a salt in iterated mode");
+ throw Invalid_Argument("OpenPGP S2K requires a salt in iterated mode");
- secure_vector<uint8_t> input_buf(salt_len + passphrase.size());
+ secure_vector<uint8_t> input_buf(salt_len + password_size);
if(salt_len > 0)
{
copy_mem(&input_buf[0], salt, salt_len);
}
- if(passphrase.empty() == false)
+ if(password_size > 0)
{
copy_mem(&input_buf[salt_len],
- cast_char_ptr_to_uint8(passphrase.data()),
- passphrase.size());
+ cast_char_ptr_to_uint8(password),
+ password_size);
}
- secure_vector<uint8_t> hash_buf(m_hash->output_length());
+ secure_vector<uint8_t> hash_buf(hash.output_length());
size_t pass = 0;
size_t generated = 0;
@@ -108,7 +108,7 @@ size_t OpenPGP_S2K::pbkdf(uint8_t output_buf[], size_t output_len,
// Preload some number of zero bytes (empty first iteration)
std::vector<uint8_t> zero_padding(pass);
- m_hash->update(zero_padding);
+ hash.update(zero_padding);
// The input is always fully processed even if iterations is very small
if(input_buf.empty() == false)
@@ -117,18 +117,106 @@ size_t OpenPGP_S2K::pbkdf(uint8_t output_buf[], size_t output_len,
while(left > 0)
{
const size_t input_to_take = std::min(left, input_buf.size());
- m_hash->update(input_buf.data(), input_to_take);
+ hash.update(input_buf.data(), input_to_take);
left -= input_to_take;
}
}
- m_hash->final(hash_buf.data());
+ hash.final(hash_buf.data());
copy_mem(output_buf + generated, hash_buf.data(), output_this_pass);
generated += output_this_pass;
++pass;
}
+ }
+
+}
+
+size_t OpenPGP_S2K::pbkdf(uint8_t output_buf[], size_t output_len,
+ const std::string& password,
+ const uint8_t salt[], size_t salt_len,
+ size_t iterations,
+ std::chrono::milliseconds msec) const
+ {
+ std::unique_ptr<PasswordHash> pwdhash;
+
+ if(iterations == 0)
+ {
+ RFC4880_S2K_Family s2k_params(m_hash->clone());
+ iterations = s2k_params.tune(output_len, msec, 0)->iterations();
+ }
+
+ pgp_s2k(*m_hash, output_buf, output_len,
+ password.c_str(), password.size(),
+ salt, salt_len,
+ iterations);
return iterations;
}
+std::string RFC4880_S2K_Family::name() const
+ {
+ return "OpenPGP-S2K(" + m_hash->name() + ")";
+ }
+
+std::unique_ptr<PasswordHash> RFC4880_S2K_Family::tune(size_t output_len, std::chrono::milliseconds msec, size_t) const
+ {
+ const std::chrono::milliseconds tune_time = std::chrono::milliseconds(30);
+
+ const size_t buf_size = 1024;
+ Botan::secure_vector<uint8_t> buffer(buf_size);
+
+ Timer timer("RFC4880_S2K", buf_size);
+ timer.run_until_elapsed(tune_time, [&]() {
+ m_hash->update(buffer);
+ });
+
+ // bug in Timer::bytes_per_second?
+ const double hash_bytes_per_second = buf_size * timer.bytes_per_second();
+ const uint64_t desired_nsec = msec.count() * 1000000;
+
+ const size_t hash_size = m_hash->output_length();
+ const size_t blocks_required = (output_len <= hash_size ? 1 : (output_len + hash_size - 1) / hash_size);
+
+ const double bytes_to_be_hashed = (hash_bytes_per_second * (desired_nsec / 1000000000.0)) / blocks_required;
+ const size_t iterations = RFC4880_round_iterations(bytes_to_be_hashed);
+
+ return std::unique_ptr<PasswordHash>(new RFC4880_S2K(m_hash->clone(), iterations));
+ }
+
+std::unique_ptr<PasswordHash> RFC4880_S2K_Family::from_params(size_t iter, size_t, size_t) const
+ {
+ return std::unique_ptr<PasswordHash>(new RFC4880_S2K(m_hash->clone(), iter));
+ }
+
+std::unique_ptr<PasswordHash> RFC4880_S2K_Family::default_params() const
+ {
+ return std::unique_ptr<PasswordHash>(new RFC4880_S2K(m_hash->clone(), 50331648));
+ }
+
+std::unique_ptr<PasswordHash> RFC4880_S2K_Family::from_iterations(size_t iter) const
+ {
+ return std::unique_ptr<PasswordHash>(new RFC4880_S2K(m_hash->clone(), iter));
+ }
+
+RFC4880_S2K::RFC4880_S2K(HashFunction* hash, size_t iterations) :
+ m_hash(hash),
+ m_iterations(iterations)
+ {
+ }
+
+std::string RFC4880_S2K::to_string() const
+ {
+ return "OpenPGP-S2K(" + m_hash->name() + "," + std::to_string(m_iterations) + ")";
+ }
+
+void RFC4880_S2K::derive_key(uint8_t out[], size_t out_len,
+ const char* password, const size_t password_len,
+ const uint8_t salt[], size_t salt_len) const
+ {
+ pgp_s2k(*m_hash, out, out_len,
+ password, password_len,
+ salt, salt_len,
+ m_iterations);
+ }
+
}
diff --git a/src/lib/pbkdf/pgp_s2k/pgp_s2k.h b/src/lib/pbkdf/pgp_s2k/pgp_s2k.h
index 02b228258..820fa3801 100644
--- a/src/lib/pbkdf/pgp_s2k/pgp_s2k.h
+++ b/src/lib/pbkdf/pgp_s2k/pgp_s2k.h
@@ -1,6 +1,7 @@
/*
* OpenPGP PBKDF
* (C) 1999-2007,2017 Jack Lloyd
+* (C) 2018 Ribose Inc
*
* Distributed under the terms of the Botan license
*/
@@ -9,11 +10,31 @@
#define BOTAN_OPENPGP_S2K_H_
#include <botan/pbkdf.h>
+#include <botan/pwdhash.h>
#include <botan/hash.h>
namespace Botan {
/**
+* RFC 4880 encodes the iteration count to a single-byte value
+*/
+uint8_t BOTAN_PUBLIC_API(2,8) RFC4880_encode_count(size_t iterations);
+
+/**
+* Decode the iteration count from RFC 4880 encoding
+*/
+size_t BOTAN_PUBLIC_API(2,8) RFC4880_decode_count(uint8_t encoded_iter);
+
+/**
+* Round an arbitrary iteration count to next largest iteration count
+* supported by RFC4880 encoding.
+*/
+inline size_t RFC4880_round_iterations(size_t iterations)
+ {
+ return RFC4880_decode_count(RFC4880_encode_count(iterations));
+ }
+
+/**
* OpenPGP's S2K
*
* See RFC 4880 sections 3.7.1.1, 3.7.1.2, and 3.7.1.3
@@ -57,10 +78,75 @@ class BOTAN_PUBLIC_API(2,2) OpenPGP_S2K final : public PBKDF
/**
* RFC 4880 encodes the iteration count to a single-byte value
*/
- static uint8_t encode_count(size_t iterations);
+ static uint8_t encode_count(size_t iterations)
+ {
+ return RFC4880_encode_count(iterations);
+ }
+
+ static size_t decode_count(uint8_t encoded_iter)
+ {
+ return RFC4880_decode_count(encoded_iter);
+ }
+
+ private:
+ std::unique_ptr<HashFunction> m_hash;
+ };
+
+/**
+* OpenPGP's S2K
+*
+* See RFC 4880 sections 3.7.1.1, 3.7.1.2, and 3.7.1.3
+* If the salt is empty and iterations == 1, "simple" S2K is used
+* If the salt is non-empty and iterations == 1, "salted" S2K is used
+* If the salt is non-empty and iterations > 1, "iterated" S2K is used
+*
+* Note that unlike PBKDF2, OpenPGP S2K's "iterations" are defined as
+* the number of bytes hashed.
+*/
+class BOTAN_PUBLIC_API(2,8) RFC4880_S2K final : public PasswordHash
+ {
+ public:
+ /**
+ * @param hash the hash function to use
+ * @param the iterations to use (this is rounded due to PGP formatting)
+ */
+ RFC4880_S2K(HashFunction* hash, size_t iterations);
+
+ std::string to_string() const override;
+
+ size_t iterations() const override { return m_iterations; }
+
+ void derive_key(uint8_t out[], size_t out_len,
+ const char* password, const size_t password_len,
+ const uint8_t salt[], size_t salt_len) const;
+
+ private:
+ std::unique_ptr<HashFunction> m_hash;
+ size_t m_iterations;
+ };
+
+class BOTAN_PUBLIC_API(2,8) RFC4880_S2K_Family final : public PasswordHashFamily
+ {
+ public:
+ RFC4880_S2K_Family(HashFunction* hash) : m_hash(hash) {}
+
+ std::string name() const override;
+
+ std::unique_ptr<PasswordHash> tune(size_t output_len,
+ std::chrono::milliseconds msec,
+ size_t max_mem) const override;
+
+ /**
+ * Return some default parameter set for this PBKDF that should be good
+ * enough for most users. The value returned may change over time as
+ * processing power and attacks improve.
+ */
+ std::unique_ptr<PasswordHash> default_params() const override;
- static size_t decode_count(uint8_t encoded_iter);
+ std::unique_ptr<PasswordHash> from_iterations(size_t iter) const override;
+ std::unique_ptr<PasswordHash> from_params(
+ size_t iter, size_t, size_t) const override;
private:
std::unique_ptr<HashFunction> m_hash;
};
diff --git a/src/lib/pbkdf/pwdhash.cpp b/src/lib/pbkdf/pwdhash.cpp
new file mode 100644
index 000000000..783ac7066
--- /dev/null
+++ b/src/lib/pbkdf/pwdhash.cpp
@@ -0,0 +1,88 @@
+/*
+* (C) 2018 Ribose Inc
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#include <botan/pbkdf.h>
+#include <botan/exceptn.h>
+#include <botan/scan_name.h>
+
+#if defined(BOTAN_HAS_PBKDF2)
+ #include <botan/pbkdf2.h>
+#endif
+
+#if defined(BOTAN_HAS_PGP_S2K)
+ #include <botan/pgp_s2k.h>
+#endif
+
+#if defined(BOTAN_HAS_SCRYPT)
+ #include <botan/scrypt.h>
+#endif
+
+namespace Botan {
+
+std::unique_ptr<PasswordHashFamily> PasswordHashFamily::create(const std::string& algo_spec,
+ const std::string& provider)
+ {
+ const SCAN_Name req(algo_spec);
+
+#if defined(BOTAN_HAS_PBKDF2)
+ if(req.algo_name() == "PBKDF2")
+ {
+ // TODO OpenSSL
+
+ if(provider.empty() || provider == "base")
+ {
+ if(auto mac = MessageAuthenticationCode::create(req.arg(0)))
+ return std::unique_ptr<PasswordHashFamily>(new PBKDF2_Family(mac.release()));
+
+ if(auto mac = MessageAuthenticationCode::create("HMAC(" + req.arg(0) + ")"))
+ return std::unique_ptr<PasswordHashFamily>(new PBKDF2_Family(mac.release()));
+ }
+
+ return nullptr;
+ }
+#endif
+
+#if defined(BOTAN_HAS_SCRYPT)
+ if(req.algo_name() == "Scrypt")
+ {
+ return std::unique_ptr<PasswordHashFamily>(new Scrypt_Family);
+ }
+#endif
+
+#if defined(BOTAN_HAS_PGP_S2K)
+ if(req.algo_name() == "OpenPGP-S2K" && req.arg_count() == 1)
+ {
+ if(auto hash = HashFunction::create(req.arg(0)))
+ {
+ return std::unique_ptr<PasswordHashFamily>(new RFC4880_S2K_Family(hash.release()));
+ }
+ }
+#endif
+
+ BOTAN_UNUSED(req);
+ BOTAN_UNUSED(provider);
+
+ return nullptr;
+ }
+
+//static
+std::unique_ptr<PasswordHashFamily>
+PasswordHashFamily::create_or_throw(const std::string& algo,
+ const std::string& provider)
+ {
+ if(auto pbkdf = PasswordHashFamily::create(algo, provider))
+ {
+ return pbkdf;
+ }
+ throw Lookup_Error("PasswordHashFamily", algo, provider);
+ }
+
+std::vector<std::string> PasswordHashFamily::providers(const std::string& algo_spec)
+ {
+ return probe_providers_of<PasswordHashFamily>(algo_spec, { "base", "openssl" });
+ }
+
+}
diff --git a/src/lib/pbkdf/pwdhash.h b/src/lib/pbkdf/pwdhash.h
new file mode 100644
index 000000000..d4a205340
--- /dev/null
+++ b/src/lib/pbkdf/pwdhash.h
@@ -0,0 +1,162 @@
+/*
+* (C) 2018 Ribose Inc
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#ifndef BOTAN_PWDHASH_H_
+#define BOTAN_PWDHASH_H_
+
+#include <botan/types.h>
+#include <string>
+#include <memory>
+#include <vector>
+#include <chrono>
+
+namespace Botan {
+
+/**
+* Base class for password based key derivation functions.
+*
+* Converts a password into a key using a salt and iterated hashing to
+* make brute force attacks harder.
+*/
+class BOTAN_PUBLIC_API(2,8) PasswordHash
+ {
+ public:
+ virtual ~PasswordHash() = default;
+
+ virtual std::string to_string() const = 0;
+
+ /**
+ * Most password hashes have some notion of iterations.
+ */
+ virtual size_t iterations() const = 0;
+
+ /**
+ * Some password hashing algorithms have a parameter which controls how
+ * much memory is used. If not supported by some algorithm, returns 0.
+ */
+ virtual size_t memory_param() const { return 0; }
+
+ /**
+ * Some password hashing algorithms have a parallelism parameter.
+ * If the algorithm does not support this notion, then the
+ * function returns zero. This allows distinguishing between a
+ * password hash which just does not support parallel operation,
+ * vs one that does support parallel operation but which has been
+ * configured to use a single lane.
+ */
+ virtual size_t parallelism() const { return 0; }
+
+ /**
+ * Returns an estimate of the total memory usage required to perform this
+ * key derivation.
+ *
+ * If this algorithm uses a small and constant amount of memory, with no
+ * effort made towards being memory hard, this function returns 0.
+ */
+ virtual size_t total_memory_usage() const { return 0; }
+
+ /**
+ * Derive a key from a password
+ *
+ * @param out buffer to store the derived key, must be of out_len bytes
+ * @param out_len the desired length of the key to produce
+ * @param password the password to derive the key from
+ * @param password_len the length of password in bytes
+ * @param salt a randomly chosen salt
+ * @param salt_len length of salt in bytes
+ *
+ * This function is const, but is not thread safe. Different threads should
+ * either use unique objects, or serialize all access.
+ */
+ virtual void derive_key(uint8_t out[], size_t out_len,
+ const char* password, size_t password_len,
+ const uint8_t salt[], size_t salt_len) const = 0;
+ };
+
+class BOTAN_PUBLIC_API(2,8) PasswordHashFamily
+ {
+ public:
+ /**
+ * Create an instance based on a name
+ * If provider is empty then best available is chosen.
+ * @param algo_spec algorithm name
+ * @param provider provider implementation to choose
+ * @return a null pointer if the algo/provider combination cannot be found
+ */
+ static std::unique_ptr<PasswordHashFamily> create(const std::string& algo_spec,
+ const std::string& provider = "");
+
+ /**
+ * Create an instance based on a name, or throw if the
+ * algo/provider combination cannot be found. If provider is
+ * empty then best available is chosen.
+ */
+ static std::unique_ptr<PasswordHashFamily>
+ create_or_throw(const std::string& algo_spec,
+ const std::string& provider = "");
+
+ /**
+ * @return list of available providers for this algorithm, empty if not available
+ */
+ static std::vector<std::string> providers(const std::string& algo_spec);
+
+ virtual ~PasswordHashFamily() = default;
+
+ /**
+ * @return name of this PasswordHash
+ */
+ virtual std::string name() const = 0;
+
+ /**
+ * Return a new parameter set tuned for this machine
+ * @param output_length how long the output length will be
+ * @param msec the desired execution time in milliseconds
+ *
+ * @param max_memory_usage some password hash functions can use a tunable
+ * amount of memory, in this case max_memory_usage limits the amount of RAM
+ * the returned parameters will require, in mebibytes (2**20 bytes). It may
+ * require some small amount above the request. Set to zero to place no
+ * limit at all.
+ */
+ virtual std::unique_ptr<PasswordHash> tune(size_t output_len,
+ std::chrono::milliseconds msec,
+ size_t max_memory_usage_mb = 0) const = 0;
+
+ /**
+ * Return some default parameter set for this PBKDF that should be good
+ * enough for most users. The value returned may change over time as
+ * processing power and attacks improve.
+ */
+ virtual std::unique_ptr<PasswordHash> default_params() const = 0;
+
+ /**
+ * Return a parameter chosen based on a rough approximation with the
+ * specified iteration count. The exact value this returns for a particular
+ * algorithm may change from over time. Think of it as an alternative to
+ * tune, where time is expressed in terms of PBKDF2 iterations rather than
+ * milliseconds.
+ */
+ virtual std::unique_ptr<PasswordHash> from_iterations(size_t iterations) const = 0;
+
+ /**
+ * Create a password hash using some scheme specific format.
+ * Eg PBKDF2 and PGP-S2K set iterations in i1
+ * Scrypt uses N,r,p in i{1-3}
+ * Bcrypt-PBKDF just has iterations
+ * Argon2{i,d,id} would use iterations, memory, parallelism for i{1-3},
+ * and Argon2 type is part of the family.
+ *
+ * Values not needed should be set to 0
+ */
+ virtual std::unique_ptr<PasswordHash> from_params(
+ size_t i1,
+ size_t i2 = 0,
+ size_t i3 = 0) const = 0;
+ };
+
+}
+
+#endif
diff --git a/src/lib/pbkdf/scrypt/info.txt b/src/lib/pbkdf/scrypt/info.txt
index cd1551d3b..3ba48fd72 100644
--- a/src/lib/pbkdf/scrypt/info.txt
+++ b/src/lib/pbkdf/scrypt/info.txt
@@ -1,5 +1,5 @@
<defines>
-SCRYPT -> 20180515
+SCRYPT -> 20180902
</defines>
<requires>
diff --git a/src/lib/pbkdf/scrypt/scrypt.cpp b/src/lib/pbkdf/scrypt/scrypt.cpp
index e258f391d..826438926 100644
--- a/src/lib/pbkdf/scrypt/scrypt.cpp
+++ b/src/lib/pbkdf/scrypt/scrypt.cpp
@@ -1,5 +1,6 @@
/**
* (C) 2018 Jack Lloyd
+* (C) 2018 Ribose Inc
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
@@ -8,10 +9,157 @@
#include <botan/pbkdf2.h>
#include <botan/salsa20.h>
#include <botan/loadstor.h>
+#include <botan/exceptn.h>
#include <botan/internal/bit_ops.h>
+#include <botan/internal/timer.h>
+#include <sstream>
namespace Botan {
+std::string Scrypt_Family::name() const
+ {
+ return "Scrypt";
+ }
+
+std::unique_ptr<PasswordHash> Scrypt_Family::default_params() const
+ {
+ return std::unique_ptr<PasswordHash>(new Scrypt(32768, 8, 1));
+ }
+
+std::unique_ptr<PasswordHash> Scrypt_Family::tune(size_t output_length,
+ std::chrono::milliseconds msec,
+ size_t max_memory_usage_mb) const
+ {
+ BOTAN_UNUSED(output_length);
+
+ /*
+ * Some rough relations between scrypt parameters and runtime.
+ * Denote here by stime(N,r,p) the msec it takes to run scrypt.
+ *
+ * Emperically for smaller sizes:
+ * stime(N,8*r,p) / stime(N,r,p) is ~ 6-7
+ * stime(N,r,8*p) / stime(N,r,8*p) is ~ 7
+ * stime(2*N,r,p) / stime(N,r,p) is ~ 2
+ *
+ * Compute stime(8192,1,1) as baseline and extrapolate
+ */
+
+ const size_t max_memory_usage = max_memory_usage_mb * 1024 * 1024;
+ // Starting parameters
+ size_t N = 8192;
+ size_t r = 1;
+ size_t p = 1;
+
+ Timer timer("Scrypt", 0);
+ const std::chrono::milliseconds tune_msec(30);
+
+ timer.run_until_elapsed(tune_msec, [&]() {
+ uint8_t output[32] = { 0 };
+ scrypt(output, sizeof(output), "test", 4, nullptr, 0, N, r, p);
+ });
+
+ // No timer events seems strange, perhaps something is wrong - give
+ // up on this and just return default params
+ if(timer.events() == 0)
+ return default_params();
+
+ // nsec per eval of scrypt with initial params
+ const uint64_t measured_time = timer.value() / timer.events();
+
+ const uint64_t target_nsec = msec.count() * static_cast<uint64_t>(1000000);
+
+ uint64_t est_nsec = measured_time;
+
+ // First move increase r by 8x if possible
+
+ if(max_memory_usage == 0 || scrypt_memory_usage(N, r, p)*8 < max_memory_usage)
+ {
+ if(target_nsec / est_nsec >= 5)
+ {
+ r *= 8;
+ est_nsec *= 5;
+ }
+ }
+
+ // Now double N as many times as we can
+
+ while(max_memory_usage == 0 || scrypt_memory_usage(N, r, p)*2 < max_memory_usage)
+ {
+ if(target_nsec / est_nsec >= 2)
+ {
+ N *= 2;
+ est_nsec *= 2;
+ }
+ else
+ break;
+ }
+
+ // If we have extra runtime budget, increment p
+
+ if(target_nsec / est_nsec > 2)
+ p *= std::min<size_t>(1024, (target_nsec / est_nsec));
+
+ return std::unique_ptr<PasswordHash>(new Scrypt(N, r, p));
+ }
+
+std::unique_ptr<PasswordHash> Scrypt_Family::from_params(size_t N, size_t r, size_t p) const
+ {
+ return std::unique_ptr<PasswordHash>(new Scrypt(N, r, p));
+ }
+
+std::unique_ptr<PasswordHash> Scrypt_Family::from_iterations(size_t iter) const
+ {
+ const size_t r = 8;
+ const size_t p = 1;
+
+ size_t N = 8192;
+
+ if(iter > 50000)
+ N = 16384;
+ if(iter > 100000)
+ N = 32768;
+ if(iter > 150000)
+ N = 65536;
+
+ return std::unique_ptr<PasswordHash>(new Scrypt(N, r, p));
+ }
+
+Scrypt::Scrypt(size_t N, size_t r, size_t p) :
+ m_N(N), m_r(r), m_p(p)
+ {
+ if(!is_power_of_2(N))
+ throw Invalid_Argument("Scrypt N parameter must be a power of 2");
+
+ if(p == 0 || p > 1024)
+ throw Invalid_Argument("Invalid or unsupported scrypt p");
+ if(r == 0 || r > 256)
+ throw Invalid_Argument("Invalid or unsupported scrypt r");
+ if(N < 1 || N > 4194304)
+ throw Invalid_Argument("Invalid or unsupported scrypt N");
+ }
+
+std::string Scrypt::to_string() const
+ {
+ std::ostringstream oss;
+ oss << "Scrypt(" << m_N << "," << m_r << "," << m_p << ")";
+ return oss.str();
+ }
+
+size_t Scrypt::total_memory_usage() const
+ {
+ return scrypt_memory_usage(m_N, m_r, m_p);
+ }
+
+void Scrypt::derive_key(uint8_t output[], size_t output_len,
+ const char* password, size_t password_len,
+ const uint8_t salt[], size_t salt_len) const
+ {
+ scrypt(output, output_len,
+ password, password_len,
+ salt, salt_len,
+ N(), r(), p());
+ }
+
namespace {
void scryptBlockMix(size_t r, uint8_t* B, uint8_t* Y)
@@ -60,101 +208,31 @@ void scryptROMmix(size_t r, size_t N, uint8_t* B, secure_vector<uint8_t>& V)
}
-Scrypt_Params::Scrypt_Params(size_t N, size_t r, size_t p) :
- m_N(N), m_r(r), m_p(p)
- {
- BOTAN_ARG_CHECK(p <= 128, "Invalid scrypt p");
- BOTAN_ARG_CHECK(N <= 4194304 && is_power_of_2(N), "Invalid scrypt N");
- BOTAN_ARG_CHECK(r <= 64, "Invalid scrypt r");
- }
-
-Scrypt_Params::Scrypt_Params(std::chrono::milliseconds msec)
- {
- /*
- This mapping is highly subjective and machine specific
- For simplicity we fix r=8 and p=4
- */
- m_r = 8;
- m_p = 4;
-
- if(msec.count() <= 10)
- {
- m_N = 4096;
- m_p = 1;
- }
- else if(msec.count() <= 50)
- {
- m_N = 8192;
- }
- else if(msec.count() <= 100)
- {
- m_N = 16384;
- }
- else if(msec.count() <= 500)
- {
- m_N = 32768;
- }
- else
- {
- m_N = 65536;
- }
- }
-
-Scrypt_Params::Scrypt_Params(size_t iterations)
- {
- // This mapping is highly subjective and machine specific
- m_r = 8;
- m_p = 4;
-
- if(iterations < 1000)
- {
- m_N = 8192;
- }
- else if(iterations < 5000)
- {
- m_N = 16384;
- }
- else if(iterations < 10000)
- {
- m_N = 32768;
- }
- else
- {
- m_N = 65536;
- }
- }
-
-void scrypt(uint8_t output[], size_t output_len,
- const std::string& password,
- const uint8_t salt[], size_t salt_len,
- const Scrypt_Params& params)
- {
- scrypt(output, output_len, password, salt, salt_len,
- params.N(), params.r(), params.p());
- }
-
void scrypt(uint8_t output[], size_t output_len,
- const std::string& password,
+ const char* password, size_t password_len,
const uint8_t salt[], size_t salt_len,
size_t N, size_t r, size_t p)
{
- // Upper bounds here are much lower than scrypt maximums yet seem sufficient
- BOTAN_ARG_CHECK(p <= 128, "Invalid scrypt p");
- BOTAN_ARG_CHECK(N <= 4194304 && is_power_of_2(N), "Invalid scrypt N");
- BOTAN_ARG_CHECK(r <= 64, "Invalid scrypt r");
-
const size_t S = 128 * r;
secure_vector<uint8_t> B(p * S);
+ // temp space
+ secure_vector<uint8_t> V((N+1) * S);
- PKCS5_PBKDF2 pbkdf2(MessageAuthenticationCode::create_or_throw("HMAC(SHA-256)").release());
+ auto hmac_sha256 = MessageAuthenticationCode::create_or_throw("HMAC(SHA-256)");
- pbkdf2.pbkdf(B.data(), B.size(),
- password,
- salt, salt_len,
- 1, std::chrono::milliseconds(0));
+ try
+ {
+ hmac_sha256->set_key(cast_char_ptr_to_uint8(password), password_len);
+ }
+ catch(Invalid_Key_Length&)
+ {
+ throw Exception("Scrypt cannot accept passphrases of the provided length");
+ }
- // temp space
- secure_vector<uint8_t> V((N+1) * S);
+ pbkdf2(*hmac_sha256.get(),
+ B.data(), B.size(),
+ salt, salt_len,
+ 1);
// these can be parallel
for(size_t i = 0; i != p; ++i)
@@ -162,10 +240,10 @@ void scrypt(uint8_t output[], size_t output_len,
scryptROMmix(r, N, &B[128*r*i], V);
}
- pbkdf2.pbkdf(output, output_len,
- password,
- B.data(), B.size(),
- 1, std::chrono::milliseconds(0));
+ pbkdf2(*hmac_sha256.get(),
+ output, output_len,
+ B.data(), B.size(),
+ 1);
}
}
diff --git a/src/lib/pbkdf/scrypt/scrypt.h b/src/lib/pbkdf/scrypt/scrypt.h
index 199caa4c8..42d0f0a3b 100644
--- a/src/lib/pbkdf/scrypt/scrypt.h
+++ b/src/lib/pbkdf/scrypt/scrypt.h
@@ -1,5 +1,6 @@
/**
* (C) 2018 Jack Lloyd
+* (C) 2018 Ribose Inc
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
@@ -7,30 +8,87 @@
#ifndef BOTAN_SCRYPT_H_
#define BOTAN_SCRYPT_H_
-#include <botan/types.h>
-#include <chrono>
-#include <string>
+#include <botan/pwdhash.h>
namespace Botan {
-class Scrypt_Params
+/**
+* Scrypt key derivation function (RFC 7914)
+*/
+class BOTAN_PUBLIC_API(2,8) Scrypt final : public PasswordHash
{
public:
- Scrypt_Params(size_t N, size_t r, size_t p);
+ Scrypt(size_t N, size_t r, size_t p);
- Scrypt_Params(std::chrono::milliseconds msec);
+ Scrypt(const Scrypt& other) = default;
+ Scrypt& operator=(const Scrypt&) = default;
- Scrypt_Params(size_t iterations);
+ /**
+ * Derive a new key under the current Scrypt parameter set
+ */
+ void derive_key(uint8_t out[], size_t out_len,
+ const char* password, const size_t password_len,
+ const uint8_t salt[], size_t salt_len) const override;
+
+ std::string to_string() const override;
size_t N() const { return m_N; }
size_t r() const { return m_r; }
size_t p() const { return m_p; }
+
+ size_t iterations() const override { return r(); }
+
+ size_t parallelism() const override { return p(); }
+
+ size_t memory_param() const override { return N(); }
+
+ size_t total_memory_usage() const override;
+
private:
size_t m_N, m_r, m_p;
};
+class BOTAN_PUBLIC_API(2,8) Scrypt_Family final : public PasswordHashFamily
+ {
+ public:
+ std::string name() const override;
+
+ std::unique_ptr<PasswordHash> tune(size_t output_length,
+ std::chrono::milliseconds msec,
+ size_t max_memory) const override;
+
+ std::unique_ptr<PasswordHash> default_params() const override;
+
+ std::unique_ptr<PasswordHash> from_iterations(size_t iter) const override;
+
+ std::unique_ptr<PasswordHash> from_params(
+ size_t N, size_t r, size_t p) const override;
+ };
+
+/**
+* Scrypt key derivation function (RFC 7914)
+*
+* @param output the output will be placed here
+* @param output_len length of output
+* @param password the user password
+* @param salt the salt
+* @param salt_len length of salt
+* @param N the CPU/Memory cost parameter, must be power of 2
+* @param r the block size parameter
+* @param p the parallelization parameter
+*
+* Suitable parameters for most uses would be N = 32768, r = 8, p = 1
+*
+* Scrypt uses approximately (p + N + 1) * 128 * r bytes of memory
+*/
+void BOTAN_PUBLIC_API(2,8) scrypt(uint8_t output[], size_t output_len,
+ const char* password, size_t password_len,
+ const uint8_t salt[], size_t salt_len,
+ size_t N, size_t r, size_t p);
+
/**
* Scrypt key derivation function (RFC 7914)
+* Before 2.8 this function was the primary interface for scrypt
*
* @param output the output will be placed here
* @param output_len length of output
@@ -41,19 +99,25 @@ class Scrypt_Params
* @param r the block size parameter
* @param p the parallelization parameter
*
-* Suitable parameters for most uses would be N = 16384, r = 8, p = 1
+* Suitable parameters for most uses would be N = 32768, r = 8, p = 1
*
* Scrypt uses approximately (p + N + 1) * 128 * r bytes of memory
*/
-void BOTAN_UNSTABLE_API scrypt(uint8_t output[], size_t output_len,
- const std::string& password,
- const uint8_t salt[], size_t salt_len,
- size_t N, size_t r, size_t p);
-
-void BOTAN_UNSTABLE_API scrypt(uint8_t output[], size_t output_len,
- const std::string& password,
- const uint8_t salt[], size_t salt_len,
- const Scrypt_Params& params);
+inline void scrypt(uint8_t output[], size_t output_len,
+ const std::string& password,
+ const uint8_t salt[], size_t salt_len,
+ size_t N, size_t r, size_t p)
+ {
+ return scrypt(output, output_len,
+ password.c_str(), password.size(),
+ salt, salt_len,
+ N, r, p);
+ }
+
+inline size_t scrypt_memory_usage(size_t N, size_t r, size_t p)
+ {
+ return 128 * r * (N + p);
+ }
}
diff --git a/src/lib/pubkey/pbes2/pbes2.cpp b/src/lib/pubkey/pbes2/pbes2.cpp
index fce225e99..263263843 100644
--- a/src/lib/pubkey/pbes2/pbes2.cpp
+++ b/src/lib/pubkey/pbes2/pbes2.cpp
@@ -1,6 +1,7 @@
/*
* PKCS #5 PBES2
* (C) 1999-2008,2014 Jack Lloyd
+* (C) 2018 Ribose Inc
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
@@ -51,13 +52,11 @@ SymmetricKey derive_key(const std::string& passphrase,
if(salt.size() < 8)
throw Decoding_Error("PBE-PKCS5 v2.0: Encoded salt is too small");
- const std::string prf = OIDS::lookup(prf_algo.get_oid());
-
- std::unique_ptr<PBKDF> pbkdf(get_pbkdf("PBKDF2(" + prf + ")"));
-
if(key_length == 0)
key_length = default_key_size;
+ const std::string prf = OIDS::lookup(prf_algo.get_oid());
+ std::unique_ptr<PBKDF> pbkdf(get_pbkdf("PBKDF2(" + prf + ")"));
return pbkdf->pbkdf_iterations(key_length, passphrase, salt.data(), salt.size(), iterations);
}
#if defined(BOTAN_HAS_SCRYPT)
@@ -106,24 +105,39 @@ secure_vector<uint8_t> derive_key(const std::string& passphrase,
{
#if defined(BOTAN_HAS_SCRYPT)
- Scrypt_Params params(32768, 8, 4);
+ std::unique_ptr<PasswordHashFamily> pwhash_fam = PasswordHashFamily::create_or_throw("Scrypt");
+
+ std::unique_ptr<PasswordHash> pwhash;
if(msec_in_iterations_out)
- params = Scrypt_Params(std::chrono::milliseconds(*msec_in_iterations_out));
+ {
+ const std::chrono::milliseconds msec(*msec_in_iterations_out);
+ pwhash = pwhash_fam->tune(key_length, msec);
+ }
else
- params = Scrypt_Params(iterations_if_msec_null);
+ {
+ pwhash = pwhash_fam->from_iterations(iterations_if_msec_null);
+ }
secure_vector<uint8_t> key(key_length);
- scrypt(key.data(), key.size(), passphrase,
- salt.data(), salt.size(), params);
+ pwhash->derive_key(key.data(), key.size(),
+ passphrase.c_str(), passphrase.size(),
+ salt.data(), salt.size());
+
+ const size_t N = pwhash->memory_param();
+ const size_t r = pwhash->iterations();
+ const size_t p = pwhash->parallelism();
+
+ if(msec_in_iterations_out)
+ *msec_in_iterations_out = 0;
std::vector<uint8_t> scrypt_params;
DER_Encoder(scrypt_params)
.start_cons(SEQUENCE)
.encode(salt, OCTET_STRING)
- .encode(params.N())
- .encode(params.r())
- .encode(params.p())
+ .encode(N)
+ .encode(r)
+ .encode(p)
.encode(key_length)
.end_cons();
@@ -136,26 +150,36 @@ secure_vector<uint8_t> derive_key(const std::string& passphrase,
else
{
const std::string prf = "HMAC(" + digest + ")";
+ const std::string pbkdf_name = "PBKDF2(" + prf + ")";
- std::unique_ptr<PBKDF> pbkdf(get_pbkdf("PBKDF2(" + prf + ")"));
-
- size_t iterations = iterations_if_msec_null;
+ std::unique_ptr<PasswordHashFamily> pwhash_fam = PasswordHashFamily::create(pbkdf_name);
+ if(!pwhash_fam)
+ throw Invalid_Argument("Unknown password hash digest " + digest);
- secure_vector<uint8_t> key;
+ std::unique_ptr<PasswordHash> pwhash;
if(msec_in_iterations_out)
{
- std::chrono::milliseconds msec(*msec_in_iterations_out);
- key = pbkdf->derive_key(key_length, passphrase, salt.data(), salt.size(), msec, iterations).bits_of();
- *msec_in_iterations_out = iterations;
+ const std::chrono::milliseconds msec(*msec_in_iterations_out);
+ pwhash = pwhash_fam->tune(key_length, msec);
}
else
{
- key = pbkdf->pbkdf_iterations(key_length, passphrase, salt.data(), salt.size(), iterations);
+ pwhash = pwhash_fam->from_iterations(iterations_if_msec_null);
}
+ secure_vector<uint8_t> key(key_length);
+ pwhash->derive_key(key.data(), key.size(),
+ passphrase.c_str(), passphrase.size(),
+ salt.data(), salt.size());
+
std::vector<uint8_t> pbkdf2_params;
+ const size_t iterations = pwhash->iterations();
+
+ if(msec_in_iterations_out)
+ *msec_in_iterations_out = iterations;
+
DER_Encoder(pbkdf2_params)
.start_cons(SEQUENCE)
.encode(salt, OCTET_STRING)
diff --git a/src/lib/utils/info.txt b/src/lib/utils/info.txt
index fb9325f93..93ae795ef 100644
--- a/src/lib/utils/info.txt
+++ b/src/lib/utils/info.txt
@@ -1,5 +1,5 @@
<defines>
-UTIL_FUNCTIONS -> 20171003
+UTIL_FUNCTIONS -> 20180903
</defines>
load_on always
@@ -35,6 +35,7 @@ prefetch.h
rounding.h
safeint.h
stl_util.h
+timer.h
</header:internal>
<requires>
diff --git a/src/lib/utils/timer.cpp b/src/lib/utils/timer.cpp
new file mode 100644
index 000000000..5d64e63fb
--- /dev/null
+++ b/src/lib/utils/timer.cpp
@@ -0,0 +1,118 @@
+/*
+* (C) 2018 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#include <botan/internal/timer.h>
+#include <algorithm>
+#include <sstream>
+#include <iomanip>
+
+namespace Botan {
+
+void Timer::stop()
+ {
+ if(m_timer_start)
+ {
+ const uint64_t now = Timer::get_system_timestamp_ns();
+
+ if(now > m_timer_start)
+ {
+ uint64_t dur = now - m_timer_start;
+
+ m_time_used += dur;
+
+ if(m_cpu_cycles_start != 0)
+ {
+ uint64_t cycles_taken = Timer::get_cpu_cycle_counter() - m_cpu_cycles_start;
+ if(cycles_taken > 0)
+ {
+ m_cpu_cycles_used += static_cast<size_t>(cycles_taken * m_clock_cycle_ratio);
+ }
+ }
+
+ if(m_event_count == 0)
+ {
+ m_min_time = m_max_time = dur;
+ }
+ else
+ {
+ m_max_time = std::max(m_max_time, dur);
+ m_min_time = std::min(m_min_time, dur);
+ }
+ }
+
+ m_timer_start = 0;
+ ++m_event_count;
+ }
+ }
+
+std::string Timer::result_string_bps() const
+ {
+ const size_t MiB = 1024 * 1024;
+
+ const double MiB_total = static_cast<double>(events()) / MiB;
+ const double MiB_per_sec = MiB_total / seconds();
+
+ std::ostringstream oss;
+ oss << get_name();
+
+ if(!doing().empty())
+ {
+ oss << " " << doing();
+ }
+
+ if(buf_size() > 0)
+ {
+ oss << " buffer size " << buf_size() << " bytes:";
+ }
+
+ if(events() == 0)
+ oss << " " << "N/A";
+ else
+ oss << " " << std::fixed << std::setprecision(3) << MiB_per_sec << " MiB/sec";
+
+ if(cycles_consumed() != 0)
+ {
+ const double cycles_per_byte = static_cast<double>(cycles_consumed()) / events();
+ oss << " " << std::fixed << std::setprecision(2) << cycles_per_byte << " cycles/byte";
+ }
+
+ oss << " (" << MiB_total << " MiB in " << milliseconds() << " ms)\n";
+
+ return oss.str();
+ }
+
+std::string Timer::result_string_ops() const
+ {
+ std::ostringstream oss;
+
+ oss << get_name() << " ";
+
+ if(events() == 0)
+ {
+ oss << "no events\n";
+ }
+ else
+ {
+ oss << static_cast<uint64_t>(events_per_second())
+ << ' ' << doing() << "/sec; "
+ << std::setprecision(2) << std::fixed
+ << ms_per_event() << " ms/op";
+
+ if(cycles_consumed() != 0)
+ {
+ const double cycles_per_op = static_cast<double>(cycles_consumed()) / events();
+ const size_t precision = (cycles_per_op < 10000) ? 2 : 0;
+ oss << " " << std::fixed << std::setprecision(precision) << cycles_per_op << " cycles/op";
+ }
+
+ oss << " (" << events() << " " << (events() == 1 ? "op" : "ops")
+ << " in " << milliseconds() << " ms)\n";
+ }
+
+ return oss.str();
+ }
+
+}
diff --git a/src/lib/utils/timer.h b/src/lib/utils/timer.h
new file mode 100644
index 000000000..6a6b807db
--- /dev/null
+++ b/src/lib/utils/timer.h
@@ -0,0 +1,216 @@
+/*
+* (C) 2018 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#ifndef BOTAN_TIMER_H_
+#define BOTAN_TIMER_H_
+
+#include <botan/types.h>
+#include <botan/internal/os_utils.h>
+#include <string>
+#include <chrono>
+
+namespace Botan {
+
+class BOTAN_TEST_API Timer final
+ {
+ public:
+ Timer(const std::string& name,
+ const std::string& provider,
+ const std::string& doing,
+ uint64_t event_mult,
+ size_t buf_size,
+ double clock_cycle_ratio,
+ uint64_t clock_speed)
+ : m_name(name + ((provider.empty() || provider == "base") ? "" : " [" + provider + "]"))
+ , m_doing(doing)
+ , m_buf_size(buf_size)
+ , m_event_mult(event_mult)
+ , m_clock_cycle_ratio(clock_cycle_ratio)
+ , m_clock_speed(clock_speed)
+ {}
+
+ Timer(const std::string& name, size_t buf_size = 0) :
+ Timer(name, "", "", 1, buf_size, 0.0, 0.0)
+ {}
+
+ Timer(const Timer& other) = default;
+
+ static uint64_t get_system_timestamp_ns()
+ {
+ return Botan::OS::get_system_timestamp_ns();
+ }
+
+ static uint64_t get_cpu_cycle_counter()
+ {
+ return Botan::OS::get_processor_timestamp();
+ }
+
+ void start()
+ {
+ stop();
+ m_timer_start = Timer::get_system_timestamp_ns();
+ m_cpu_cycles_start = Timer::get_cpu_cycle_counter();
+ }
+
+ void stop();
+
+ bool under(std::chrono::milliseconds msec)
+ {
+ return (milliseconds() < msec.count());
+ }
+
+ class Timer_Scope final
+ {
+ public:
+ explicit Timer_Scope(Timer& timer)
+ : m_timer(timer)
+ {
+ m_timer.start();
+ }
+ ~Timer_Scope()
+ {
+ try
+ {
+ m_timer.stop();
+ }
+ catch(...) {}
+ }
+ private:
+ Timer& m_timer;
+ };
+
+ template<typename F>
+ auto run(F f) -> decltype(f())
+ {
+ Timer_Scope timer(*this);
+ return f();
+ }
+
+ template<typename F>
+ void run_until_elapsed(std::chrono::milliseconds msec, F f)
+ {
+ while(this->under(msec))
+ {
+ run(f);
+ }
+ }
+
+ uint64_t value() const
+ {
+ return m_time_used;
+ }
+
+ double seconds() const
+ {
+ return milliseconds() / 1000.0;
+ }
+
+ double milliseconds() const
+ {
+ return value() / 1000000.0;
+ }
+
+ double ms_per_event() const
+ {
+ return milliseconds() / events();
+ }
+
+ uint64_t cycles_consumed() const
+ {
+ if(m_clock_speed != 0)
+ {
+ return (static_cast<double>(m_clock_speed) * value()) / 1000;
+ }
+ return m_cpu_cycles_used;
+ }
+
+ uint64_t events() const
+ {
+ return m_event_count * m_event_mult;
+ }
+
+ const std::string& get_name() const
+ {
+ return m_name;
+ }
+
+ const std::string& doing() const
+ {
+ return m_doing;
+ }
+
+ size_t buf_size() const
+ {
+ return m_buf_size;
+ }
+
+ double bytes_per_second() const
+ {
+ return seconds() > 0.0 ? events() / seconds() : 0.0;
+ }
+
+ double events_per_second() const
+ {
+ return seconds() > 0.0 ? events() / seconds() : 0.0;
+ }
+
+ double seconds_per_event() const
+ {
+ return events() > 0 ? seconds() / events() : 0.0;
+ }
+
+ void set_custom_msg(const std::string& s)
+ {
+ m_custom_msg = s;
+ }
+
+ bool operator<(const Timer& other) const
+ {
+ if(this->doing() != other.doing())
+ return (this->doing() < other.doing());
+
+ return (this->get_name() < other.get_name());
+ }
+
+ std::string to_string() const
+ {
+ if(m_custom_msg.size() > 0)
+ {
+ return m_custom_msg;
+ }
+ else if(this->buf_size() == 0)
+ {
+ return result_string_ops();
+ }
+ else
+ {
+ return result_string_bps();
+ }
+ }
+
+ private:
+ std::string result_string_bps() const;
+ std::string result_string_ops() const;
+
+ // const data
+ std::string m_name, m_doing;
+ size_t m_buf_size;
+ uint64_t m_event_mult;
+ double m_clock_cycle_ratio;
+ uint64_t m_clock_speed;
+
+ // set at runtime
+ std::string m_custom_msg;
+ uint64_t m_time_used = 0, m_timer_start = 0;
+ uint64_t m_event_count = 0;
+
+ uint64_t m_max_time = 0, m_min_time = 0;
+ uint64_t m_cpu_cycles_start = 0, m_cpu_cycles_used = 0;
+ };
+
+}
+
+#endif