Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 2 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,18 +129,10 @@ jobs:
include:
- target: shared
compiler: xcode
host_os: macos-15-intel
- target: amalgamation
compiler: xcode
host_os: macos-15-intel
make_tool: ninja
- target: shared
compiler: xcode
host_os: macos-15 # uses Apple Silicon
make_tool: ninja
host_os: macos-26
- target: amalgamation
compiler: xcode
host_os: macos-15 # uses Apple Silicon
host_os: macos-26

runs-on: ${{ matrix.host_os }}

Expand Down
26 changes: 20 additions & 6 deletions doc/api_ref/pbkdf.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,33 +24,39 @@ specified with all parameters (say "Scrypt" with ``N`` = 8192, ``r`` = 64, and

.. cpp:function:: void hash(std::span<uint8_t> out, \
std::string_view password, \
std::span<uint8> salt)
std::span<uint8> salt, \
const std::optional<std::stop_token>& stop_token = std::nullopt)

Derive a key from the specified *password* and *salt*, placing it into *out*.
Optionally pass *stop_token* to enable cooperative cancellation (example below).

.. cpp:function:: void hash(std::span<uint8_t> out, \
std::string_view password, \
std::span<const uint8> salt, \
std::span<const uint8> ad, \
std::span<const uint8> key)
std::span<const uint8> key, \
const std::optional<std::stop_token>& stop_token = std::nullopt)

Derive a key from the specified *password*, *salt*, associated data (*ad*), and
secret *key*, placing it into *out*. The *ad* and *key* are both allowed
to be empty. Currently non-empty AD/key is only supported with Argon2.
Optionally pass *stop_token* to enable cooperative cancellation (example below).

.. cpp:function:: 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
const uint8_t salt[], size_t salt_len, \
const std::optional<std::stop_token>& stop_token = std::nullopt) const

Same functionality as the 3 argument variant of :cpp:func:`PasswordHash::hash`.
Same functionality as the 4 argument variant of :cpp:func:`PasswordHash::hash`.

.. cpp:function:: 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 uint8_t ad[], size_t ad_len, \
const uint8_t key[], size_t key_len) const
const uint8_t key[], size_t key_len, \
const std::optional<std::stop_token>& stop_token = std::nullopt) const

Same functionality as the 5 argument variant of :cpp:func:`PasswordHash::hash`.
Same functionality as the 6 argument variant of :cpp:func:`PasswordHash::hash`.

.. cpp:function:: std::string to_string() const

Expand Down Expand Up @@ -149,6 +155,14 @@ as associated data. See :ref:`aead` for more information.
.. literalinclude:: /../src/examples/password_encryption.cpp
:language: cpp

To enable cooperative cancellation in a multi-threaded context, provide a ``std::stop_token``
obtained from a ``std::jthread`` or manually constructed ``std::stop_source``.
If cancellation is requested, the operation will terminate early by throwing an
``Operation_Canceled`` exception.

.. literalinclude:: /../src/examples/pbkdf_cooperative_cancellation.cpp
:language: cpp

Available Schemes
----------------------

Expand Down
39 changes: 39 additions & 0 deletions src/examples/pbkdf_cooperative_cancellation.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include <botan/pwdhash.h>
#include <botan/system_rng.h>
#include <chrono>
#include <future>
#include <thread>

