From b76afff27490686cd35c9a8cf004f543f7d65a42 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Fri, 31 Jul 2026 23:29:40 -0500 Subject: [PATCH] Wallet: Use unsigned KDF iteration count CMasterKey::nDeriveIterations values are deserialized from wallet files as unsigned 32-bit integers, but key derivation narrowed the count to a signed int. A count above INT_MAX became negative in the conversion, and the derivation loop counter then overflowed, which is undefined behavior. Keep the count unsigned through the derivation path to match the serialized type. Add a unit test for zero and normal counts. --- src/wallet/crypter.cpp | 6 +++--- src/wallet/crypter.h | 2 +- src/wallet/test/wallet_crypto_tests.cpp | 7 +++++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index 148a0ad2efe..7e195bd881d 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -12,7 +12,7 @@ #include namespace wallet { -int CCrypter::BytesToKeySHA512AES(const std::span salt, const SecureString& key_data, int count, unsigned char* key, unsigned char* iv) const +int CCrypter::BytesToKeySHA512AES(const std::span salt, const SecureString& key_data, unsigned int count, unsigned char* key, unsigned char* iv) const { // This mimics the behavior of openssl's EVP_BytesToKey with an aes256cbc // cipher and sha512 message digest. Because sha512's output size (64b) is @@ -29,7 +29,7 @@ int CCrypter::BytesToKeySHA512AES(const std::span salt, con di.Write(salt.data(), salt.size()); di.Finalize(buf); - for(int i = 0; i != count - 1; i++) + for (unsigned int i = 0; i != count - 1; ++i) di.Reset().Write(buf, sizeof(buf)).Finalize(buf); memcpy(key, buf, WALLET_CRYPTO_KEY_SIZE); @@ -40,7 +40,7 @@ int CCrypter::BytesToKeySHA512AES(const std::span salt, con bool CCrypter::SetKeyFromPassphrase(const SecureString& key_data, const std::span salt, const unsigned int rounds, const unsigned int derivation_method) { - if (rounds < 1 || salt.size() != WALLET_CRYPTO_SALT_SIZE) { + if (!rounds || salt.size() != WALLET_CRYPTO_SALT_SIZE) { return false; } diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index 90871245e57..58ba65081aa 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -76,7 +76,7 @@ private: std::vector> vchIV; bool fKeySet; - int BytesToKeySHA512AES(std::span salt, const SecureString& key_data, int count, unsigned char* key, unsigned char* iv) const; + int BytesToKeySHA512AES(std::span salt, const SecureString& key_data, unsigned int count, unsigned char* key, unsigned char* iv) const; public: bool SetKeyFromPassphrase(const SecureString& key_data, std::span salt, unsigned int rounds, unsigned int derivation_method); diff --git a/src/wallet/test/wallet_crypto_tests.cpp b/src/wallet/test/wallet_crypto_tests.cpp index 45383c8584e..353eb46f97d 100644 --- a/src/wallet/test/wallet_crypto_tests.cpp +++ b/src/wallet/test/wallet_crypto_tests.cpp @@ -96,6 +96,13 @@ BOOST_AUTO_TEST_CASE(passphrase) { TestCrypter::TestPassphrase(vchSalt, SecureString(hash.begin(), hash.end()), rounds); } +BOOST_AUTO_TEST_CASE(passphrase_zero_rounds) { + constexpr auto salt{"0000deadbeef0000"_hex_u8}; + CCrypter crypt; + BOOST_CHECK(!crypt.SetKeyFromPassphrase("passphrase", salt, /*rounds=*/0, /*derivation_method=*/0)); + BOOST_CHECK(crypt.SetKeyFromPassphrase("passphrase", salt, /*rounds=*/1, /*derivation_method=*/0)); +} + BOOST_AUTO_TEST_CASE(encrypt) { constexpr std::array salt{"0000deadbeef0000"_hex_u8}; CCrypter crypt;