aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJack Lloyd <[email protected]>2019-05-31 23:04:32 -0400
committerJack Lloyd <[email protected]>2019-05-31 23:04:32 -0400
commit32e54436797d8cba658f5663cf1a0dae589f8670 (patch)
tree329d69bfd71d53b126c02864d779887b42ce13dd
parentdbf4b8d7418587d4d30c048e1ff13f418944ffc4 (diff)
parent184a7825a8cc449476951de0d2019d8014667c8f (diff)
Merge GH #1990 Add Bcrypt-PBKDF
-rw-r--r--src/lib/block/blowfish/blowfish.cpp14
-rw-r--r--src/lib/block/blowfish/blowfish.h2
-rw-r--r--src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.cpp182
-rw-r--r--src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.h75
-rw-r--r--src/lib/pbkdf/bcrypt_pbkdf/info.txt8
-rw-r--r--src/lib/pbkdf/pwdhash.cpp11
-rw-r--r--src/tests/data/bcrypt_pbkdf.vec184
-rw-r--r--src/tests/test_pbkdf.cpp50
8 files changed, 520 insertions, 6 deletions
diff --git a/src/lib/block/blowfish/blowfish.cpp b/src/lib/block/blowfish/blowfish.cpp
index d219bc22a..ecb9f82e3 100644
--- a/src/lib/block/blowfish/blowfish.cpp
+++ b/src/lib/block/blowfish/blowfish.cpp
@@ -374,7 +374,7 @@ void Blowfish::key_expansion(const uint8_t key[],
*/
void Blowfish::salted_set_key(const uint8_t key[], size_t length,
const uint8_t salt[], size_t salt_length,
- size_t workfactor)
+ size_t workfactor, bool salt_first)
{
BOTAN_ARG_CHECK(salt_length > 0 && salt_length % 4 == 0,
"Invalid salt length for Blowfish salted key schedule");
@@ -398,8 +398,16 @@ void Blowfish::salted_set_key(const uint8_t key[], size_t length,
for(size_t r = 0; r != rounds; ++r)
{
- key_expansion(key, length, nullptr, 0);
- key_expansion(salt, salt_length, nullptr, 0);
+ if(salt_first)
+ {
+ key_expansion(salt, salt_length, nullptr, 0);
+ key_expansion(key, length, nullptr, 0);
+ }
+ else
+ {
+ key_expansion(key, length, nullptr, 0);
+ key_expansion(salt, salt_length, nullptr, 0);
+ }
}
}
}
diff --git a/src/lib/block/blowfish/blowfish.h b/src/lib/block/blowfish/blowfish.h
index d5c318752..97a1b841c 100644
--- a/src/lib/block/blowfish/blowfish.h
+++ b/src/lib/block/blowfish/blowfish.h
@@ -26,7 +26,7 @@ class BOTAN_PUBLIC_API(2,0) Blowfish final : public Block_Cipher_Fixed_Params<8,
*/
void salted_set_key(const uint8_t key[], size_t key_length,
const uint8_t salt[], size_t salt_length,
- size_t workfactor);
+ const size_t workfactor, bool salt_first = false);
BOTAN_DEPRECATED("Use Blowfish::salted_set_key taking salt length")
void eks_key_schedule(const uint8_t key[], size_t key_length,
diff --git a/src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.cpp b/src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.cpp
new file mode 100644
index 000000000..ecdac689a
--- /dev/null
+++ b/src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.cpp
@@ -0,0 +1,182 @@
+/*
+* (C) 2018,2019 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#include <botan/bcrypt_pbkdf.h>
+#include <botan/blowfish.h>
+#include <botan/hash.h>
+#include <botan/internal/timer.h>
+
+namespace Botan {
+
+void Bcrypt_PBKDF::derive_key(uint8_t output[], size_t output_len,
+ const char* password, const size_t password_len,
+ const uint8_t salt[], size_t salt_len) const
+ {
+ bcrypt_pbkdf(output, output_len,
+ password, password_len,
+ salt, salt_len,
+ m_iterations);
+ }
+
+std::string Bcrypt_PBKDF::to_string() const
+ {
+ return "Bcrypt-PBKDF(" + std::to_string(m_iterations) + ")";
+ }
+
+std::string Bcrypt_PBKDF_Family::name() const
+ {
+ return "Bcrypt-PBKDF";
+ }
+
+std::unique_ptr<PasswordHash> Bcrypt_PBKDF_Family::tune(size_t output_length,
+ std::chrono::milliseconds msec,
+ size_t /*max_memory*/) const
+ {
+ Timer timer("Bcrypt_PBKDF");
+ const auto tune_time = BOTAN_PBKDF_TUNING_TIME;
+
+ const size_t blocks = (output_length + 32 - 1) / 32;
+
+ if(blocks == 0)
+ return default_params();
+
+ const size_t starting_iter = 2;
+
+ timer.run_until_elapsed(tune_time, [&]() {
+ uint8_t output[32] = { 0 };
+ bcrypt_pbkdf(output, sizeof(output), "test", 4, nullptr, 0, starting_iter);
+ });
+
+ if(timer.events() < blocks || timer.value() == 0)
+ return default_params();
+
+ const uint64_t measured_time = timer.value() / (timer.events() / blocks);
+
+ const uint64_t target_nsec = msec.count() * static_cast<uint64_t>(1000000);
+
+ const size_t desired_increase = target_nsec / measured_time;
+
+ if(desired_increase == 0)
+ return this->from_iterations(starting_iter);
+
+ return this->from_iterations(desired_increase * starting_iter);
+ }
+
+std::unique_ptr<PasswordHash> Bcrypt_PBKDF_Family::default_params() const
+ {
+ return this->from_iterations(32); // About 100 ms on fast machine
+ }
+
+std::unique_ptr<PasswordHash> Bcrypt_PBKDF_Family::from_iterations(size_t iter) const
+ {
+ return std::unique_ptr<PasswordHash>(new Bcrypt_PBKDF(iter));
+ }
+
+std::unique_ptr<PasswordHash> Bcrypt_PBKDF_Family::from_params(size_t iter, size_t /*t*/, size_t /*p*/) const
+ {
+ return this->from_iterations(iter);
+ }
+
+namespace {
+
+void bcrypt_round(Blowfish& blowfish,
+ const secure_vector<uint8_t>& pass_hash,
+ const secure_vector<uint8_t>& salt_hash,
+ secure_vector<uint8_t>& out,
+ secure_vector<uint8_t>& tmp)
+ {
+ const size_t BCRYPT_PBKDF_OUTPUT = 32;
+
+ // "OxychromaticBlowfishSwatDynamite"
+ static const uint8_t BCRYPT_PBKDF_MAGIC[BCRYPT_PBKDF_OUTPUT] = {
+ 0x4F, 0x78, 0x79, 0x63, 0x68, 0x72, 0x6F, 0x6D,
+ 0x61, 0x74, 0x69, 0x63, 0x42, 0x6C, 0x6F, 0x77,
+ 0x66, 0x69, 0x73, 0x68, 0x53, 0x77, 0x61, 0x74,
+ 0x44, 0x79, 0x6E, 0x61, 0x6D, 0x69, 0x74, 0x65
+ };
+
+ const size_t BCRYPT_PBKDF_WORKFACTOR = 6;
+ const size_t BCRYPT_PBKDF_ROUNDS = 64;
+
+ blowfish.salted_set_key(pass_hash.data(), pass_hash.size(),
+ salt_hash.data(), salt_hash.size(),
+ BCRYPT_PBKDF_WORKFACTOR, true);
+
+ copy_mem(tmp.data(), BCRYPT_PBKDF_MAGIC, BCRYPT_PBKDF_OUTPUT);
+ for(size_t i = 0; i != BCRYPT_PBKDF_ROUNDS; ++i)
+ blowfish.encrypt(tmp);
+
+ /*
+ Bcrypt PBKDF loads the Blowfish output as big endian for no reason
+ in particular. We can't just swap everything once at the end
+ because the (big-endian) values are fed into SHA-512 to generate
+ the salt for the next round
+ */
+ for(size_t i = 0; i != 32/4; ++i)
+ {
+ const uint32_t w = load_le<uint32_t>(tmp.data(), i);
+ store_be(w, &tmp[sizeof(uint32_t)*i]);
+ }
+
+ xor_buf(out.data(), tmp.data(), BCRYPT_PBKDF_OUTPUT);
+ }
+
+}
+
+void bcrypt_pbkdf(uint8_t output[], size_t output_len,
+ const char* pass, size_t pass_len,
+ const uint8_t salt[], size_t salt_len,
+ size_t rounds)
+ {
+ BOTAN_ARG_CHECK(rounds >= 1, "Invalid rounds for Bcrypt PBKDF");
+
+ // No output desired, so we are all done already...
+ if(output_len == 0)
+ return;
+
+ BOTAN_ARG_CHECK(output_len <= 10*1024*1024, "Too much output for Bcrypt PBKDF");
+
+ const size_t BCRYPT_BLOCK_SIZE = 32;
+ const size_t blocks = (output_len + BCRYPT_BLOCK_SIZE - 1) / BCRYPT_BLOCK_SIZE;
+
+ std::unique_ptr<HashFunction> sha512 = HashFunction::create_or_throw("SHA-512");
+ const secure_vector<uint8_t> pass_hash = sha512->process(reinterpret_cast<const uint8_t*>(pass), pass_len);
+
+ secure_vector<uint8_t> salt_hash(sha512->output_length());
+
+ Blowfish blowfish;
+ secure_vector<uint8_t> out(BCRYPT_BLOCK_SIZE);
+ secure_vector<uint8_t> tmp(BCRYPT_BLOCK_SIZE);
+
+ for(size_t block = 0; block != blocks; ++block)
+ {
+ clear_mem(out.data(), out.size());
+
+ sha512->update(salt, salt_len);
+ sha512->update_be(static_cast<uint32_t>(block + 1));
+ sha512->final(salt_hash.data());
+
+ bcrypt_round(blowfish, pass_hash, salt_hash, out, tmp);
+
+ for(size_t r = 1; r != rounds; ++r)
+ {
+ // Next salt is H(prev_output)
+ sha512->update(tmp);
+ sha512->final(salt_hash.data());
+
+ bcrypt_round(blowfish, pass_hash, salt_hash, out, tmp);
+ }
+
+ for(size_t i = 0; i != BCRYPT_BLOCK_SIZE; ++i)
+ {
+ const size_t dest = i * blocks + block;
+ if(dest < output_len)
+ output[dest] = out[i];
+ }
+ }
+ }
+
+}
diff --git a/src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.h b/src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.h
new file mode 100644
index 000000000..8984e6497
--- /dev/null
+++ b/src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.h
@@ -0,0 +1,75 @@
+/*
+* (C) 2018,2019 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#ifndef BOTAN_PBKDF_BCRYPT_H_
+#define BOTAN_PBKDF_BCRYPT_H_
+
+#include <botan/pwdhash.h>
+
+namespace Botan {
+
+/**
+* Bcrypt-PBKDF key derivation function
+*/
+class BOTAN_PUBLIC_API(2,11) Bcrypt_PBKDF final : public PasswordHash
+ {
+ public:
+ Bcrypt_PBKDF(size_t iterations) : m_iterations(iterations) {}
+
+ Bcrypt_PBKDF(const Bcrypt_PBKDF& other) = default;
+ Bcrypt_PBKDF& operator=(const Bcrypt_PBKDF&) = default;
+
+ /**
+ * Derive a new key under the current Bcrypt-PBKDF 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 iterations() const override { return m_iterations; }
+
+ size_t parallelism() const override { return 0; }
+
+ size_t memory_param() const override { return 0; }
+
+ size_t total_memory_usage() const override { return 4096; }
+
+ private:
+ size_t m_iterations;
+ };
+
+class BOTAN_PUBLIC_API(2,11) Bcrypt_PBKDF_Family final : public PasswordHashFamily
+ {
+ public:
+ Bcrypt_PBKDF_Family() {}
+
+ 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 i, size_t, size_t) const override;
+ };
+
+/**
+* Bcrypt PBKDF compatible with OpenBSD bcrypt_pbkdf
+*/
+void BOTAN_UNSTABLE_API bcrypt_pbkdf(uint8_t output[], size_t output_len,
+ const char* pass, size_t pass_len,
+ const uint8_t salt[], size_t salt_len,
+ size_t rounds);
+
+}
+
+#endif
diff --git a/src/lib/pbkdf/bcrypt_pbkdf/info.txt b/src/lib/pbkdf/bcrypt_pbkdf/info.txt
new file mode 100644
index 000000000..0e2b10178
--- /dev/null
+++ b/src/lib/pbkdf/bcrypt_pbkdf/info.txt
@@ -0,0 +1,8 @@
+<defines>
+PBKDF_BCRYPT -> 20190531
+</defines>
+
+<requires>
+blowfish
+sha2_64
+</requires>
diff --git a/src/lib/pbkdf/pwdhash.cpp b/src/lib/pbkdf/pwdhash.cpp
index f5ee26c6a..718024a22 100644
--- a/src/lib/pbkdf/pwdhash.cpp
+++ b/src/lib/pbkdf/pwdhash.cpp
@@ -24,6 +24,10 @@
#include <botan/argon2.h>
#endif
+#if defined(BOTAN_HAS_PBKDF_BCRYPT)
+ #include <botan/bcrypt_pbkdf.h>
+#endif
+
namespace Botan {
std::unique_ptr<PasswordHashFamily> PasswordHashFamily::create(const std::string& algo_spec,
@@ -71,6 +75,13 @@ std::unique_ptr<PasswordHashFamily> PasswordHashFamily::create(const std::string
}
#endif
+#if defined(BOTAN_HAS_PBKDF_BCRYPT)
+ if(req.algo_name() == "Bcrypt-PBKDF")
+ {
+ return std::unique_ptr<PasswordHashFamily>(new Bcrypt_PBKDF_Family);
+ }
+#endif
+
#if defined(BOTAN_HAS_PGP_S2K)
if(req.algo_name() == "OpenPGP-S2K" && req.arg_count() == 1)
{
diff --git a/src/tests/data/bcrypt_pbkdf.vec b/src/tests/data/bcrypt_pbkdf.vec
new file mode 100644
index 000000000..e56745265
--- /dev/null
+++ b/src/tests/data/bcrypt_pbkdf.vec
@@ -0,0 +1,184 @@
+
+# From https://github.com/dchest/bcrypt_pbkdf/blob/master/bcrypt_pbkdf_test.go
+Passphrase = password
+Salt = 73616C74
+Iterations = 12
+Output = 1AE42C05D487BC02F64921A4EBE4EA93BCACFE135FDA99974C06B7B01FAE149A
+
+# Generated by OpenBSD bcrypt_pbkdf
+Passphrase = A
+Salt = 0001020304050607
+Iterations = 10
+Output = 3C705DA7E10C61C2523B
+
+Passphrase = A
+Salt = 0001020304050607
+Iterations = 10
+Output = 3CF670095D5FA753E1430C6D6177C299521B3B614D9E53FA89C7F893F33289F677DDD72EE1AA29CBF5574A948DE4623C71996FFD16C8806FDD7ACBE02992DEAC
+
+# Generated by https://github.com/dchest/bcrypt_pbkdf/
+Passphrase = password
+Salt = 73616C74
+Iterations = 3
+Output = CCB508027535A962F591D4A7EEA08FE15EE0B68044B1A7B209605FDFBFB41228
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 4
+Output = 5BBF0CC293587F1C3635555C27796598
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 4
+Output = 5BBF0CC293587F1C3635555C27796598D47E579071BF427E9D8FBE842ABA34D9
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 4
+Output = 5BA4BFC60C7AC272931458407F4C1C4936EA356C55125C5A279B791D65BF9842D49D7E1B572A9052715EBFA9421E7E94
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 4
+Output = 5BA4BFC60C7AC272931458407F4C1C4936EA356C55125C5A279B791D65BF9842D49D7E1B572A9052715EBFA9421E7E949D8F8F19BE3284732AF1BA28341DD9BF
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 5
+Output = 4137FF05DC28382925690E6FD53B8EA1
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 5
+Output = 4137FF05DC28382925690E6FD53B8EA112DA02D8F79B787E48ADBE77B1CE5B30
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 5
+Output = 41CF37FCFF41055CDC5C280D389829D625DD69490E3B6F6BD5EB3B4C8E51A19E12A2DAFB02E8D82EF7F69B4A78717E51
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 5
+Output = 41CF37FCFF41055CDC5C280D389829D625DD69490E3B6F6BD5EB3B4C8E51A19E12A2DAFB02E8D82EF7F69B4A78717E514812AD4CBE5877D3B1D0CEB35BA3308E
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 6
+Output = 1E6CF2445415961A1FC9FCEDA40DF381
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 6
+Output = 1E6CF2445415961A1FC9FCEDA40DF381A86F5B327864A3CEA627EA8CFB081969
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 6
+Output = 1E906CE9F245442B5418153196301AFE1F5DC9FAFC06ED55A4160DBBF3BC81FFA8A16FD95B15326C78B3644CA356CE28
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 6
+Output = 1E906CE9F245442B5418153196301AFE1F5DC9FAFC06ED55A4160DBBF3BC81FFA8A16FD95B15326C78B3644CA356CE28A6162741EAE68C2AFB5E08A81921697C
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 7
+Output = 2BF6125E9A0532A025B1E777FD5F9540
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 7
+Output = 2BF6125E9A0532A025B1E777FD5F9540C95408393ADEA4241A5AACFEC17A74FC
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 7
+Output = 2BC7F6A012985EF39A6B05343202A0972507B1DFE7F97703FD9D5FF7951F40E3C92854FE087739603ADDDE00A4722471
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 7
+Output = 2BC7F6A012985EF39A6B05343202A0972507B1DFE7F97703FD9D5FF7951F40E3C92854FE087739603ADDDE00A47224711A8F5A40ACBFFE3DC1A87A8174FAFC9E
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 8
+Output = E17E1533ACC14423155493C99B9C3BBE
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 8
+Output = E17E1533ACC14423155493C99B9C3BBE62EA0884207A7802E7BA72EFF94D085E
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 8
+Output = E1367EC5151A33FAAC4CC1C144CD23FA15D5548493ECC99B9B5D9C0D3B27BEC76227EA66088B849B20AB7AA478010246
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 8
+Output = E1367EC5151A33FAAC4CC1C144CD23FA15D5548493ECC99B9B5D9C0D3B27BEC76227EA66088B849B20AB7AA478010246E74BBA51723FEFA9F9474D6508845E8D
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 9
+Output = 5871A1FE827AC417E5B17BC90E0CF8EB
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 9
+Output = 5871A1FE827AC417E5B17BC90E0CF8EBC59384735EF44F0A4A79FFC70629D39B
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 9
+Output = 586571F0A1F9FE2982337A42C478174BE58EB17C7B90C9B40E800CBAF82DEB7BC54D936784A973FC5E01F4484F120AD3
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 9
+Output = 586571F0A1F9FE2982337A42C478174BE58EB17C7B90C9B40E800CBAF82DEB7BC54D936784A973FC5E01F4484F120AD34A7179E4FF1EC7D406922935D3809B86
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 10
+Output = ABC7DCFB41032D844AF67A1D1EF8A5E6
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 10
+Output = ABC7DCFB41032D844AF67A1D1EF8A5E6A04AA3073D4F2777BFA5FE07A54CFF9D
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 10
+Output = ABFBC7DBDCB6FBC9414703BF2D0584994A9DF60A7AA71DC51EC7F8E1A55BE673A01D4AB9A3BA070E3DD74F92270177F9
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 10
+Output = ABFBC7DBDCB6FBC9414703BF2D0584994A9DF60A7AA71DC51EC7F8E1A55BE673A01D4AB9A3BA070E3DD74F92270177F9BFCFA5A9FEA107DCA59F4C5EFFC09D96
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 11
+Output = 07B2C9848D4535A9E96EDB0AB4783841
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 11
+Output = 07B2C9848D4535A9E96EDB0AB4783841A2A4571BE59CB3D14742218AA72A89D5
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 11
+Output = 0726B275C9CD84B18DD245DB35F4A944E9A46E7FDBB50A14B4C4789E38AF41C8A228A4FC57601B32E5409C5DB3C6D194
+
+Passphrase = password
+Salt = 73616C74
+Iterations = 11
+Output = 0726B275C9CD84B18DD245DB35F4A944E9A46E7FDBB50A14B4C4789E38AF41C8A228A4FC57601B32E5409C5DB3C6D194475D424421568AEBA7732ABF89C6D56D
+
diff --git a/src/tests/test_pbkdf.cpp b/src/tests/test_pbkdf.cpp
index 0e780437b..f10b721e2 100644
--- a/src/tests/test_pbkdf.cpp
+++ b/src/tests/test_pbkdf.cpp
@@ -1,5 +1,5 @@
/*
-* (C) 2014,2015 Jack Lloyd
+* (C) 2014,2015,2019 Jack Lloyd
* (C) 2018 Ribose Inc
*
* Botan is released under the Simplified BSD License (see license.txt)
@@ -24,6 +24,10 @@
#include <botan/argon2.h>
#endif
+#if defined(BOTAN_HAS_PBKDF_BCRYPT)
+ #include <botan/bcrypt_pbkdf.h>
+#endif
+
namespace Botan_Tests {
namespace {
@@ -88,7 +92,17 @@ class Pwdhash_Tests : public Test
{
std::vector<Test::Result> results;
- for(std::string pwdhash : { "Scrypt", "PBKDF2(SHA-256)", "OpenPGP-S2K(SHA-384)", "Argon2d", "Argon2i", "Argon2id" })
+ const std::vector<std::string> all_pwdhash = {
+ "Scrypt",
+ "PBKDF2(SHA-256)",
+ "OpenPGP-S2K(SHA-384)",
+ "Argon2d",
+ "Argon2i",
+ "Argon2id",
+ "Bcrypt-PBKDF"
+ };
+
+ for(std::string pwdhash : all_pwdhash)
{
Test::Result result("Pwdhash " + pwdhash);
auto pwdhash_fam = Botan::PasswordHashFamily::create(pwdhash);
@@ -150,6 +164,38 @@ BOTAN_REGISTER_TEST("pwdhash", Pwdhash_Tests);
#endif
+#if defined(BOTAN_HAS_PBKDF_BCRYPT)
+
+class Bcrypt_PBKDF_KAT_Tests final : public Text_Based_Test
+ {
+ public:
+ Bcrypt_PBKDF_KAT_Tests() : Text_Based_Test("bcrypt_pbkdf.vec", "Passphrase,Salt,Iterations,Output") {}
+
+ Test::Result run_one_test(const std::string&, const VarMap& vars) override
+ {
+ const size_t rounds = vars.get_req_sz("Iterations");
+ const std::vector<uint8_t> salt = vars.get_req_bin("Salt");
+ const std::string passphrase = vars.get_req_str("Passphrase");
+ const std::vector<uint8_t> expected = vars.get_req_bin("Output");
+
+ Test::Result result("bcrypt PBKDF");
+
+ std::vector<uint8_t> output(expected.size());
+ Botan::bcrypt_pbkdf(output.data(), output.size(),
+ passphrase.data(), passphrase.size(),
+ salt.data(), salt.size(),
+ rounds);
+
+ result.test_eq("derived key", output, expected);
+
+ return result;
+ }
+ };
+
+BOTAN_REGISTER_TEST("bcrypt_pbkdf", Bcrypt_PBKDF_KAT_Tests);
+
+#endif
+
#if defined(BOTAN_HAS_SCRYPT)
class Scrypt_KAT_Tests final : public Text_Based_Test