Skip to content

Commit 14b8dfb

Browse files
committed
Merge bitcoin#31398: wallet: refactor: various master key encryption cleanups
a8333fc scripted-diff: wallet: rename plain and encrypted master key variables (Sebastian Falbesoner) 5a92077 wallet: refactor: dedup master key decryption (Sebastian Falbesoner) 8465459 wallet: refactor: dedup master key encryption / derivation rounds setting (Sebastian Falbesoner) a6d9b41 wallet: refactor: introduce `CMasterKey::DEFAULT_DERIVE_ITERATIONS` constant (Sebastian Falbesoner) 62c209f wallet: doc: remove mentions of unavailable scrypt derivation method (Sebastian Falbesoner) Pull request description: This PR contains various cleanups around the wallet's master key encryption logic. The default/minimum key derivation rounds magic number of 25000 is hoisted into a constant (member of `CMasterKey`) and two new functions `EncryptMasterKey`/`DecryptMasterKey` are introduced in order to deduplicate code for the derivation round determination and master key en/decryption. Also, mentions of the never-implemented derivation method `scrypt` are removed from the wallet crypter header and both plain and encrypted master key instances are renamed to adapt to moderning coding style (hopefully improving readability). ACKs for top commit: davidgumberg: ACK bitcoin@a8333fc achow101: ACK a8333fc Tree-SHA512: 5a66d3b26f481347d0b5b4f742dd237803a35aad6e3480ed15fd38b7fa3700650bd5f67f4c30ed88f5fad45d6cd4c893fe4f1657e36e563b4294fd3596187724
2 parents a60445c + a8333fc commit 14b8dfb

File tree

4 files changed

+81
-70
lines changed

4 files changed

+81
-70
lines changed

src/wallet/crypter.h

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const unsigned int WALLET_CRYPTO_IV_SIZE = 16;
2323
* derived using derivation method nDerivationMethod
2424
* (0 == EVP_sha512()) and derivation iterations nDeriveIterations.
2525
* vchOtherDerivationParameters is provided for alternative algorithms
26-
* which may require more parameters (such as scrypt).
26+
* which may require more parameters.
2727
*
2828
* Wallet Private Keys are then encrypted using AES-256-CBC
2929
* with the double-sha256 of the public key as the IV, and the
@@ -37,23 +37,24 @@ class CMasterKey
3737
std::vector<unsigned char> vchCryptedKey;
3838
std::vector<unsigned char> vchSalt;
3939
//! 0 = EVP_sha512()
40-
//! 1 = scrypt()
4140
unsigned int nDerivationMethod;
4241
unsigned int nDeriveIterations;
43-
//! Use this for more parameters to key derivation,
44-
//! such as the various parameters to scrypt
42+
//! Use this for more parameters to key derivation (currently unused)
4543
std::vector<unsigned char> vchOtherDerivationParameters;
4644

45+
//! Default/minimum number of key derivation rounds
46+
// 25000 rounds is just under 0.1 seconds on a 1.86 GHz Pentium M
47+
// ie slightly lower than the lowest hardware we need bother supporting
48+
static constexpr unsigned int DEFAULT_DERIVE_ITERATIONS = 25000;
49+
4750
SERIALIZE_METHODS(CMasterKey, obj)
4851
{
4952
READWRITE(obj.vchCryptedKey, obj.vchSalt, obj.nDerivationMethod, obj.nDeriveIterations, obj.vchOtherDerivationParameters);
5053
}
5154

5255
CMasterKey()
5356
{
54-
// 25000 rounds is just under 0.1 seconds on a 1.86 GHz Pentium M
55-
// ie slightly lower than the lowest hardware we need bother supporting
56-
nDeriveIterations = 25000;
57+
nDeriveIterations = DEFAULT_DERIVE_ITERATIONS;
5758
nDerivationMethod = 0;
5859
vchOtherDerivationParameters = std::vector<unsigned char>(0);
5960
}

