diff options
author | Jack Lloyd <[email protected]> | 2018-05-22 22:43:45 -0400 |
---|---|---|
committer | Jack Lloyd <[email protected]> | 2018-05-22 23:26:49 -0400 |
commit | f87b9e4128698951c10e47dca01811a677577ca0 (patch) | |
tree | 0923ea1d3dc2bba07445d3af5916fea7398a5fdd | |
parent | 5df1042ea95e27b58c2a4a96d036a9492e22ef67 (diff) |
Support scrypt for encrypting private keys
-rw-r--r-- | src/cli/speed.cpp | 4 | ||||
-rw-r--r-- | src/lib/pbkdf/scrypt/scrypt.cpp | 73 | ||||
-rw-r--r-- | src/lib/pbkdf/scrypt/scrypt.h | 22 | ||||
-rw-r--r-- | src/lib/pubkey/pbes2/pbes2.cpp | 249 | ||||
-rw-r--r-- | src/tests/test_pubkey.cpp | 109 |
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(); |