Merge a8333fc9ff9adaa97a1f9024f5783cc071777150 into db2c57ae9eebdb75c58cd165ac929919969c19a9

This commit is contained in:
Sebastian Falbesoner 2025-03-17 10:36:37 +01:00 committed by GitHub
commit 193f232fc8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 81 additions and 70 deletions

View File

@ -23,7 +23,7 @@ const unsigned int WALLET_CRYPTO_IV_SIZE = 16;
* derived using derivation method nDerivationMethod
* (0 == EVP_sha512()) and derivation iterations nDeriveIterations.
* vchOtherDerivationParameters is provided for alternative algorithms
* which may require more parameters (such as scrypt).
* which may require more parameters.
*
* Wallet Private Keys are then encrypted using AES-256-CBC
* with the double-sha256 of the public key as the IV, and the
@ -37,13 +37,16 @@ public:
std::vector<unsigned char> vchCryptedKey;
std::vector<unsigned char> vchSalt;
//! 0 = EVP_sha512()
//! 1 = scrypt()
unsigned int nDerivationMethod;
unsigned int nDeriveIterations;
//! Use this for more parameters to key derivation,
//! such as the various parameters to scrypt
//! Use this for more parameters to key derivation (currently unused)
std::vector<unsigned char> vchOtherDerivationParameters;
//! Default/minimum number of key derivation rounds
// 25000 rounds is just under 0.1 seconds on a 1.86 GHz Pentium M
// ie slightly lower than the lowest hardware we need bother supporting
static constexpr unsigned int DEFAULT_DERIVE_ITERATIONS = 25000;
SERIALIZE_METHODS(CMasterKey, obj)
{
READWRITE(obj.vchCryptedKey, obj.vchSalt, obj.nDerivationMethod, obj.nDeriveIterations, obj.vchOtherDerivationParameters);
@ -51,9 +54,7 @@ public:
CMasterKey()
{
// 25000 rounds is just under 0.1 seconds on a 1.86 GHz Pentium M
// ie slightly lower than the lowest hardware we need bother supporting
nDeriveIterations = 25000;
nDeriveIterations = DEFAULT_DERIVE_ITERATIONS;
nDerivationMethod = 0;
vchOtherDerivationParameters = std::vector<unsigned char>(0);
}

View File

@ -38,7 +38,7 @@ FUZZ_TARGET(crypter, .init = initialize_crypter)
// Limiting the value of rounds since it is otherwise uselessly expensive and causes a timeout when fuzzing.
crypt.SetKeyFromPassphrase(/*key_data=*/secure_string,
/*salt=*/ConsumeFixedLengthByteVector(fuzzed_data_provider, WALLET_CRYPTO_SALT_SIZE),
/*rounds=*/fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, 25000),
/*rounds=*/fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, CMasterKey::DEFAULT_DERIVE_ITERATIONS),
/*derivation_method=*/derivation_method);
}

View File