int main() {
std::promise<Botan::secure_vector<uint8_t>> result_promise;
std::future<Botan::secure_vector<uint8_t>> result_future = result_promise.get_future();

std::jthread worker([&](std::stop_token st) {
try {
// Construct expensive password hash
auto pwd_fam = Botan::PasswordHashFamily::create_or_throw("PBKDF2(SHA-256)");
auto pwdhash = pwd_fam->from_params(static_cast<size_t>(1) << 31);
// Derive key
Botan::secure_vector<uint8_t> out(32);
const auto salt = Botan::system_rng().random_array<32>();
pwdhash->hash(out, "secret", salt, st);
// Not canceled
result_promise.set_value(out);
} catch(...) {
result_promise.set_exception(std::current_exception());
}
});

// Simulate cancellation after 0.1s
std::this_thread::sleep_for(std::chrono::milliseconds(100));
worker.request_stop(); // asks the thread to stop

try {
auto key = result_future.get();
// Handle successful derivation
} catch(const Botan::Operation_Canceled&) {
// Handle cancellation
}

// jthread joins automatically on destruction
}
12 changes: 10 additions & 2 deletions src/lib/block/blowfish/blowfish.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -336,8 +336,13 @@ void Blowfish::key_expansion(const uint8_t key[], size_t length, const uint8_t s
/*
* Modified key schedule used for bcrypt password hashing
*/
void Blowfish::salted_set_key(
const uint8_t key[], size_t length, const uint8_t salt[], size_t salt_length, size_t workfactor, bool salt_first) {
void Blowfish::salted_set_key(const uint8_t key[],
size_t length,
const uint8_t salt[],
size_t salt_length,
size_t workfactor,
bool salt_first,
const std::optional<std::stop_token>& stop_token) {
BOTAN_ARG_CHECK(salt_length > 0 && salt_length % 4 == 0, "Invalid salt length for Blowfish salted key schedule");

// Truncate longer passwords to the 72 char bcrypt limit
Expand All @@ -354,6 +359,9 @@ void Blowfish::salted_set_key(
const size_t rounds = static_cast<size_t>(1) << workfactor;

for(size_t r = 0; r != rounds; ++r) {
if(stop_token.has_value() && stop_token->stop_requested()) {
throw Botan::Operation_Canceled("blowfish_salted_set_key");
}
if(salt_first) {
key_expansion(salt, salt_length, nullptr, 0);
key_expansion(key, length, nullptr, 0);
Expand Down
5 changes: 4 additions & 1 deletion src/lib/block/blowfish/blowfish.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

#include <botan/block_cipher.h>
#include <botan/secmem.h>
#include <optional>
#include <stop_token>

namespace Botan {

Expand All @@ -29,7 +31,8 @@ class BOTAN_TEST_API Blowfish final : public Block_Cipher_Fixed_Params<8, 1, 56>
const uint8_t salt[],
size_t salt_length,
size_t workfactor,
bool salt_first = false);
bool salt_first = false,
const std::optional<std::stop_token>& stop_token = std::nullopt);

void clear() override;

Expand Down
1 change: 1 addition & 0 deletions src/lib/ffi/ffi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ int ffi_map_error_type(Botan::ErrorType err) {
return BOTAN_FFI_ERROR_OUT_OF_MEMORY;
case Botan::ErrorType::InternalError:
return BOTAN_FFI_ERROR_INTERNAL_ERROR;
case Botan::ErrorType::OperationCanceled:
case Botan::ErrorType::InvalidObjectState:
return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE;
case Botan::ErrorType::KeyNotSet:
Expand Down
23 changes: 17 additions & 6 deletions src/lib/pbkdf/argon2/argon2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,8 @@ void process_block(secure_vector<uint64_t>& B,
size_t threads,
uint8_t mode,
size_t memory,
size_t time) {
size_t time,
const std::optional<std::stop_token>& stop_token) {
uint64_t T[128];
size_t index = 0;
if(n == 0 && slice == 0) {
Expand All @@ -296,6 +297,10 @@ void process_block(secure_vector<uint64_t>& B,
}

while(index < segments) {
if((index & 63) == 0 && stop_token.has_value() && stop_token->stop_requested()) {
throw Botan::Operation_Canceled("argon2");
}

const size_t offset = lane * lanes + slice * segments + index;

size_t prev = offset - 1;
Expand Down Expand Up @@ -326,7 +331,12 @@ void process_block(secure_vector<uint64_t>& B,
}
}

void process_blocks(secure_vector<uint64_t>& B, size_t t, size_t memory, size_t threads, uint8_t mode) {
void process_blocks(secure_vector<uint64_t>& B,
size_t t,
size_t memory,
size_t threads,
uint8_t mode,
const std::optional<std::stop_token>& stop_token) {
const size_t lanes = memory / threads;
const size_t segments = lanes / SYNC_POINTS;

Expand All @@ -341,7 +351,7 @@ void process_blocks(secure_vector<uint64_t>& B, size_t t, size_t memory, size_t

for(size_t lane = 0; lane != threads; ++lane) {
fut_results.push_back(thread_pool.run(
process_block, std::ref(B), n, slice, lane, lanes, segments, threads, mode, memory, t));
process_block, std::ref(B), n, slice, lane, lanes, segments, threads, mode, memory, t, stop_token));
}

for(auto& fut : fut_results) {
Expand All @@ -357,7 +367,7 @@ void process_blocks(secure_vector<uint64_t>& B, size_t t, size_t memory, size_t
for(size_t n = 0; n != t; ++n) {
for(size_t slice = 0; slice != SYNC_POINTS; ++slice) {
for(size_t lane = 0; lane != threads; ++lane) {
process_block(B, n, slice, lane, lanes, segments, threads, mode, memory, t);
process_block(B, n, slice, lane, lanes, segments, threads, mode, memory, t, stop_token);
}
}
}
Expand All @@ -374,7 +384,8 @@ void Argon2::argon2(uint8_t output[],
const uint8_t key[],
size_t key_len,
const uint8_t ad[],
size_t ad_len) const {
size_t ad_len,
const std::optional<std::stop_token>& stop_token) const {
BOTAN_ARG_CHECK(output_len >= 4 && output_len <= std::numeric_limits<uint32_t>::max(),
"Invalid Argon2 output length");
BOTAN_ARG_CHECK(password_len <= std::numeric_limits<uint32_t>::max(), "Invalid Argon2 password length");
Expand Down Expand Up @@ -406,7 +417,7 @@ void Argon2::argon2(uint8_t output[],
secure_vector<uint64_t> B(memory * 1024 / 8);

init_blocks(B, *blake2, H0, memory, m_p);
process_blocks(B, m_t, memory, m_p, m_family);
process_blocks(B, m_t, memory, m_p, m_family, stop_token);

clear_mem(output, output_len);
extract_key(output, output_len, B, memory, m_p);
Expand Down
9 changes: 6 additions & 3 deletions src/lib/pbkdf/argon2/argon2.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ class BOTAN_PUBLIC_API(2, 11) Argon2 final : public PasswordHash {
const char* password,
size_t password_len,
const uint8_t salt[],
size_t salt_len) const override;
size_t salt_len,
const std::optional<std::stop_token>& stop_token) const override;

void derive_key(uint8_t out[],
size_t out_len,
Expand All @@ -46,7 +47,8 @@ class BOTAN_PUBLIC_API(2, 11) Argon2 final : public PasswordHash {
const uint8_t ad[],
size_t ad_len,
const uint8_t key[],
size_t key_len) const override;
size_t key_len,
const std::optional<std::stop_token>& stop_token) const override;

std::string to_string() const override;

Expand Down Expand Up @@ -91,7 +93,8 @@ class BOTAN_PUBLIC_API(2, 11) Argon2 final : public PasswordHash {
const uint8_t key[],
size_t key_len,
const uint8_t ad[],
size_t ad_len) const;
size_t ad_len,
const std::optional<std::stop_token>& stop_token) const;

uint8_t m_family;
size_t m_M, m_t, m_p;
Expand Down
10 changes: 6 additions & 4 deletions src/lib/pbkdf/argon2/argon2pwhash.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ void Argon2::derive_key(uint8_t output[],
const char* password,
size_t password_len,
const uint8_t salt[],
size_t salt_len) const {
argon2(output, output_len, password, password_len, salt, salt_len, nullptr, 0, nullptr, 0);
size_t salt_len,
const std::optional<std::stop_token>& stop_token) const {
argon2(output, output_len, password, password_len, salt, salt_len, nullptr, 0, nullptr, 0, stop_token);
}

void Argon2::derive_key(uint8_t output[],
Expand All @@ -39,8 +40,9 @@ void Argon2::derive_key(uint8_t output[],
const uint8_t ad[],
size_t ad_len,
const uint8_t key[],
size_t key_len) const {
argon2(output, output_len, password, password_len, salt, salt_len, key, key_len, ad, ad_len);
size_t key_len,
const std::optional<std::stop_token>& stop_token) const {
argon2(output, output_len, password, password_len, salt, salt_len, key, key_len, ad, ad_len, stop_token);
}

namespace {
Expand Down
19 changes: 13 additions & 6 deletions src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ 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) {
secure_vector<uint8_t>& tmp,
const std::optional<std::stop_token>& stop_token) {
const size_t BCRYPT_PBKDF_OUTPUT = 32;

// "OxychromaticBlowfishSwatDynamite"
Expand All @@ -88,8 +89,13 @@ void bcrypt_round(Blowfish& blowfish,
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);
blowfish.salted_set_key(pass_hash.data(),
pass_hash.size(),
salt_hash.data(),
salt_hash.size(),
BCRYPT_PBKDF_WORKFACTOR,
true,
stop_token);

copy_mem(tmp.data(), BCRYPT_PBKDF_MAGIC, BCRYPT_PBKDF_OUTPUT);
for(size_t i = 0; i != BCRYPT_PBKDF_ROUNDS; ++i) {
Expand Down Expand Up @@ -117,7 +123,8 @@ void Bcrypt_PBKDF::derive_key(uint8_t output[],
const char* password,
size_t password_len,
const uint8_t salt[],
size_t salt_len) const {
size_t salt_len,
const std::optional<std::stop_token>& stop_token) const {
// No output desired, so we are all done already...
if(output_len == 0) {
return;
Expand All @@ -144,14 +151,14 @@ void Bcrypt_PBKDF::derive_key(uint8_t output[],
sha512->update_be(static_cast<uint32_t>(block + 1));
sha512->final(salt_hash.data());

bcrypt_round(blowfish, pass_hash, salt_hash, out, tmp);
bcrypt_round(blowfish, pass_hash, salt_hash, out, tmp, stop_token);

for(size_t r = 1; r < m_iterations; ++r) {
// Next salt is H(prev_output)
sha512->update(tmp);
sha512->final(salt_hash.data());

bcrypt_round(blowfish, pass_hash, salt_hash, out, tmp);
bcrypt_round(blowfish, pass_hash, salt_hash, out, tmp, stop_token);
}

for(size_t i = 0; i != BCRYPT_BLOCK_SIZE; ++i) {
Expand Down
3 changes: 2 additions & 1 deletion src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ class BOTAN_PUBLIC_API(2, 11) Bcrypt_PBKDF final : public PasswordHash {
const char* password,
size_t password_len,
const uint8_t salt[],
size_t salt_len) const override;
size_t salt_len,
const std::optional<std::stop_token>& stop_token) const override;

std::string to_string() const override;

Expand Down
Loading
Loading