diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index 944858fb3f8..fa15f02c8c6 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -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 vchCryptedKey; std::vector 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 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(0); } diff --git a/src/wallet/test/fuzz/crypter.cpp b/src/wallet/test/fuzz/crypter.cpp index 7869f5f39c8..c1454c6e059 100644 --- a/src/wallet/test/fuzz/crypter.cpp +++ b/src/wallet/test/fuzz/crypter.cpp @@ -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(0, 25000), + /*rounds=*/fuzzed_data_provider.ConsumeIntegralInRange(0, CMasterKey::DEFAULT_DERIVE_ITERATIONS), /*derivation_method=*/derivation_method); } diff --git a/src/wallet/test/wallet_crypto_tests.cpp b/src/wallet/test/wallet_crypto_tests.cpp index 7c74c313088..e01c7a4e5cf 100644 --- a/src/wallet/test/wallet_crypto_tests.cpp +++ b/src/wallet/test/wallet_crypto_tests.cpp @@ -83,7 +83,7 @@ static void TestEncrypt(const CCrypter& crypt, const std::span 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 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); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 09eda0c28e4..4a0ecd0c2d4 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -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(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(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(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(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(25000 * target / (SteadyClock::now() - start)); - - start = SteadyClock::now(); - crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod); - kMasterKey.nDeriveIterations = (kMasterKey.nDeriveIterations + static_cast(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;