From 8bbec86f8984b52b5d0cce8cd1309563d2b294cc Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Sun, 26 Feb 2017 15:48:35 -0500 Subject: Add ability to specify iterations when encrypting a private key GH #896 --- src/lib/ffi/ffi.cpp | 82 ++++++++++++++++++++++++++++++++++++-- src/lib/ffi/ffi.h | 27 +++++++++++++ src/lib/pubkey/pbes2/pbes2.cpp | 79 +++++++++++++++++++++++++++++++------ src/lib/pubkey/pbes2/pbes2.h | 37 ++++++++++++++++++ src/lib/pubkey/pkcs8.cpp | 86 +++++++++++++++++++++++++++++++++++++++- src/lib/pubkey/pkcs8.h | 89 ++++++++++++++++++++++++++++++++++++++++++ src/tests/test_ffi.cpp | 28 ++++--------- 7 files changed, 390 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/lib/ffi/ffi.cpp b/src/lib/ffi/ffi.cpp index 80d7ec611..07c20ce3b 100644 --- a/src/lib/ffi/ffi.cpp +++ b/src/lib/ffi/ffi.cpp @@ -983,19 +983,93 @@ int botan_privkey_export_encrypted(botan_privkey_t key, uint8_t out[], size_t* out_len, botan_rng_t rng_obj, const char* pass, - const char* pbe, + const char* /*ignored - pbe*/, uint32_t flags) { + return botan_privkey_export_encrypted_pbkdf_iter(key, out, out_len, rng_obj, pass, 100000, nullptr, nullptr, flags); + } + +int botan_privkey_export_encrypted_pbkdf_msec(botan_privkey_t key, + uint8_t out[], size_t* out_len, + botan_rng_t rng_obj, + const char* pass, + uint32_t pbkdf_msec, + size_t* pbkdf_iters_out, + const char* maybe_cipher, + const char* maybe_pbkdf_hash, + uint32_t flags) + { + return BOTAN_FFI_DO(Botan::Private_Key, key, k, { + const std::chrono::milliseconds pbkdf_time(pbkdf_msec); + Botan::RandomNumberGenerator& rng = safe_get(rng_obj); + + std::string cipher; + if(maybe_cipher) + { + cipher = maybe_cipher; + } + + std::string pbkdf_hash; + if(maybe_pbkdf_hash) + { + pbkdf_hash = maybe_pbkdf_hash; + } + + if(flags == BOTAN_PRIVKEY_EXPORT_FLAG_DER) + { + return write_vec_output(out, out_len, + Botan::PKCS8::BER_encode_encrypted_pbkdf_msec(k, rng, pass, pbkdf_time, pbkdf_iters_out, cipher, pbkdf_hash)); + } + else if(flags == BOTAN_PRIVKEY_EXPORT_FLAG_PEM) + { + return write_str_output(out, out_len, + Botan::PKCS8::PEM_encode_encrypted_pbkdf_msec(k, rng, pass, pbkdf_time, pbkdf_iters_out, cipher, pbkdf_hash)); + } + else + { + return -2; + } + }); + } + +int botan_privkey_export_encrypted_pbkdf_iter(botan_privkey_t key, + uint8_t out[], size_t* out_len, + botan_rng_t rng_obj, + const char* pass, + size_t pbkdf_iter, + const char* maybe_cipher, + const char* maybe_pbkdf_hash, + uint32_t flags) + { return BOTAN_FFI_DO(Botan::Private_Key, key, k, { - auto pbkdf_time = std::chrono::milliseconds(300); Botan::RandomNumberGenerator& rng = safe_get(rng_obj); + std::string cipher; + if(maybe_cipher) + { + cipher = maybe_cipher; + } + + std::string pbkdf_hash; + if(maybe_pbkdf_hash) + { + pbkdf_hash = maybe_pbkdf_hash; + } + if(flags == BOTAN_PRIVKEY_EXPORT_FLAG_DER) - return write_vec_output(out, out_len, Botan::PKCS8::BER_encode(k, rng, pass, pbkdf_time, pbe)); + { + return write_vec_output(out, out_len, + Botan::PKCS8::BER_encode_encrypted_pbkdf_iter(k, rng, pass, pbkdf_iter, cipher, pbkdf_hash)); + } else if(flags == BOTAN_PRIVKEY_EXPORT_FLAG_PEM) - return write_str_output(out, out_len, Botan::PKCS8::PEM_encode(k, rng, pass, pbkdf_time, pbe)); + { + return write_str_output(out, out_len, + Botan::PKCS8::PEM_encode_encrypted_pbkdf_iter(k, rng, pass, pbkdf_iter, cipher, pbkdf_hash)); + } else + { return -2; + } }); } diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index 264c3d24d..98792d4bd 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -506,6 +506,7 @@ BOTAN_DLL int botan_privkey_export(botan_privkey_t key, /* * Set encryption_algo to NULL or "" to have the library choose a default (recommended) */ +BOTAN_DEPRECATED("Use botan_privkey_export_encrypted_pbkdf_{msec,iter}") BOTAN_DLL int botan_privkey_export_encrypted(botan_privkey_t key, uint8_t out[], size_t* out_len, botan_rng_t rng, @@ -513,6 +514,32 @@ BOTAN_DLL int botan_privkey_export_encrypted(botan_privkey_t key, const char* encryption_algo, uint32_t flags); +/* +* Export a private key, running PBKDF for specified amount of time +* @param key the private key to export +*/ +BOTAN_DLL int botan_privkey_export_encrypted_pbkdf_msec(botan_privkey_t key, + uint8_t out[], size_t* out_len, + botan_rng_t rng, + const char* passphrase, + uint32_t pbkdf_msec_runtime, + size_t* pbkdf_iterations_out, + const char* cipher_algo, + const char* pbkdf_algo, + uint32_t flags); + +/* +* Export a private key using the specified number of iterations. +*/ +BOTAN_DLL int botan_privkey_export_encrypted_pbkdf_iter(botan_privkey_t key, + uint8_t out[], size_t* out_len, + botan_rng_t rng, + const char* passphrase, + size_t pbkdf_iterations, + const char* cipher_algo, + const char* pbkdf_algo, + uint32_t flags); + typedef struct botan_pubkey_struct* botan_pubkey_t; BOTAN_DLL int botan_pubkey_load(botan_pubkey_t* key, const uint8_t bits[], size_t len); diff --git a/src/lib/pubkey/pbes2/pbes2.cpp b/src/lib/pubkey/pbes2/pbes2.cpp index 01bab76bb..b0a7f336d 100644 --- a/src/lib/pubkey/pbes2/pbes2.cpp +++ b/src/lib/pubkey/pbes2/pbes2.cpp @@ -55,18 +55,17 @@ std::vector encode_pbes2_params(const std::string& cipher, .get_contents_unlocked(); } -} - /* -* PKCS#5 v2.0 PBE Constructor +* PKCS#5 v2.0 PBE Encryption */ std::pair> -pbes2_encrypt(const secure_vector& key_bits, - const std::string& passphrase, - std::chrono::milliseconds msec, - const std::string& cipher, - const std::string& digest, - RandomNumberGenerator& rng) +pbes2_encrypt_shared(const secure_vector& key_bits, + const std::string& passphrase, + size_t* msec_in_iterations_out, + size_t iterations_if_msec_null, + const std::string& cipher, + const std::string& digest, + RandomNumberGenerator& rng) { const std::string prf = "HMAC(" + digest + ")"; @@ -87,12 +86,22 @@ pbes2_encrypt(const secure_vector& key_bits, std::unique_ptr pbkdf(get_pbkdf("PBKDF2(" + prf + ")")); const size_t key_length = enc->key_spec().maximum_keylength(); - size_t iterations = 0; + secure_vector iv = rng.random_vec(enc->default_nonce_length()); - enc->set_key(pbkdf->derive_key(key_length, passphrase, salt.data(), salt.size(), - msec, iterations).bits_of()); + size_t iterations = iterations_if_msec_null; + + 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)); + } enc->start(iv); secure_vector buf = key_bits; @@ -105,6 +114,52 @@ pbes2_encrypt(const secure_vector& key_bits, return std::make_pair(id, unlock(buf)); } + +} + +std::pair> +pbes2_encrypt(const secure_vector& key_bits, + const std::string& passphrase, + std::chrono::milliseconds msec, + const std::string& cipher, + const std::string& digest, + RandomNumberGenerator& rng) + { + size_t msec_in_iterations_out = msec.count(); + return pbes2_encrypt_shared(key_bits, passphrase, &msec_in_iterations_out, 0, cipher, digest, rng); + // return value msec_in_iterations_out discarded + } + +std::pair> +pbes2_encrypt_msec(const secure_vector& key_bits, + const std::string& passphrase, + std::chrono::milliseconds msec, + size_t* out_iterations_if_nonnull, + const std::string& cipher, + const std::string& digest, + RandomNumberGenerator& rng) + { + size_t msec_in_iterations_out = msec.count(); + + auto ret = pbes2_encrypt_shared(key_bits, passphrase, &msec_in_iterations_out, 0, cipher, digest, rng); + + if(out_iterations_if_nonnull) + *out_iterations_if_nonnull = msec_in_iterations_out; + + return ret; + } + +std::pair> +pbes2_encrypt_iter(const secure_vector& key_bits, + const std::string& passphrase, + size_t pbkdf_iter, + const std::string& cipher, + const std::string& digest, + RandomNumberGenerator& rng) + { + return pbes2_encrypt_shared(key_bits, passphrase, nullptr, pbkdf_iter, cipher, digest, rng); + } + secure_vector pbes2_decrypt(const secure_vector& key_bits, const std::string& passphrase, diff --git a/src/lib/pubkey/pbes2/pbes2.h b/src/lib/pubkey/pbes2/pbes2.h index e50896c6d..951ba3178 100644 --- a/src/lib/pubkey/pbes2/pbes2.h +++ b/src/lib/pubkey/pbes2/pbes2.h @@ -31,6 +31,43 @@ BOTAN_DLL pbes2_encrypt(const secure_vector& key_bits, const std::string& digest, RandomNumberGenerator& rng); +/** +* Encrypt with PBES2 from PKCS #5 v2.0 +* @param key_bits the input +* @param passphrase the passphrase to use for encryption +* @param msec how many milliseconds to run PBKDF2 +* @param out_iterations_if_nonnull if not null, set to the number +* of PBKDF iterations used +* @param cipher specifies the block cipher to use to encrypt +* @param digest specifies the PRF to use with PBKDF2 (eg "HMAC(SHA-1)") +* @param rng a random number generator +*/ +std::pair> +BOTAN_DLL pbes2_encrypt_msec(const secure_vector& key_bits, + const std::string& passphrase, + std::chrono::milliseconds msec, + size_t* out_iterations_if_nonnull, + const std::string& cipher, + const std::string& digest, + RandomNumberGenerator& rng); + +/** +* Encrypt with PBES2 from PKCS #5 v2.0 +* @param key_bits the input +* @param passphrase the passphrase to use for encryption +* @param iterations how many iterations to run PBKDF2 +* @param cipher specifies the block cipher to use to encrypt +* @param digest specifies the PRF to use with PBKDF2 (eg "HMAC(SHA-1)") +* @param rng a random number generator +*/ +std::pair> +BOTAN_DLL pbes2_encrypt_iter(const secure_vector& key_bits, + const std::string& passphrase, + size_t iterations, + const std::string& cipher, + const std::string& digest, + RandomNumberGenerator& rng); + /** * Decrypt a PKCS #5 v2.0 encrypted stream * @param key_bits the input diff --git a/src/lib/pubkey/pkcs8.cpp b/src/lib/pubkey/pkcs8.cpp index c4294d563..010516973 100644 --- a/src/lib/pubkey/pkcs8.cpp +++ b/src/lib/pubkey/pkcs8.cpp @@ -175,8 +175,8 @@ std::vector BER_encode(const Private_Key& key, const auto pbe_params = choose_pbe_params(pbe_algo, key.algo_name()); const std::pair> pbe_info = - pbes2_encrypt(PKCS8::BER_encode(key), pass, msec, - pbe_params.first, pbe_params.second, rng); + pbes2_encrypt_msec(PKCS8::BER_encode(key), pass, msec, nullptr, + pbe_params.first, pbe_params.second, rng); return DER_Encoder() .start_cons(SEQUENCE) @@ -202,6 +202,88 @@ std::string PEM_encode(const Private_Key& key, "ENCRYPTED PRIVATE KEY"); } +/* +* BER encode a PKCS #8 private key, encrypted +*/ +std::vector BER_encode_encrypted_pbkdf_iter(const Private_Key& key, + RandomNumberGenerator& rng, + const std::string& pass, + size_t pbkdf_iterations, + const std::string& cipher, + const std::string& pbkdf_hash) + { + const std::pair> pbe_info = + pbes2_encrypt_iter(key.private_key_info(), + pass, pbkdf_iterations, + cipher.empty() ? "AES-256/CBC" : cipher, + pbkdf_hash.empty() ? "SHA-256" : pbkdf_hash, + rng); + + return DER_Encoder() + .start_cons(SEQUENCE) + .encode(pbe_info.first) + .encode(pbe_info.second, OCTET_STRING) + .end_cons() + .get_contents_unlocked(); + } + +/* +* PEM encode a PKCS #8 private key, encrypted +*/ +std::string PEM_encode_encrypted_pbkdf_iter(const Private_Key& key, + RandomNumberGenerator& rng, + const std::string& pass, + size_t pbkdf_iterations, + const std::string& cipher, + const std::string& pbkdf_hash) + { + return PEM_Code::encode( + PKCS8::BER_encode_encrypted_pbkdf_iter(key, rng, pass, pbkdf_iterations, cipher, pbkdf_hash), + "ENCRYPTED PRIVATE KEY"); + } + +/* +* BER encode a PKCS #8 private key, encrypted +*/ +std::vector BER_encode_encrypted_pbkdf_msec(const Private_Key& key, + RandomNumberGenerator& rng, + const std::string& pass, + std::chrono::milliseconds pbkdf_msec, + size_t* pbkdf_iterations, + const std::string& cipher, + const std::string& pbkdf_hash) + { + const std::pair> pbe_info = + pbes2_encrypt_msec(key.private_key_info(), pass, + pbkdf_msec, pbkdf_iterations, + cipher.empty() ? "AES-256/CBC" : cipher, + pbkdf_hash.empty() ? "SHA-256" : pbkdf_hash, + rng); + + return DER_Encoder() + .start_cons(SEQUENCE) + .encode(pbe_info.first) + .encode(pbe_info.second, OCTET_STRING) + .end_cons() + .get_contents_unlocked(); + } + +/* +* PEM encode a PKCS #8 private key, encrypted +*/ +std::string PEM_encode_encrypted_pbkdf_msec(const Private_Key& key, + RandomNumberGenerator& rng, + const std::string& pass, + std::chrono::milliseconds pbkdf_msec, + size_t* pbkdf_iterations, + const std::string& cipher, + const std::string& pbkdf_hash) + { + return PEM_Code::encode( + PKCS8::BER_encode_encrypted_pbkdf_msec(key, rng, pass, pbkdf_msec, pbkdf_iterations, cipher, pbkdf_hash), + "ENCRYPTED PRIVATE KEY"); + } + namespace { /* diff --git a/src/lib/pubkey/pkcs8.h b/src/lib/pubkey/pkcs8.h index 309ca2798..014ce8714 100644 --- a/src/lib/pubkey/pkcs8.h +++ b/src/lib/pubkey/pkcs8.h @@ -79,6 +79,95 @@ PEM_encode(const Private_Key& key, std::chrono::milliseconds msec = std::chrono::milliseconds(300), const std::string& pbe_algo = ""); +/** +* Encrypt a key using PKCS #8 encryption and a fixed iteration count +* @param key the key to encode +* @param rng the rng to use +* @param pass the password to use for encryption +* @param pbkdf_iter number of interations to run PBKDF2 +* @param cipher if non-empty specifies the cipher to use. CBC and GCM modes +* are supported, for example "AES-128/CBC", "AES-256/GCM", "Serpent/CBC". +* If empty a suitable default is chosen. +* @param pbkdf_hash if non-empty specifies the PBKDF hash function to use. +* For example "SHA-256" or "SHA-384". If empty a suitable default is chosen. +* @return encrypted key in binary BER form +*/ +BOTAN_DLL std::vector +BER_encode_encrypted_pbkdf_iter(const Private_Key& key, + RandomNumberGenerator& rng, + const std::string& pass, + size_t pbkdf_iter, + const std::string& cipher = "", + const std::string& pbkdf_hash = ""); + +/** +* Get a string containing a PEM encoded private key, encrypting it with a +* password. +* @param key the key to encode +* @param rng the rng to use +* @param pass the password to use for encryption +* @param pbkdf_iter number of iterations to run PBKDF +* @param cipher if non-empty specifies the cipher to use. CBC and GCM modes +* are supported, for example "AES-128/CBC", "AES-256/GCM", "Serpent/CBC". +* If empty a suitable default is chosen. +* @param pbkdf_hash if non-empty specifies the PBKDF hash function to use. +* For example "SHA-256" or "SHA-384". If empty a suitable default is chosen. +* @return encrypted key in PEM form +*/ +BOTAN_DLL std::string +PEM_encode_encrypted_pbkdf_iter(const Private_Key& key, + RandomNumberGenerator& rng, + const std::string& pass, + size_t pbkdf_iter, + const std::string& cipher = "", + const std::string& pbkdf_hash = ""); + +/** +* Encrypt a key using PKCS #8 encryption and a variable iteration count +* @param key the key to encode +* @param rng the rng to use +* @param pass the password to use for encryption +* @param pbkdf_msec how long to run PBKDF2 +* @param pbkdf_iterations if non-null, set to the number of iterations used +* @param cipher if non-empty specifies the cipher to use. CBC and GCM modes +* are supported, for example "AES-128/CBC", "AES-256/GCM", "Serpent/CBC". +* If empty a suitable default is chosen. +* @param pbkdf_hash if non-empty specifies the PBKDF hash function to use. +* For example "SHA-256" or "SHA-384". If empty a suitable default is chosen. +* @return encrypted key in binary BER form +*/ +BOTAN_DLL std::vector +BER_encode_encrypted_pbkdf_msec(const Private_Key& key, + RandomNumberGenerator& rng, + const std::string& pass, + std::chrono::milliseconds pbkdf_msec, + size_t* pbkdf_iterations, + const std::string& cipher = "", + const std::string& pbkdf_hash = ""); + +/** +* Get a string containing a PEM encoded private key, encrypting it with a +* password. +* @param key the key to encode +* @param rng the rng to use +* @param pass the password to use for encryption +* @param pbkdf_iter number of iterations to run PBKDF +* @param cipher if non-empty specifies the cipher to use. CBC and GCM modes +* are supported, for example "AES-128/CBC", "AES-256/GCM", "Serpent/CBC". +* If empty a suitable default is chosen. +* @param pbkdf_hash if non-empty specifies the PBKDF hash function to use. +* For example "SHA-256" or "SHA-384". If empty a suitable default is chosen. +* @return encrypted key in PEM form +*/ +BOTAN_DLL std::string +PEM_encode_encrypted_pbkdf_msec(const Private_Key& key, + RandomNumberGenerator& rng, + const std::string& pass, + std::chrono::milliseconds pbkdf_msec, + size_t* pbkdf_iterations, + const std::string& cipher = "", + const std::string& pbkdf_hash = ""); + /** * Load an encrypted key from a data source. * @param source the data source providing the encoded key diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp index 3e272d9cb..455318ce8 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -400,6 +400,8 @@ class FFI_Unit_Tests : public Test private: void ffi_test_pubkey_export(Test::Result& result, botan_pubkey_t pub, botan_privkey_t priv, botan_rng_t rng) { + const size_t pbkdf_iter = 1000; + // export public key size_t pubkey_len = 0; TEST_FFI_RC(BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE, botan_pubkey_export, (pub, nullptr, &pubkey_len, BOTAN_PRIVKEY_EXPORT_FLAG_DER)); @@ -417,24 +419,10 @@ class FFI_Unit_Tests : public Test std::vector privkey; size_t privkey_len = 0; - /* - * botan_privkey_export is bogus for several reasons. first it hardcodes a 300 msec - * pbkdf, instead of taking that as an argument. secondly, calling it twice not only - * returns different results (due to the encryption) but they may have different sizes, - * if the number of PBKDF iterations that is used in the two runs differs greatly, and - * ends up encoding as fewer bytes in the variable length ASN.1 encoding used in PKCS #8 - * private key encryption. - * - * here request the size but then add a few bytes. this is an attempt to avoid occasional - * cases on CI where the above case occurs, and the build fails because on the second - * call, more space was required than the first call had returned. - */ - const size_t privkey_size_slop = 64; - // call with nullptr to query the length TEST_FFI_RC(BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE, botan_privkey_export, (priv, nullptr, &privkey_len, BOTAN_PRIVKEY_EXPORT_FLAG_DER)); - privkey.resize(privkey_len + privkey_size_slop); + privkey.resize(privkey_len); privkey_len = privkey.size(); // set buffer size TEST_FFI_OK(botan_privkey_export, (priv, privkey.data(), &privkey_len, BOTAN_PRIVKEY_EXPORT_FLAG_DER)); @@ -453,18 +441,18 @@ class FFI_Unit_Tests : public Test // export private key encrypted privkey_len = 0; - TEST_FFI_RC(BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE, botan_privkey_export_encrypted, (priv, nullptr, &privkey_len, rng, "password", "", BOTAN_PRIVKEY_EXPORT_FLAG_DER)); + TEST_FFI_RC(BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE, botan_privkey_export_encrypted_pbkdf_iter, (priv, nullptr, &privkey_len, rng, "password", pbkdf_iter, "", "", BOTAN_PRIVKEY_EXPORT_FLAG_DER)); - privkey.resize(privkey_len + privkey_size_slop); + privkey.resize(privkey_len); privkey_len = privkey.size(); - TEST_FFI_OK(botan_privkey_export_encrypted, (priv, privkey.data(), &privkey_len, rng, "password", "", BOTAN_PRIVKEY_EXPORT_FLAG_DER)); + TEST_FFI_OK(botan_privkey_export_encrypted_pbkdf_iter, (priv, privkey.data(), &privkey_len, rng, "password", pbkdf_iter, "", "", BOTAN_PRIVKEY_EXPORT_FLAG_DER)); privkey_len = 0; - TEST_FFI_RC(BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE, botan_privkey_export_encrypted, (priv, nullptr, &privkey_len, rng, "password", "", BOTAN_PRIVKEY_EXPORT_FLAG_PEM)); + TEST_FFI_RC(BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE, botan_privkey_export_encrypted_pbkdf_iter, (priv, nullptr, &privkey_len, rng, "password", pbkdf_iter, "", "", BOTAN_PRIVKEY_EXPORT_FLAG_PEM)); privkey.resize(privkey_len); - TEST_FFI_OK(botan_privkey_export_encrypted, (priv, privkey.data(), &privkey_len, rng, "password", "", BOTAN_PRIVKEY_EXPORT_FLAG_PEM)); + TEST_FFI_OK(botan_privkey_export_encrypted_pbkdf_iter, (priv, privkey.data(), &privkey_len, rng, "password", pbkdf_iter, "", "", BOTAN_PRIVKEY_EXPORT_FLAG_PEM)); // calculate fingerprint size_t strength = 0; -- cgit v1.2.3