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.
This commit is contained in:
benthecarman
2026-07-31 23:29:40 -05:00
parent 67efced1fc
commit b76afff274
3 changed files with 11 additions and 4 deletions

View File

@@ -12,7 +12,7 @@
#include <vector>
namespace wallet {
int CCrypter::BytesToKeySHA512AES(const std::span<const unsigned char> salt, const SecureString& key_data, int count, unsigned char* key, unsigned char* iv) const
int CCrypter::BytesToKeySHA512AES(const std::span<const unsigned char> 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<const unsigned char> 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<const unsigned char> salt, con
bool CCrypter::SetKeyFromPassphrase(const SecureString& key_data, const std::span<const unsigned char> 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;
}

View File

@@ -76,7 +76,7 @@ private:
std::vector<unsigned char, secure_allocator<unsigned char>> vchIV;
bool fKeySet;
int BytesToKeySHA512AES(std::span<const unsigned char> salt, const SecureString& key_data, int count, unsigned char* key, unsigned char* iv) const;
int BytesToKeySHA512AES(std::span<const unsigned char> salt, const SecureString& key_data, unsigned int count, unsigned char* key, unsigned char* iv) const;
public:
bool SetKeyFromPassphrase(const SecureString& key_data, std::span<const unsigned char> salt, unsigned int rounds, unsigned int derivation_method);

View File

@@ -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<uint8_t, WALLET_CRYPTO_SALT_SIZE> salt{"0000deadbeef0000"_hex_u8};
CCrypter crypt;