Conversation
Fixed issue #2221
There was a problem hiding this comment.
Hello @lvca, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
Summary of Changes
Hello team, gemini-code-assist here with a summary of this pull request. This PR introduces significant functionality by adding support for encrypting database backups and decrypting them during the restore process. The changes span multiple areas, including the SQL grammar for specifying encryption settings, the backup and restore settings classes, the core backup/restore format implementations for handling the encryption/decryption logic, and new test cases to verify the functionality.
Highlights
- Encrypted Backup SQL Syntax: The
BACKUP DATABASESQL command now supports aWITHclause to specify encryption settings likeencryptionAlgorithmandencryptionKey. - Encryption/Decryption Logic: AES encryption/decryption logic has been implemented within the
FullBackupFormatandFullRestoreFormatclasses, wrapping the standard zip streams with cipher streams when an encryption key is provided. The Initialization Vector (IV) is written to the beginning of the encrypted backup file. - Settings Updates:
BackupSettingsandRestoreSettingsclasses have been updated to include fields forencryptionAlgorithmandencryptionKey, allowing these settings to be passed and parsed. - New Test Coverage: New unit and integration tests have been added to specifically verify the encrypted backup and restore processes using different encryption keys.
Changelog
Click here to see the changelog
- engine/src/main/grammar/SQLGrammar.jjt
- Added a
WITHclause to theBackupDatabaseStatementgrammar rule (lines 4192-4199) to allow specifying key-value settings, intended for encryption parameters.
- Added a
- engine/src/main/java/com/arcadedb/query/sql/parser/BackupDatabaseStatement.java
- Added fields
key,value, and asettingsmap to store parameters from theWITHclause (lines 39-41). - Added logic in
executeSimpleto iterate through thesettingsmap and use reflection to callsetEncryptionAlgorithmandsetEncryptionKeymethods on theBackupobject if the corresponding keys are found (lines 74-81).
- Added fields
- integration/src/main/java/com/arcadedb/integration/backup/Backup.java
- Added public setter methods
setEncryptionAlgorithm(lines 83-86) andsetEncryptionKey(lines 88-91) to theBackupclass to allow configuring encryption settings.
- Added public setter methods
- integration/src/main/java/com/arcadedb/integration/backup/BackupSettings.java
- Added
encryptionAlgorithm(defaulting to "AES") andencryptionKeyfields (lines 32-33). - Updated the
parseParametermethod to handleencryptionAlgorithmandencryptionKeyparameters (lines 65-66).
- Added
- integration/src/main/java/com/arcadedb/integration/backup/format/FullBackupFormat.java
- Introduced a private
BackupCallbackinterface (lines 36-38). - Refactored
backupDatabaseto use a newencryptFilemethod, passing the core backup logic as a callback (lines 72-102). - Added the
encryptFilemethod (lines 129-160) which handles creating aFileOutputStream, optionally wrapping it with aCipherOutputStreamusing AES/CTR/NoPadding based onsettings.encryptionKey, writing the IV, and then creating theZipOutputStream.
- Introduced a private
- integration/src/main/java/com/arcadedb/integration/restore/RestoreSettings.java
- Added
encryptionAlgorithm(defaulting to "AES") andencryptionKeyfields (lines 30-31). - Updated the
parseParametermethod to handleencryptionAlgorithmandencryptionKeyparameters using aswitchexpression (lines 44-50).
- Added
- integration/src/main/java/com/arcadedb/integration/restore/format/FullRestoreFormat.java
- Introduced a private
RestoreCallbackinterface (lines 34-36). - Refactored
restoreDatabaseto use a newdecryptFilemethod, passing the core restore logic as a callback (lines 76-94). - Added the
decryptFilemethod (lines 151-180) which handles creating the initialInputStream, reading the IV, optionally wrapping it with aCipherInputStreamusing AES/CTR/NoPadding based onsettings.encryptionKey, and then creating theZipInputStream.
- Introduced a private
- integration/src/test/java/com/arcadedb/integration/backup/BackupDatabaseTest.java
- Added a new test
testBackupEncryption(lines 30-37) to verify theBACKUP DATABASESQL command with an encryption key.
- Added a new test
- integration/src/test/java/com/arcadedb/integration/backup/FullBackupIT.java
- Added a new integration test
testEncryptedFullBackupCommandLineOK(lines 77-95) to verify the full backup and restore process using command-line parameters with an encryption key, including database comparison.
- Added a new integration test
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Data safe and sound,
Encrypted, can't be found,
Backup key is gold.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Code Review
This pull request introduces a significant and valuable feature: encrypted backup and restore capabilities. The use of AES/CTR for encryption is a solid choice. The changes are well-structured, particularly the separation of encryption/decryption logic using callbacks.
However, there are critical security concerns regarding how the encryption key is derived from the user-provided passphrase. Additionally, there are several areas for improvement in terms of correctness, maintainability, and adherence to best practices, such as handling expression evaluation for settings and using constants for cryptographic parameters and setting keys.
Addressing the security issue is paramount before this PR can be considered for merging.
Summary of Findings
- Security: Encryption Key Derivation: The most critical issue is the direct use of the user-provided passphrase (padded/truncated) as the AES encryption key. This is a significant security vulnerability. A strong Key Derivation Function (KDF) like PBKDF2, scrypt, or Argon2, along with a salt, must be used to derive the actual encryption key from the passphrase. This was noted for both
FullBackupFormat.javaandFullRestoreFormat.java. - Correctness: Setting Value Retrieval in SQL Parser: In
BackupDatabaseStatement.java, the retrieval of encryption setting values like((BaseExpression) entry.getValue().value).stringmight incorrectly include quotes from the string literal. It should useentry.getValue().execute(null, context)to get the properly evaluated string. - Maintainability: Magic Strings for Settings and Parameters: Strings like
"encryptionAlgorithm"and"encryptionKey"are used as magic strings inBackupDatabaseStatement.java,BackupSettings.java, andRestoreSettings.java. These should be defined as constants. - Maintainability: Hardcoded Cryptographic Parameters: Key and IV lengths (32 and 16 bytes) are hardcoded in
FullBackupFormat.javaandFullRestoreFormat.java. These should be named constants. - Correctness: Charset for Encryption Key: Using
DatabaseFactory.getDefaultCharset()for converting the encryption key string to bytes can be problematic if the default charset varies. Explicitly usingStandardCharsets.UTF_8is recommended for consistency. - Maintainability: Reflection Usage: Reflection is used in
BackupDatabaseStatement.javato call methods on theBackupclass. While this might be for decoupling, it reduces type safety and refactorability. If possible, direct calls or interfaces are preferred. - Style: Wildcard Imports (Not Commented): Several files (
BackupDatabaseStatement.java,FullBackupFormat.java,FullRestoreFormat.java) use wildcard imports (e.g.,java.io.*,java.util.*). While not a critical issue, explicit imports are often preferred for clarity. This was not commented on due to review settings (low severity). - Testing: Hardcoded Keys in Tests (Not Commented): Test files
BackupDatabaseTest.javaandFullBackupIT.javause hardcoded encryption keys. This is standard for testing but worth noting. This was not commented on due to review settings (low severity).
Merge Readiness
This pull request introduces a crucial feature for data security. However, due to the critical security vulnerability in how encryption keys are handled (direct use of passphrase instead of a KDF), this PR is not ready for merging. This issue must be addressed urgently.
Additionally, there are several high and medium severity issues related to correctness in parsing SQL settings, and maintainability (magic strings, hardcoded crypto parameters, reflection usage) that should also be resolved.
I am unable to approve this pull request in its current state. Please address the identified issues, especially the security concerns, before seeking further review and merging.
| final ZipOutputStream zipFile; | ||
| if (settings.encryptionKey != null) { | ||
| // Generate a random AES key | ||
| final byte[] keyBytes = Arrays.copyOf(settings.encryptionKey.getBytes(DatabaseFactory.getDefaultCharset()), 32); |
There was a problem hiding this comment.
Using settings.encryptionKey.getBytes(...) directly (padded/truncated to 32 bytes) as an AES key is a significant security vulnerability. Passphrases or user-provided strings should never be used directly as cryptographic keys.
A Key Derivation Function (KDF) such as PBKDF2WithHmacSHA256, scrypt, or Argon2 should be used to derive a cryptographically strong key from the user-provided encryptionKey. This process involves a salt (which should be randomly generated and stored alongside the IV) and a configurable iteration count.
For example, to derive a key using PBKDF2:
// Generate a random salt (e.g., 16 bytes)
byte[] salt = new byte[16];
new java.security.SecureRandom().nextBytes(salt);
// Store this salt at the beginning of the backup file, similar to the IV.
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
// Iteration count should be high, e.g., 65536 or more
KeySpec spec = new PBEKeySpec(settings.encryptionKey.toCharArray(), salt, 65536, 256); // 256-bit key
SecretKey tmp = factory.generateSecret(spec);
byte[] derivedKeyBytes = tmp.getEncoded();
final javax.crypto.SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(derivedKeyBytes, settings.encryptionAlgorithm);This derived key should then be used for encryption. The same salt and iteration count must be used during decryption.
Could you refactor this to use a proper KDF?
There was a problem hiding this comment.
@gemini I agree, Can you do modify this?
| final ZipInputStream zipFile; | ||
| if (settings.encryptionKey != null) { | ||
| // Prepare AES key | ||
| final byte[] keyBytes = Arrays.copyOf(settings.encryptionKey.getBytes(DatabaseFactory.getDefaultCharset()), 32); |
There was a problem hiding this comment.
This is the same critical security vulnerability as in FullBackupFormat.java. The user-provided encryptionKey is being converted to bytes and used directly (padded/truncated) as the AES key.
A Key Derivation Function (KDF) like PBKDF2WithHmacSHA256 must be used here as well. The salt used during backup (which should be stored in the backup file, e.g., before the IV) must be read here and used with the same KDF, iteration count, and user passphrase to re-derive the exact same cryptographic key for decryption.
Could this be updated to use a KDF, ensuring it's compatible with the KDF used during backup?
| switch (entry.getKey().toString()) { | ||
| case "encryptionAlgorithm" -> clazz.getMethod("setEncryptionAlgorithm", String.class) | ||
| .invoke(backup, ((BaseExpression) entry.getValue().value).string); | ||
| case "encryptionKey" -> | ||
| clazz.getMethod("setEncryptionKey", String.class).invoke(backup, ((BaseExpression) entry.getValue().value).string); |
There was a problem hiding this comment.
There are a couple of concerns here:
-
Correctness of Setting Value Retrieval: The method
((BaseExpression) entry.getValue().value).stringto retrieve the string value for encryption settings appears to fetch the raw string literal, potentially including quotes (e.g.,"'mySecretKey'"). This is likely not the intended behavior, as the encryption mechanism would then use a key that includes these quotes. The correct way to get the evaluated string value from anExpressionis typically by calling itsexecute()method. Could you clarify ifentry.getValue().execute(null, context)would be more appropriate here to get the actual string value? -
Maintainability - Magic Strings: The strings
"encryptionAlgorithm"and"encryptionKey"are used directly. It's better practice to define these as constants, perhaps inBackupSettingsor a shared constants class, to avoid typos and improve maintainability. -
Maintainability - Reflection: While reflection might be used for decoupling, directly invoking methods like
setEncryptionAlgorithmandsetEncryptionKeyvia reflection can make the code harder to debug, refactor, and less type-safe. Ifcom.arcadedb.integration.backup.Backupis accessible, consider a more direct invocation method or an interface if loose coupling is strictly necessary. If reflection must be used, consider defining method names as constants.
| switch (entry.getKey().toString()) { | |
| case "encryptionAlgorithm" -> clazz.getMethod("setEncryptionAlgorithm", String.class) | |
| .invoke(backup, ((BaseExpression) entry.getValue().value).string); | |
| case "encryptionKey" -> | |
| clazz.getMethod("setEncryptionKey", String.class).invoke(backup, ((BaseExpression) entry.getValue().value).string); | |
| switch (entry.getKey().toString()) { | |
| case BackupSettings.ENCRYPTION_ALGORITHM_SETTING: // Assuming ENCRYPTION_ALGORITHM_SETTING = "encryptionAlgorithm" | |
| Object algoValue = entry.getValue().execute(null, context); | |
| if (!(algoValue instanceof String)) { | |
| throw new CommandExecutionException("Value for 'encryptionAlgorithm' must be a string."); | |
| } | |
| clazz.getMethod("setEncryptionAlgorithm", String.class).invoke(backup, (String) algoValue); | |
| break; | |
| case BackupSettings.ENCRYPTION_KEY_SETTING: // Assuming ENCRYPTION_KEY_SETTING = "encryptionKey" | |
| Object keyValue = entry.getValue().execute(null, context); | |
| if (!(keyValue instanceof String)) { | |
| throw new CommandExecutionException("Value for 'encryptionKey' must be a string."); | |
| } | |
| clazz.getMethod("setEncryptionKey", String.class).invoke(backup, (String) keyValue); | |
| break; | |
| } | |
| case "encryptionAlgorithm" -> encryptionAlgorithm = value; | ||
| case "encryptionKey" -> encryptionKey = value; |
There was a problem hiding this comment.
The case labels "encryptionAlgorithm" and "encryptionKey" are magic strings. To improve maintainability and reduce the risk of typos, would it be better to define these as public static final String constants within this class or a shared constants class and use those constants here?
// Example constants in BackupSettings class:
// public static final String ENCRYPTION_ALGORITHM_SETTING = "encryptionAlgorithm";
// public static final String ENCRYPTION_KEY_SETTING = "encryptionKey";
switch (name) {
case ENCRYPTION_ALGORITHM_SETTING -> encryptionAlgorithm = value;
case ENCRYPTION_KEY_SETTING -> encryptionKey = value;| final ZipOutputStream zipFile; | ||
| if (settings.encryptionKey != null) { | ||
| // Generate a random AES key | ||
| final byte[] keyBytes = Arrays.copyOf(settings.encryptionKey.getBytes(DatabaseFactory.getDefaultCharset()), 32); |
There was a problem hiding this comment.
Using DatabaseFactory.getDefaultCharset() for converting the encryptionKey string to bytes can be risky if this default charset is not guaranteed to be consistent across all environments where backup and restore operations might occur (e.g., UTF-8). If different charsets are used, the derived byte array for the key will differ, leading to decryption failures.
It's generally safer to explicitly specify a standard charset, like StandardCharsets.UTF_8, when converting passphrases to bytes for cryptographic purposes, especially before feeding into a Key Derivation Function (which is also recommended for this encryptionKey).
What are your thoughts on standardizing this to StandardCharsets.UTF_8?
| final byte[] keyBytes = Arrays.copyOf(settings.encryptionKey.getBytes(DatabaseFactory.getDefaultCharset()), 32); | ||
| final javax.crypto.SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(keyBytes, settings.encryptionAlgorithm); | ||
|
|
||
| // Initialize cipher | ||
| final javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(settings.encryptionAlgorithm + "/CTR/NoPadding"); | ||
| final byte[] iv = new byte[16]; | ||
| new java.security.SecureRandom().nextBytes(iv); |
There was a problem hiding this comment.
The key size (32 bytes for AES-256) and IV size (16 bytes for AES) are hardcoded. While these are standard values for AES, defining them as named constants (e.g., AES_256_KEY_SIZE_BYTES, AES_IV_LENGTH_BYTES) would improve code readability and maintainability, making it clear what these numbers represent.
For example:
private static final int AES_KEY_SIZE_BYTES = 32; // 256-bit
private static final int AES_IV_LENGTH_BYTES = 16;
// ...
final byte[] keyBytes = Arrays.copyOf(settings.encryptionKey.getBytes(...), AES_KEY_SIZE_BYTES);
// ...
final byte[] iv = new byte[AES_IV_LENGTH_BYTES];Would you consider refactoring these magic numbers into constants?
| case "encryptionAlgorithm" -> { | ||
| encryptionAlgorithm = value; | ||
| yield 2; | ||
| } | ||
| case "encryptionKey" -> { | ||
| encryptionKey = value; |
There was a problem hiding this comment.
Similar to BackupSettings, the case labels "encryptionAlgorithm" and "encryptionKey" are magic strings. Would it be beneficial to define these as public static final String constants, possibly shared with BackupSettings or in a common constants class, to enhance maintainability?
// Example constants (could be in a shared class or this class)
// public static final String ENCRYPTION_ALGORITHM_SETTING = "encryptionAlgorithm";
// public static final String ENCRYPTION_KEY_SETTING = "encryptionKey";
case ENCRYPTION_ALGORITHM_SETTING -> {
encryptionAlgorithm = value;
yield 2;
}
case ENCRYPTION_KEY_SETTING -> {
encryptionKey = value;
yield 2;
}| final ZipInputStream zipFile; | ||
| if (settings.encryptionKey != null) { | ||
| // Prepare AES key | ||
| final byte[] keyBytes = Arrays.copyOf(settings.encryptionKey.getBytes(DatabaseFactory.getDefaultCharset()), 32); |
There was a problem hiding this comment.
As with the backup process, using DatabaseFactory.getDefaultCharset() for converting the encryptionKey to bytes can lead to issues if the default charset isn't consistent. For robust decryption, it's crucial that the byte representation of the key matches exactly what was used during encryption.
Consider standardizing on StandardCharsets.UTF_8 here as well, especially when used in conjunction with a KDF.
| final byte[] keyBytes = Arrays.copyOf(settings.encryptionKey.getBytes(DatabaseFactory.getDefaultCharset()), 32); | ||
| final javax.crypto.SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(keyBytes, settings.encryptionAlgorithm); | ||
|
|
||
| // Read IV from the beginning of the file | ||
| final byte[] iv = new byte[16]; |
There was a problem hiding this comment.
The key size (32 bytes) and IV size (16 bytes) are hardcoded here as well. Similar to the backup format, defining these as named constants (e.g., AES_256_KEY_SIZE_BYTES, AES_IV_LENGTH_BYTES) would improve clarity and maintainability.
For example:
private static final int AES_KEY_SIZE_BYTES = 32; // 256-bit
private static final int AES_IV_LENGTH_BYTES = 16;
// ...
final byte[] keyBytes = Arrays.copyOf(settings.encryptionKey.getBytes(...), AES_KEY_SIZE_BYTES);
// ...
final byte[] iv = new byte[AES_IV_LENGTH_BYTES];Would you consider this refactoring?
Followed code review: #2222 (comment)
Coverage summary from CodacySee diff coverage on Codacy
Coverage variation details
Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: Diff coverage details
Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: See your quality gate settings Change summary preferences |
robfrank
left a comment
There was a problem hiding this comment.
LGTM for me, maybe you can implement what static analysis and Gemini are suggesting to improve the code
… to 2.1.36 [skip ci] Bumps [io.swagger.parser.v3:swagger-parser](https://github.com/swagger-api/swagger-parser) from 2.1.35 to 2.1.36. Release notes *Sourced from [io.swagger.parser.v3:swagger-parser's releases](https://github.com/swagger-api/swagger-parser/releases).* > Swagger-parser 2.1.36 released! > ------------------------------- > > * fix: same name and different location in param + small refactor ([#2250](https://redirect.github.com/swagger-api/swagger-parser/issues/2250)) > * Fixed [#2222](https://redirect.github.com/swagger-api/swagger-parser/issues/2222) (Not being able to have different discriminator values map to the same entity) by switching key and value in map ([#2249](https://redirect.github.com/swagger-api/swagger-parser/issues/2249)) Commits * [`dddcf0f`](swagger-api/swagger-parser@dddcf0f) prepare release 2.1.36 ([#2251](https://redirect.github.com/swagger-api/swagger-parser/issues/2251)) * [`bafaee7`](swagger-api/swagger-parser@bafaee7) fix: same name and different location in param + small refactor ([#2250](https://redirect.github.com/swagger-api/swagger-parser/issues/2250)) * [`676c821`](swagger-api/swagger-parser@676c821) Fix discriminator key and value were getting mixed up resulting in DuplicateK... * [`ef2cf16`](swagger-api/swagger-parser@ef2cf16) bump snapshot 2.1.36-SNAPSHOT ([#2240](https://redirect.github.com/swagger-api/swagger-parser/issues/2240)) * See full diff in [compare view](swagger-api/swagger-parser@v2.1.35...v2.1.36) [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- Dependabot commands and options You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
This pull request introduces encryption support for database backups and restores, along with associated tests and refactoring. The most significant changes include adding encryption settings to the backup and restore processes, implementing encryption and decryption logic, and updating the grammar and tests to support these features.
Encryption Support for Backup and Restore:
BackupDatabaseStatement(SQLGrammar.jjt,BackupDatabaseStatement.java): Added support for specifying encryption settings (encryptionAlgorithmandencryptionKey) in SQL commands using theWITHclause. These settings are stored in aMapand applied during backup execution. [1] [2] [3]BackupSettingsandRestoreSettings(BackupSettings.java,RestoreSettings.java): AddedencryptionAlgorithmandencryptionKeyfields with default values. Updated parameter parsing to handle encryption-related options. [1] [2] [3] [4]Encryption and Decryption Logic:
FullBackupFormatandFullRestoreFormat(FullBackupFormat.java,FullRestoreFormat.java): ImplementedencryptFileanddecryptFilemethods using AES encryption. These methods wrap the backup and restore streams with encryption or decryption logic based on the provided settings. [1] [2]Grammar and Test Updates:
BackupDatabaseTestandFullBackupIT(BackupDatabaseTest.java,FullBackupIT.java): Added new test cases to verify encrypted backups and restores using encryption keys. These tests ensure the integrity and correctness of encrypted backups. [1] [2]These changes collectively enhance the database's backup and restore functionality by introducing encryption, ensuring data security during these operations.