src/wallet/test/fuzz/crypter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ FUZZ_TARGET(crypter, .init = initialize_crypter)
3939
// Limiting the value of rounds since it is otherwise uselessly expensive and causes a timeout when fuzzing.
4040
crypt.SetKeyFromPassphrase(/*key_data=*/secure_string,
4141
/*salt=*/ConsumeFixedLengthByteVector(fuzzed_data_provider, WALLET_CRYPTO_SALT_SIZE),
42-
/*rounds=*/fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, 25000),
42+
/*rounds=*/fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, CMasterKey::DEFAULT_DERIVE_ITERATIONS),
4343
/*derivation_method=*/derivation_method);
4444
}
4545

src/wallet/test/wallet_crypto_tests.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ static void TestEncrypt(const CCrypter& crypt, const std::span<const unsigned ch
8383
BOOST_AUTO_TEST_CASE(passphrase) {
8484
// These are expensive.
8585

86-
TestCrypter::TestPassphrase("0000deadbeef0000"_hex_u8, "test", 25000,
86+
TestCrypter::TestPassphrase("0000deadbeef0000"_hex_u8, "test", CMasterKey::DEFAULT_DERIVE_ITERATIONS,
8787
"fc7aba077ad5f4c3a0988d8daa4810d0d4a0e3bcb53af662998898f33df0556a"_hex_u8,
8888
"cf2f2691526dd1aa220896fb8bf7c369"_hex_u8);
8989

@@ -99,7 +99,7 @@ BOOST_AUTO_TEST_CASE(passphrase) {
9999
BOOST_AUTO_TEST_CASE(encrypt) {
100100
constexpr std::array<uint8_t, WALLET_CRYPTO_SALT_SIZE> salt{"0000deadbeef0000"_hex_u8};
101101
CCrypter crypt;
102-
crypt.SetKeyFromPassphrase("passphrase", salt, 25000, 0);
102+
crypt.SetKeyFromPassphrase("passphrase", salt, CMasterKey::DEFAULT_DERIVE_ITERATIONS, 0);
103103
TestCrypter::TestEncrypt(crypt, "22bcade09ac03ff6386914359cfe885cfeb5f77ff0d670f102f619687453b29d"_hex_u8);
104104

105105
for (int i = 0; i != 100; i++)
@@ -113,7 +113,7 @@ BOOST_AUTO_TEST_CASE(encrypt) {
113113
BOOST_AUTO_TEST_CASE(decrypt) {
114114
constexpr std::array<uint8_t, WALLET_CRYPTO_SALT_SIZE> salt{"0000deadbeef0000"_hex_u8};
115115
CCrypter crypt;
116-
crypt.SetKeyFromPassphrase("passphrase", salt, 25000, 0);
116+
crypt.SetKeyFromPassphrase("passphrase", salt, CMasterKey::DEFAULT_DERIVE_ITERATIONS, 0);
117117

118118
// Some corner cases the came up while testing
119119
TestCrypter::TestDecrypt(crypt,"795643ce39d736088367822cdc50535ec6f103715e3e48f4f3b1a60a08ef59ca"_hex_u8);

src/wallet/wallet.cpp

Lines changed: 69 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -576,20 +576,60 @@ void CWallet::UpgradeDescriptorCache()
576576
SetWalletFlag(WALLET_FLAG_LAST_HARDENED_XPUB_CACHED);
577577
}
578578

579-
bool CWallet::Unlock(const SecureString& strWalletPassphrase)
579+
/* Given a wallet passphrase string and an unencrypted master key, determine the proper key
580+
* derivation parameters (should take at least 100ms) and encrypt the master key. */
581+
static bool EncryptMasterKey(const SecureString& wallet_passphrase, const CKeyingMaterial& plain_master_key, CMasterKey& master_key)
580582
{
583+
constexpr MillisecondsDouble target{100};
584+
auto start{SteadyClock::now()};
581585
CCrypter crypter;
582-
CKeyingMaterial _vMasterKey;
586+
587+
crypter.SetKeyFromPassphrase(wallet_passphrase, master_key.vchSalt, master_key.nDeriveIterations, master_key.nDerivationMethod);
588+
master_key.nDeriveIterations = static_cast<unsigned int>(master_key.nDeriveIterations * target / (SteadyClock::now() - start));
589+
590+
start = SteadyClock::now();
591+
crypter.SetKeyFromPassphrase(wallet_passphrase, master_key.vchSalt, master_key.nDeriveIterations, master_key.nDerivationMethod);
592+
master_key.nDeriveIterations = (master_key.nDeriveIterations + static_cast<unsigned int>(master_key.nDeriveIterations * target / (SteadyClock::now() - start))) / 2;
593+
594+
if (master_key.nDeriveIterations < CMasterKey::DEFAULT_DERIVE_ITERATIONS) {
595+
master_key.nDeriveIterations = CMasterKey::DEFAULT_DERIVE_ITERATIONS;
596+
}
597+
598+
if (!crypter.SetKeyFromPassphrase(wallet_passphrase, master_key.vchSalt, master_key.nDeriveIterations, master_key.nDerivationMethod)) {
599+
return false;
600+
}
601+
if (!crypter.Encrypt(plain_master_key, master_key.vchCryptedKey)) {
602+
return false;
603+
}
604+
605+
return true;
606+
}
607+
608+
static bool DecryptMasterKey(const SecureString& wallet_passphrase, const CMasterKey& master_key, CKeyingMaterial& plain_master_key)
609+
{
610+
CCrypter crypter;
611+
if (!crypter.SetKeyFromPassphrase(wallet_passphrase, master_key.vchSalt, master_key.nDeriveIterations, master_key.nDerivationMethod)) {
612+
return false;
613+
}
614+
if (!crypter.Decrypt(master_key.vchCryptedKey, plain_master_key)) {
615+
return false;
616+
}
617+
618+
return true;
619+
}
620+
621+
bool CWallet::Unlock(const SecureString& strWalletPassphrase)
622+
{
623+
CKeyingMaterial plain_master_key;
583624

584625
{
585626
LOCK(cs_wallet);
586-
for (const MasterKeyMap::value_type& pMasterKey : mapMasterKeys)
627+
for (const auto& [_, master_key] : mapMasterKeys)
587628
{
588-
if(!crypter.SetKeyFromPassphrase(strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
589-
return false;
590-
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey))
629+
if (!DecryptMasterKey(strWalletPassphrase, master_key, plain_master_key)) {
591630
continue; // try another master key
592-
if (Unlock(_vMasterKey)) {
631+
}
632+
if (Unlock(plain_master_key)) {
593633
// Now that we've unlocked, upgrade the key metadata
594634
UpgradeKeyMetadata();
595635
// Now that we've unlocked, upgrade the descriptor cache
@@ -609,35 +649,20 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase,
609649
LOCK2(m_relock_mutex, cs_wallet);
610650
Lock();
611651

612-
CCrypter crypter;
613-
CKeyingMaterial _vMasterKey;
614-
for (MasterKeyMap::value_type& pMasterKey : mapMasterKeys)
652+
CKeyingMaterial plain_master_key;
653+
for (auto& [master_key_id, master_key] : mapMasterKeys)
615654
{
616-
if(!crypter.SetKeyFromPassphrase(strOldWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
617-
return false;
618-
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey))
655+
if (!DecryptMasterKey(strOldWalletPassphrase, master_key, plain_master_key)) {
619656
return false;
620-
if (Unlock(_vMasterKey))
657+
}
658+
if (Unlock(plain_master_key))
621659
{
622-
constexpr MillisecondsDouble target{100};
623-
auto start{SteadyClock::now()};
624-
crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod);
625-
pMasterKey.second.nDeriveIterations = static_cast<unsigned int>(pMasterKey.second.nDeriveIterations * target / (SteadyClock::now() - start));
626-
627-
start = SteadyClock::now();
628-
crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod);
629-
pMasterKey.second.nDeriveIterations = (pMasterKey.second.nDeriveIterations + static_cast<unsigned int>(pMasterKey.second.nDeriveIterations * target / (SteadyClock::now() - start))) / 2;
630-
631-
if (pMasterKey.second.nDeriveIterations < 25000)
632-
pMasterKey.second.nDeriveIterations = 25000;
633-
634-
WalletLogPrintf("Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations);
635-
636-
if (!crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
637-
return false;
638-
if (!crypter.Encrypt(_vMasterKey, pMasterKey.second.vchCryptedKey))
660+
if (!EncryptMasterKey(strNewWalletPassphrase, plain_master_key, master_key)) {
639661
return false;
640-
WalletBatch(GetDatabase()).WriteMasterKey(pMasterKey.first, pMasterKey.second);
662+
}
663+
WalletLogPrintf("Wallet passphrase changed to an nDeriveIterations of %i\n", master_key.nDeriveIterations);
664+
665+
WalletBatch(GetDatabase()).WriteMasterKey(master_key_id, master_key);
641666
if (fWasLocked)
642667
Lock();
643668
return true;
@@ -812,50 +837,35 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
812837
if (IsCrypted())
813838
return false;
814839

815-
CKeyingMaterial _vMasterKey;
816-
817-
_vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE);
818-
GetStrongRandBytes(_vMasterKey);
819-
820-
CMasterKey kMasterKey;
821-
822-
kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE);
823-
GetStrongRandBytes(kMasterKey.vchSalt);
824-
825-
CCrypter crypter;
826-
constexpr MillisecondsDouble target{100};
827-
auto start{SteadyClock::now()};
828-
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000, kMasterKey.nDerivationMethod);
829-
kMasterKey.nDeriveIterations = static_cast<unsigned int>(25000 * target / (SteadyClock::now() - start));
840+
CKeyingMaterial plain_master_key;
830841

831-
start = SteadyClock::now();
832-
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod);
833-
kMasterKey.nDeriveIterations = (kMasterKey.nDeriveIterations + static_cast<unsigned int>(kMasterKey.nDeriveIterations * target / (SteadyClock::now() - start))) / 2;
842+
plain_master_key.resize(WALLET_CRYPTO_KEY_SIZE);
843+
GetStrongRandBytes(plain_master_key);
834844

835-
if (kMasterKey.nDeriveIterations < 25000)
836-
kMasterKey.nDeriveIterations = 25000;
845+
CMasterKey master_key;
837846

838-
WalletLogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations);
847+
master_key.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE);
848+
GetStrongRandBytes(master_key.vchSalt);
839849

840-
if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod))
841-
return false;
842-
if (!crypter.Encrypt(_vMasterKey, kMasterKey.vchCryptedKey))
850+
if (!EncryptMasterKey(strWalletPassphrase, plain_master_key, master_key)) {
843851
return false;
852+
}
853+
WalletLogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n", master_key.nDeriveIterations);
844854

845855
{
846856
LOCK2(m_relock_mutex, cs_wallet);
847-
mapMasterKeys[++nMasterKeyMaxID] = kMasterKey;
857+
mapMasterKeys[++nMasterKeyMaxID] = master_key;
848858
WalletBatch* encrypted_batch = new WalletBatch(GetDatabase());
849859
if (!encrypted_batch->TxnBegin()) {
850860
delete encrypted_batch;
851861
encrypted_batch = nullptr;
852862
return false;
853863
}
854-
encrypted_batch->WriteMasterKey(nMasterKeyMaxID, kMasterKey);
864+
encrypted_batch->WriteMasterKey(nMasterKeyMaxID, master_key);
855865

856866
for (const auto& spk_man_pair : m_spk_managers) {
857867
auto spk_man = spk_man_pair.second.get();
858-
if (!spk_man->Encrypt(_vMasterKey, encrypted_batch)) {
868+
if (!spk_man->Encrypt(plain_master_key, encrypted_batch)) {
859869
encrypted_batch->TxnAbort();
860870
delete encrypted_batch;
861871
encrypted_batch = nullptr;

0 commit comments

Comments
 (0)