aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJack Lloyd <[email protected]>2018-05-22 22:43:45 -0400
committerJack Lloyd <[email protected]>2018-05-22 23:26:49 -0400
commitf87b9e4128698951c10e47dca01811a677577ca0 (patch)
tree0923ea1d3dc2bba07445d3af5916fea7398a5fdd
parent5df1042ea95e27b58c2a4a96d036a9492e22ef67 (diff)
Support scrypt for encrypting private keys
-rw-r--r--src/cli/speed.cpp4
-rw-r--r--src/lib/pbkdf/scrypt/scrypt.cpp73
-rw-r--r--src/lib/pbkdf/scrypt/scrypt.h22
-rw-r--r--src/lib/pubkey/pbes2/pbes2.cpp249
-rw-r--r--src/tests/test_pubkey.cpp109
5 files changed, 323 insertions, 134 deletions
diff --git a/src/cli/speed.cpp b/src/cli/speed.cpp
index d586e68ee..56de27ca8 100644
--- a/src/cli/speed.cpp
+++ b/src/cli/speed.cpp
@@ -2151,11 +2151,11 @@ class Speed final : public Command
std::chrono::milliseconds msec)
{
- for(size_t N : { 8192, 16384, 32768 })
+ for(size_t N : { 8192, 16384, 32768, 65536 })
{
for(size_t r : { 1, 8 })
{
- for(size_t p : { 1, 2, 4 })
+ for(size_t p : { 1, 4, 8 })
{
std::unique_ptr<Timer> scrypt_timer = make_timer(
"scrypt-" + std::to_string(N) + "-" +
diff --git a/src/lib/pbkdf/scrypt/scrypt.cpp b/src/lib/pbkdf/scrypt/scrypt.cpp
index 81170bf89..e258f391d 100644
--- a/src/lib/pbkdf/scrypt/scrypt.cpp
+++ b/src/lib/pbkdf/scrypt/scrypt.cpp
@@ -60,6 +60,79 @@ 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 uint8_t salt[], size_t salt_len,
diff --git a/src/lib/pbkdf/scrypt/scrypt.h b/src/lib/pbkdf/scrypt/scrypt.h
index 5eaa3a4fc..199caa4c8 100644
--- a/src/lib/pbkdf/scrypt/scrypt.h
+++ b/src/lib/pbkdf/scrypt/scrypt.h
@@ -8,10 +8,27 @@
#define BOTAN_SCRYPT_H_
#include <botan/types.h>
+#include <chrono>
#include <string>
namespace Botan {
+class Scrypt_Params
+ {
+ public:
+ Scrypt_Params(size_t N, size_t r, size_t p);
+
+ Scrypt_Params(std::chrono::milliseconds msec);
+
+ Scrypt_Params(size_t iterations);
+
+ size_t N() const { return m_N; }
+ size_t r() const { return m_r; }
+ size_t p() const { return m_p; }
+ private:
+ size_t m_N, m_r, m_p;
+ };
+
/**
* Scrypt key derivation function (RFC 7914)
*
@@ -33,6 +50,11 @@ void BOTAN_UNSTABLE_API scrypt(uint8_t output[], size_t output_len,
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);
+
}
#endif
diff --git a/src/lib/pubkey/pbes2/pbes2.cpp b/src/lib/pubkey/pbes2/pbes2.cpp
index a6590938a..384fb1e6a 100644
--- a/src/lib/pubkey/pbes2/pbes2.cpp
+++ b/src/lib/pubkey/pbes2/pbes2.cpp
@@ -15,44 +15,154 @@
#include <botan/oids.h>
#include <botan/rng.h>
+#if defined(BOTAN_HAS_SCRYPT)
+ #include <botan/scrypt.h>
+#endif
+
namespace Botan {
namespace {
-/*
-* Encode PKCS#5 PBES2 parameters
-*/
-std::vector<uint8_t> encode_pbes2_params(const std::string& cipher,
- const std::string& prf,
- const secure_vector<uint8_t>& salt,
- const secure_vector<uint8_t>& iv,
- size_t iterations,
- size_t key_length)
+SymmetricKey derive_key(const std::string& passphrase,
+ const AlgorithmIdentifier& kdf_algo,
+ size_t default_key_size)
{
- std::vector<uint8_t> output;
+ if(kdf_algo.get_oid() == OIDS::lookup("PKCS5.PBKDF2"))
+ {
+ secure_vector<uint8_t> salt;
+ size_t iterations = 0, key_length = 0;
- std::vector<uint8_t> pbkdf2_params;
+ AlgorithmIdentifier prf_algo;
+ BER_Decoder(kdf_algo.get_parameters())
+ .start_cons(SEQUENCE)
+ .decode(salt, OCTET_STRING)
+ .decode(iterations)
+ .decode_optional(key_length, INTEGER, UNIVERSAL)
+ .decode_optional(prf_algo, SEQUENCE, CONSTRUCTED,
+ AlgorithmIdentifier("HMAC(SHA-160)",
+ AlgorithmIdentifier::USE_NULL_PARAM))
+ .end_cons();
- DER_Encoder(pbkdf2_params)
- .start_cons(SEQUENCE)
- .encode(salt, OCTET_STRING)
- .encode(iterations)
- .encode(key_length)
- .encode_if(prf != "HMAC(SHA-160)",
- AlgorithmIdentifier(prf, AlgorithmIdentifier::USE_NULL_PARAM))
- .end_cons();
+ if(salt.size() < 8)
+ throw Decoding_Error("PBE-PKCS5 v2.0: Encoded salt is too small");
- DER_Encoder(output)
- .start_cons(SEQUENCE)
- .encode(AlgorithmIdentifier("PKCS5.PBKDF2", pbkdf2_params))
- .encode(
- AlgorithmIdentifier(cipher,
- DER_Encoder().encode(iv, OCTET_STRING).get_contents_unlocked()
- )
- )
- .end_cons();
+ 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;
- return output;
+ return pbkdf->pbkdf_iterations(key_length, passphrase, salt.data(), salt.size(), iterations);
+ }
+#if defined(BOTAN_HAS_SCRYPT)
+ else if(kdf_algo.get_oid() == OIDS::lookup("Scrypt"))
+ {
+ secure_vector<uint8_t> salt;
+ size_t N = 0, r = 0, p = 0;
+ size_t key_length = 0;
+
+ AlgorithmIdentifier prf_algo;
+ BER_Decoder(kdf_algo.get_parameters())
+ .start_cons(SEQUENCE)
+ .decode(salt, OCTET_STRING)
+ .decode(N)
+ .decode(r)
+ .decode(p)
+ .decode_optional(key_length, INTEGER, UNIVERSAL)
+ .end_cons();
+
+ if(key_length == 0)
+ key_length = default_key_size;
+
+ secure_vector<uint8_t> output(key_length);
+ scrypt(output.data(), output.size(), passphrase,
+ salt.data(), salt.size(), N, r, p);
+
+ return SymmetricKey(output);
+ }
+#endif
+ else
+ throw Decoding_Error("PBE-PKCS5 v2.0: Unknown KDF algorithm " +
+ kdf_algo.get_oid().as_string());
+ }
+
+secure_vector<uint8_t> derive_key(const std::string& passphrase,
+ const std::string& digest,
+ RandomNumberGenerator& rng,
+ size_t* msec_in_iterations_out,
+ size_t iterations_if_msec_null,
+ size_t key_length,
+ AlgorithmIdentifier& kdf_algo)
+ {
+ const secure_vector<uint8_t> salt = rng.random_vec(12);
+
+ if(digest == "Scrypt")
+ {
+#if defined(BOTAN_HAS_SCRYPT)
+
+ Scrypt_Params params(32768, 8, 4);
+
+ if(msec_in_iterations_out)
+ params = Scrypt_Params(std::chrono::milliseconds(*msec_in_iterations_out));
+ else
+ params = Scrypt_Params(iterations_if_msec_null);
+
+ secure_vector<uint8_t> key(key_length);
+ scrypt(key.data(), key.size(), passphrase,
+ salt.data(), salt.size(), params);
+
+ 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(key_length)
+ .end_cons();
+
+ kdf_algo = AlgorithmIdentifier(OIDS::lookup("Scrypt"), scrypt_params);
+ return key;
+#else
+ throw Not_Implemented("Scrypt is not available in this build");
+#endif
+ }
+ else
+ {
+ const std::string prf = "HMAC(" + digest + ")";
+
+ std::unique_ptr<PBKDF> pbkdf(get_pbkdf("PBKDF2(" + prf + ")"));
+
+ size_t iterations = iterations_if_msec_null;
+
+ secure_vector<uint8_t> key;
+
+ 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;
+ }
+ else
+ {
+ key = pbkdf->pbkdf_iterations(key_length, passphrase, salt.data(), salt.size(), iterations);
+ }
+
+ std::vector<uint8_t> pbkdf2_params;
+
+ DER_Encoder(pbkdf2_params)
+ .start_cons(SEQUENCE)
+ .encode(salt, OCTET_STRING)
+ .encode(iterations)
+ .encode(key_length)
+ .encode_if(prf != "HMAC(SHA-160)",
+ AlgorithmIdentifier(prf, AlgorithmIdentifier::USE_NULL_PARAM))
+ .end_cons();
+
+ kdf_algo = AlgorithmIdentifier("PKCS5.PBKDF2", pbkdf2_params);
+ return key;
+ }
}
/*
@@ -64,17 +174,13 @@ pbes2_encrypt_shared(const secure_vector<uint8_t>& key_bits,
size_t* msec_in_iterations_out,
size_t iterations_if_msec_null,
const std::string& cipher,
- const std::string& digest,
+ const std::string& prf,
RandomNumberGenerator& rng)
{
- const std::string prf = "HMAC(" + digest + ")";
-
const std::vector<std::string> cipher_spec = split_on(cipher, '/');
if(cipher_spec.size() != 2)
throw Decoding_Error("PBE-PKCS5 v2.0: Invalid cipher spec " + cipher);
- const secure_vector<uint8_t> salt = rng.random_vec(12);
-
if(cipher_spec[1] != "CBC" && cipher_spec[1] != "GCM")
throw Decoding_Error("PBE-PKCS5 v2.0: Don't know param format for " + cipher);
@@ -83,35 +189,37 @@ pbes2_encrypt_shared(const secure_vector<uint8_t>& key_bits,
if(!enc)
throw Decoding_Error("PBE-PKCS5 cannot encrypt no cipher " + cipher);
- std::unique_ptr<PBKDF> pbkdf(get_pbkdf("PBKDF2(" + prf + ")"));
-
const size_t key_length = enc->key_spec().maximum_keylength();
+ const secure_vector<uint8_t> iv = rng.random_vec(enc->default_nonce_length());
- secure_vector<uint8_t> iv = rng.random_vec(enc->default_nonce_length());
-
- size_t iterations = iterations_if_msec_null;
+ AlgorithmIdentifier kdf_algo;
- if(msec_in_iterations_out)
- {
- std::chrono::milliseconds msec(*msec_in_iterations_out);
- enc->set_key(pbkdf->derive_key(key_length, passphrase, salt.data(), salt.size(), msec, iterations).bits_of());
- *msec_in_iterations_out = iterations;
- }
- else
- {
- enc->set_key(pbkdf->pbkdf_iterations(key_length, passphrase, salt.data(), salt.size(), iterations));
- }
+ const secure_vector<uint8_t> derived_key =
+ derive_key(passphrase, prf, rng,
+ msec_in_iterations_out, iterations_if_msec_null,
+ key_length, kdf_algo);
+ enc->set_key(derived_key);
enc->start(iv);
- secure_vector<uint8_t> buf = key_bits;
- enc->finish(buf);
+ secure_vector<uint8_t> ctext = key_bits;
+ enc->finish(ctext);
+
+ std::vector<uint8_t> pbes2_params;
+
+ DER_Encoder(pbes2_params)
+ .start_cons(SEQUENCE)
+ .encode(kdf_algo)
+ .encode(
+ AlgorithmIdentifier(cipher,
+ DER_Encoder().encode(iv, OCTET_STRING).get_contents_unlocked()
+ )
+ )
+ .end_cons();
- AlgorithmIdentifier id(
- OIDS::lookup("PBE-PKCS5v20"),
- encode_pbes2_params(cipher, prf, salt, iv, iterations, key_length));
+ AlgorithmIdentifier id(OIDS::lookup("PBE-PKCS5v20"), pbes2_params);
- return std::make_pair(id, unlock(buf));
+ return std::make_pair(id, unlock(ctext));
}
@@ -173,25 +281,6 @@ pbes2_decrypt(const secure_vector<uint8_t>& key_bits,
.decode(enc_algo)
.end_cons();
- AlgorithmIdentifier prf_algo;
-
- if(kdf_algo.get_oid() != OIDS::lookup("PKCS5.PBKDF2"))
- throw Decoding_Error("PBE-PKCS5 v2.0: Unknown KDF algorithm " +
- kdf_algo.get_oid().as_string());
-
- secure_vector<uint8_t> salt;
- size_t iterations = 0, key_length = 0;
-
- BER_Decoder(kdf_algo.get_parameters())
- .start_cons(SEQUENCE)
- .decode(salt, OCTET_STRING)
- .decode(iterations)
- .decode_optional(key_length, INTEGER, UNIVERSAL)
- .decode_optional(prf_algo, SEQUENCE, CONSTRUCTED,
- AlgorithmIdentifier("HMAC(SHA-160)",
- AlgorithmIdentifier::USE_NULL_PARAM))
- .end_cons();
-
const std::string cipher = OIDS::lookup(enc_algo.get_oid());
const std::vector<std::string> cipher_spec = split_on(cipher, '/');
if(cipher_spec.size() != 2)
@@ -199,24 +288,14 @@ pbes2_decrypt(const secure_vector<uint8_t>& key_bits,
if(cipher_spec[1] != "CBC" && cipher_spec[1] != "GCM")
throw Decoding_Error("PBE-PKCS5 v2.0: Don't know param format for " + cipher);
- if(salt.size() < 8)
- throw Decoding_Error("PBE-PKCS5 v2.0: Encoded salt is too small");
-
secure_vector<uint8_t> iv;
BER_Decoder(enc_algo.get_parameters()).decode(iv, OCTET_STRING).verify_end();
- const std::string prf = OIDS::lookup(prf_algo.get_oid());
-
- std::unique_ptr<PBKDF> pbkdf(get_pbkdf("PBKDF2(" + prf + ")"));
-
std::unique_ptr<Cipher_Mode> dec = Cipher_Mode::create(cipher, DECRYPTION);
if(!dec)
throw Decoding_Error("PBE-PKCS5 cannot decrypt no cipher " + cipher);
- if(key_length == 0)
- key_length = dec->key_spec().maximum_keylength();
-
- dec->set_key(pbkdf->pbkdf_iterations(key_length, passphrase, salt.data(), salt.size(), iterations));
+ dec->set_key(derive_key(passphrase, kdf_algo, dec->key_spec().maximum_keylength()));
dec->start(iv);
diff --git a/src/tests/test_pubkey.cpp b/src/tests/test_pubkey.cpp
index 38bfaf3a8..ea794b9ba 100644
--- a/src/tests/test_pubkey.cpp
+++ b/src/tests/test_pubkey.cpp
@@ -500,6 +500,63 @@ std::vector<std::string> PK_Key_Generation_Test::possible_providers(
return Test::provider_filter(pk_provider);
}
+namespace {
+
+void test_pbe_roundtrip(Test::Result& result,
+ const Botan::Private_Key& key,
+ const std::string& pbe_algo,
+ const std::string& passphrase)
+ {
+ try
+ {
+ Botan::DataSource_Memory data_src(
+ Botan::PKCS8::PEM_encode(key, Test::rng(), passphrase,
+ std::chrono::milliseconds(10),
+ pbe_algo));
+
+ std::unique_ptr<Botan::Private_Key> loaded(
+ Botan::PKCS8::load_key(data_src, Test::rng(), passphrase));
+
+ result.confirm("recovered private key from encrypted blob", loaded.get() != nullptr);
+ result.test_eq("reloaded key has same type", loaded->algo_name(), key.algo_name());
+ try
+ {
+ result.confirm("private key passes self tests", loaded->check_key(Test::rng(), true));
+ }
+ catch(Botan::Lookup_Error&) {}
+ }
+ catch(std::exception& e)
+ {
+ result.test_failure("roundtrip encrypted PEM private key", e.what());
+ }
+
+ try
+ {
+ Botan::DataSource_Memory data_src(
+ Botan::PKCS8::BER_encode(key, Test::rng(), passphrase,
+ std::chrono::milliseconds(10),
+ pbe_algo));
+
+ std::unique_ptr<Botan::Private_Key> loaded(
+ Botan::PKCS8::load_key(data_src, Test::rng(), passphrase));
+
+ result.confirm("recovered private key from BER blob", loaded.get() != nullptr);
+ result.test_eq("reloaded key has same type", loaded->algo_name(), key.algo_name());
+
+ try
+ {
+ result.confirm("private key passes self tests", loaded->check_key(Test::rng(), true));
+ }
+ catch(Botan::Lookup_Error&) {}
+ }
+ catch(std::exception& e)
+ {
+ result.test_failure("roundtrip encrypted BER private key", e.what());
+ }
+ }
+
+}
+
std::vector<Test::Result> PK_Key_Generation_Test::run()
{
std::vector<Test::Result> results;
@@ -615,56 +672,14 @@ std::vector<Test::Result> PK_Key_Generation_Test::run()
#if defined(BOTAN_HAS_PKCS5_PBES2) && defined(BOTAN_HAS_AES) && defined(BOTAN_HAS_SHA2_32)
- const std::string pbe_algo = "PBE-PKCS5v20(AES-128/CBC,SHA-256)";
- const std::string passphrase = Test::random_password();
-
- try
- {
- Botan::DataSource_Memory data_src(
- Botan::PKCS8::PEM_encode(key, Test::rng(), passphrase,
- std::chrono::milliseconds(10),
- pbe_algo));
-
- std::unique_ptr<Botan::Private_Key> loaded(
- Botan::PKCS8::load_key(data_src, Test::rng(), passphrase));
-
- result.confirm("recovered private key from encrypted blob", loaded.get() != nullptr);
- result.test_eq("reloaded key has same type", loaded->algo_name(), key.algo_name());
- try
- {
- result.confirm("private key passes self tests", loaded->check_key(Test::rng(), true));
- }
- catch(Botan::Lookup_Error&) {}
- }
- catch(std::exception& e)
- {
- result.test_failure("roundtrip encrypted PEM private key", e.what());
- }
-
- try
- {
- Botan::DataSource_Memory data_src(
- Botan::PKCS8::BER_encode(key, Test::rng(), passphrase,
- std::chrono::milliseconds(10),
- pbe_algo));
-
- std::unique_ptr<Botan::Private_Key> loaded(
- Botan::PKCS8::load_key(data_src, Test::rng(), passphrase));
+ test_pbe_roundtrip(result, key, "PBE-PKCS5v20(AES-128/CBC,SHA-256)", Test::random_password());
+#endif
- result.confirm("recovered private key from BER blob", loaded.get() != nullptr);
- result.test_eq("reloaded key has same type", loaded->algo_name(), key.algo_name());
+#if defined(BOTAN_HAS_PKCS5_PBES2) && defined(BOTAN_HAS_AES) && defined(BOTAN_HAS_SCRYPT)
- try
- {
- result.confirm("private key passes self tests", loaded->check_key(Test::rng(), true));
- }
- catch(Botan::Lookup_Error&) {}
- }
- catch(std::exception& e)
- {
- result.test_failure("roundtrip encrypted BER private key", e.what());
- }
+ test_pbe_roundtrip(result, key, "PBE-PKCS5v20(AES-128/CBC,Scrypt)", Test::random_password());
#endif
+
}
result.end_timer();