@ -83,7 +83,7 @@ static void TestEncrypt(const CCrypter& crypt, const std::span<const unsigned ch
BOOST_AUTO_TEST_CASE(passphrase) {
// These are expensive.
TestCrypter::TestPassphrase("0000deadbeef0000"_hex_u8, "test", 25000,
TestCrypter::TestPassphrase("0000deadbeef0000"_hex_u8, "test", CMasterKey::DEFAULT_DERIVE_ITERATIONS,
"fc7aba077ad5f4c3a0988d8daa4810d0d4a0e3bcb53af662998898f33df0556a"_hex_u8,
"cf2f2691526dd1aa220896fb8bf7c369"_hex_u8);
@ -99,7 +99,7 @@ BOOST_AUTO_TEST_CASE(passphrase) {
BOOST_AUTO_TEST_CASE(encrypt) {
constexpr std::array<uint8_t, WALLET_CRYPTO_SALT_SIZE> salt{"0000deadbeef0000"_hex_u8};
CCrypter crypt;
crypt.SetKeyFromPassphrase("passphrase", salt, 25000, 0);
crypt.SetKeyFromPassphrase("passphrase", salt, CMasterKey::DEFAULT_DERIVE_ITERATIONS, 0);
TestCrypter::TestEncrypt(crypt, "22bcade09ac03ff6386914359cfe885cfeb5f77ff0d670f102f619687453b29d"_hex_u8);
for (int i = 0; i != 100; i++)
@ -113,7 +113,7 @@ BOOST_AUTO_TEST_CASE(encrypt) {
BOOST_AUTO_TEST_CASE(decrypt) {
constexpr std::array<uint8_t, WALLET_CRYPTO_SALT_SIZE> salt{"0000deadbeef0000"_hex_u8};
CCrypter crypt;
crypt.SetKeyFromPassphrase("passphrase", salt, 25000, 0);
crypt.SetKeyFromPassphrase("passphrase", salt, CMasterKey::DEFAULT_DERIVE_ITERATIONS, 0);
// Some corner cases the came up while testing
TestCrypter::TestDecrypt(crypt,"795643ce39d736088367822cdc50535ec6f103715e3e48f4f3b1a60a08ef59ca"_hex_u8);

View File

@ -576,20 +576,60 @@ void CWallet::UpgradeDescriptorCache()
SetWalletFlag(WALLET_FLAG_LAST_HARDENED_XPUB_CACHED);
}
bool CWallet::Unlock(const SecureString& strWalletPassphrase)
/* Given a wallet passphrase string and an unencrypted master key, determine the proper key
* derivation parameters (should take at least 100ms) and encrypt the master key. */
static bool EncryptMasterKey(const SecureString& wallet_passphrase, const CKeyingMaterial& plain_master_key, CMasterKey& master_key)
{
constexpr MillisecondsDouble target{100};
auto start{SteadyClock::now()};
CCrypter crypter;
crypter.SetKeyFromPassphrase(wallet_passphrase, master_key.vchSalt, master_key.nDeriveIterations, master_key.nDerivationMethod);
master_key.nDeriveIterations = static_cast<unsigned int>(master_key.nDeriveIterations * target / (SteadyClock::now() - start));
start = SteadyClock::now();
crypter.SetKeyFromPassphrase(wallet_passphrase, master_key.vchSalt, master_key.nDeriveIterations, master_key.nDerivationMethod);
master_key.nDeriveIterations = (master_key.nDeriveIterations + static_cast<unsigned int>(master_key.nDeriveIterations * target / (SteadyClock::now() - start))) / 2;
if (master_key.nDeriveIterations < CMasterKey::DEFAULT_DERIVE_ITERATIONS) {
master_key.nDeriveIterations = CMasterKey::DEFAULT_DERIVE_ITERATIONS;
}
if (!crypter.SetKeyFromPassphrase(wallet_passphrase, master_key.vchSalt, master_key.nDeriveIterations, master_key.nDerivationMethod)) {
return false;
}
if (!crypter.Encrypt(plain_master_key, master_key.vchCryptedKey)) {
return false;
}
return true;
}
static bool DecryptMasterKey(const SecureString& wallet_passphrase, const CMasterKey& master_key, CKeyingMaterial& plain_master_key)
{
CCrypter crypter;
CKeyingMaterial _vMasterKey;
if (!crypter.SetKeyFromPassphrase(wallet_passphrase, master_key.vchSalt, master_key.nDeriveIterations, master_key.nDerivationMethod)) {
return false;
}
if (!crypter.Decrypt(master_key.vchCryptedKey, plain_master_key)) {
return false;
}
return true;
}
bool CWallet::Unlock(const SecureString& strWalletPassphrase)
{
CKeyingMaterial plain_master_key;
{
LOCK(cs_wallet);
for (const MasterKeyMap::value_type& pMasterKey : mapMasterKeys)
for (const auto& [_, master_key] : mapMasterKeys)
{
if(!crypter.SetKeyFromPassphrase(strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
return false;
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey))
if (!DecryptMasterKey(strWalletPassphrase, master_key, plain_master_key)) {
continue; // try another master key
if (Unlock(_vMasterKey)) {
}
if (Unlock(plain_master_key)) {
// Now that we've unlocked, upgrade the key metadata
UpgradeKeyMetadata();
// Now that we've unlocked, upgrade the descriptor cache
@ -609,35 +649,20 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase,
LOCK2(m_relock_mutex, cs_wallet);
Lock();
CCrypter crypter;
CKeyingMaterial _vMasterKey;
for (MasterKeyMap::value_type& pMasterKey : mapMasterKeys)
CKeyingMaterial plain_master_key;
for (auto& [master_key_id, master_key] : mapMasterKeys)
{
if(!crypter.SetKeyFromPassphrase(strOldWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
if (!DecryptMasterKey(strOldWalletPassphrase, master_key, plain_master_key)) {
return false;
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey))
return false;
if (Unlock(_vMasterKey))
}
if (Unlock(plain_master_key))
{
constexpr MillisecondsDouble target{100};
auto start{SteadyClock::now()};
crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod);
pMasterKey.second.nDeriveIterations = static_cast<unsigned int>(pMasterKey.second.nDeriveIterations * target / (SteadyClock::now() - start));
start = SteadyClock::now();
crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod);
pMasterKey.second.nDeriveIterations = (pMasterKey.second.nDeriveIterations + static_cast<unsigned int>(pMasterKey.second.nDeriveIterations * target / (SteadyClock::now() - start))) / 2;
if (pMasterKey.second.nDeriveIterations < 25000)
pMasterKey.second.nDeriveIterations = 25000;
WalletLogPrintf("Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations);
if (!crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
if (!EncryptMasterKey(strNewWalletPassphrase, plain_master_key, master_key)) {
return false;
if (!crypter.Encrypt(_vMasterKey, pMasterKey.second.vchCryptedKey))
return false;
WalletBatch(GetDatabase()).WriteMasterKey(pMasterKey.first, pMasterKey.second);
}
WalletLogPrintf("Wallet passphrase changed to an nDeriveIterations of %i\n", master_key.nDeriveIterations);
WalletBatch(GetDatabase()).WriteMasterKey(master_key_id, master_key);
if (fWasLocked)
Lock();
return true;
@ -812,50 +837,35 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
if (IsCrypted())
return false;
CKeyingMaterial _vMasterKey;
CKeyingMaterial plain_master_key;
_vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE);
GetStrongRandBytes(_vMasterKey);
plain_master_key.resize(WALLET_CRYPTO_KEY_SIZE);
GetStrongRandBytes(plain_master_key);
CMasterKey kMasterKey;
CMasterKey master_key;
kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE);
GetStrongRandBytes(kMasterKey.vchSalt);
master_key.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE);
GetStrongRandBytes(master_key.vchSalt);
CCrypter crypter;
constexpr MillisecondsDouble target{100};
auto start{SteadyClock::now()};
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000, kMasterKey.nDerivationMethod);
kMasterKey.nDeriveIterations = static_cast<unsigned int>(25000 * target / (SteadyClock::now() - start));
start = SteadyClock::now();
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod);
kMasterKey.nDeriveIterations = (kMasterKey.nDeriveIterations + static_cast<unsigned int>(kMasterKey.nDeriveIterations * target / (SteadyClock::now() - start))) / 2;
if (kMasterKey.nDeriveIterations < 25000)
kMasterKey.nDeriveIterations = 25000;
WalletLogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations);
if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod))
return false;
if (!crypter.Encrypt(_vMasterKey, kMasterKey.vchCryptedKey))
if (!EncryptMasterKey(strWalletPassphrase, plain_master_key, master_key)) {
return false;
}
WalletLogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n", master_key.nDeriveIterations);
{
LOCK2(m_relock_mutex, cs_wallet);
mapMasterKeys[++nMasterKeyMaxID] = kMasterKey;
mapMasterKeys[++nMasterKeyMaxID] = master_key;
WalletBatch* encrypted_batch = new WalletBatch(GetDatabase());
if (!encrypted_batch->TxnBegin()) {
delete encrypted_batch;
encrypted_batch = nullptr;
return false;
}
encrypted_batch->WriteMasterKey(nMasterKeyMaxID, kMasterKey);
encrypted_batch->WriteMasterKey(nMasterKeyMaxID, master_key);
for (const auto& spk_man_pair : m_spk_managers) {
auto spk_man = spk_man_pair.second.get();
if (!spk_man->Encrypt(_vMasterKey, encrypted_batch)) {
if (!spk_man->Encrypt(plain_master_key, encrypted_batch)) {
encrypted_batch->TxnAbort();
delete encrypted_batch;
encrypted_batch = nullptr;