Skip to content

Commit

Permalink
feat!: refactor database encryption (#5154)
Browse files Browse the repository at this point in the history
Description
---
Refactors database encryption to enable easier [password changes](#5003) and [PBKDF parameter updates](#5151).

BREAKING CHANGE: This significantly changes how database encryption is performed, so existing databases will be rendered permanently inaccessible.

Motivation and Context
---
Currently, the user's passphrase is used with a PBKDF (as of now, `Argon2` with [recommended parameters](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id)) to derive the `XChaCha20-Poly1305` key used for database encryption. While this is a safe way to handle database encryption (up to the complexity of the passphrase), it is brittle. Changing the passphrase or updating the PBKDF parameters would require that all database entries be re-encrypted with a new key, which is likely to be tedious and prone to corruption.

This can be [improved](#5151 (comment)) by refactoring how encryption is done.

When the user wishes to set up encryption, we generate a high-entropy random `XChaCha20-Poly1305` key (the _main key_) to do the actual authenticated encryption of database entries. We then run the user's passphrase and a random salt through the PBKDF to derive another `XChaCha20-Poly1305` key (the _secondary key_), and encrypt the main key with authentication. Finally, we store the secondary key salt, the encrypted main key, and a version identifier in the database.

When we need to decrypt database entries, we derive the secondary key using the salt (and parameters from the version identifier), decrypt and authenticate the main key, and use the main key for database operations.

When the user decides to change their passphrase, or if we wish to update the PBKDF parameters, we simply derive a new secondary key, re-encrypt the main key with it, and store the new secondary key salt, encrypted main key, and version identifier. This operation can be done quickly and with minimal risk.

This PR includes the refactoring needed to support this two-key design. However, it does not directly integrate password change or PBKDF parameter update functionality, which is deferred to future work.

How Has This Been Tested?
---
Existing tests pass. Manual testing is still needed.

BREAKING CHANGE: This significantly changes how database encryption is performed, so existing databases will be rendered permanently inaccessible.
  • Loading branch information
AaronFeickert authored Feb 3, 2023
1 parent f8e6b5d commit 41413fc
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 124 deletions.
2 changes: 2 additions & 0 deletions base_layer/wallet/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ pub enum WalletStorageError {
KeyManagerError(#[from] KeyManagerError),
#[error("Recovery Seed Error: {0}")]
RecoverySeedError(String),
#[error("Bad encryption version: `{0}`")]
BadEncryptionVersion(String),
}

impl From<WalletStorageError> for ExitError {
Expand Down
20 changes: 12 additions & 8 deletions base_layer/wallet/src/storage/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ pub enum DbKey {
BaseNodeChainMetadata,
ClientKey(String),
MasterSeed,
PassphraseHash,
EncryptionSalt,
EncryptedMainKey, // the database encryption key, itself encrypted with the secondary key
SecondaryKeySalt, // the salt used (with the user's passphrase) to derive the secondary key
SecondaryKeyVersion, // the parameter version for the secondary key, which determines how it is derived
WalletBirthday,
}

Expand All @@ -82,8 +83,9 @@ impl DbKey {
DbKey::TorId => "TorId".to_string(),
DbKey::ClientKey(k) => format!("ClientKey.{}", k),
DbKey::BaseNodeChainMetadata => "BaseNodeChainMetadata".to_string(),
DbKey::PassphraseHash => "PassphraseHash".to_string(),
DbKey::EncryptionSalt => "EncryptionSalt".to_string(),
DbKey::EncryptedMainKey => "EncryptedMainKey".to_string(),
DbKey::SecondaryKeySalt => "SecondaryKeySalt".to_string(),
DbKey::SecondaryKeyVersion => "SecondaryKeyVersion".to_string(),
DbKey::WalletBirthday => "WalletBirthday".to_string(),
DbKey::CommsIdentitySignature => "CommsIdentitySignature".to_string(),
}
Expand All @@ -99,8 +101,9 @@ pub enum DbValue {
ValueCleared,
BaseNodeChainMetadata(ChainMetadata),
MasterSeed(CipherSeed),
PassphraseHash(String),
EncryptionSalt(String),
EncryptedMainKey(String),
SecondaryKeySalt(String),
SecondaryKeyVersion(String),
WalletBirthday(String),
}

Expand Down Expand Up @@ -333,8 +336,9 @@ impl Display for DbValue {
DbValue::CommsAddress(_) => f.write_str("Comms Address"),
DbValue::TorId(v) => f.write_str(&format!("Tor ID: {}", v)),
DbValue::BaseNodeChainMetadata(v) => f.write_str(&format!("Last seen Chain metadata from base node:{}", v)),
DbValue::PassphraseHash(h) => f.write_str(&format!("PassphraseHash: {}", h)),
DbValue::EncryptionSalt(s) => f.write_str(&format!("EncryptionSalt: {}", s)),
DbValue::EncryptedMainKey(k) => f.write_str(&format!("EncryptedMainKey: {:?}", k)),
DbValue::SecondaryKeySalt(s) => f.write_str(&format!("SecondaryKeySalt: {}", s)),
DbValue::SecondaryKeyVersion(v) => f.write_str(&format!("SecondaryKeyVersion: {}", v)),
DbValue::WalletBirthday(b) => f.write_str(&format!("WalletBirthday: {}", b)),
DbValue::CommsIdentitySignature(_) => f.write_str("CommsIdentitySignature"),
}
Expand Down
Loading

0 comments on commit 41413fc

Please sign in to comment.