
When cloning or refreshing an encrypted pluggable database (PDB) in Oracle Database 19c, it is easy to focus on the datafiles. Restore the backup, recover the database, and plug the PDB into the destination container database (CDB).
But don't forget the keys. With Transparent Data Encryption (TDE), the destination also needs access to the master encryption keys stored in the source wallet.
In this post I will walk through two ways to bring the keys with an encrypted PDB, and then show where those steps fit when the PDB is restored from backup. I am using Oracle Database 19c, local file-based TDE wallets, and united mode. In united mode the PDBs use the CDB's wallet, while keeping their own master encryption keys.
This builds on my posts about encrypting an Oracle PDB with TDE and Oracle wallet files for TDE and authentication.
NOTE: These are command examples to adapt and validate on your 19c Release Update. Database names, paths, passwords, and the recovery SCN are placeholders. This is a walkthrough of the key handling, not output from a completed lab.
For unplug/plug, I can bring the keys with ENCRYPT USING and DECRYPT USING, or export and import them separately. A refresh from RMAN backups also needs the historical keys during recovery into the auxiliary CDB.
In this post: Check the wallet · Unplug/plug with keys · Export and import keys · Refresh from RMAN backups · Rekey and verify · Merge wallets
TDE wallet keys: what needs to move with a PDB?
TDE has two levels of keys. The tablespace key encrypts the data, and the TDE master encryption key protects that key. The master key is stored in the keystore. Copying encrypted datafiles does not, by itself, give a different CDB access to that master key. Oracle's TDE overview
Figure 1. Move both the encrypted PDB datafiles and the required master keys into the destination environment.
For these examples I am using the following names.
| Name | Purpose |
|---|---|
PRODCDB / APPPDB |
Source CDB and encrypted PDB |
AUXCDB / APPPDB |
Temporary CDB used for a restore from backup |
TESTCDB / APP_REFRESH |
Existing destination CDB and the new refresh PDB |
SourceWalletPassword |
Password for the source wallet |
TargetWalletPassword |
Password for the destination wallet |
TransportSecret |
Separate secret protecting keys during transport |
The transport secret and the wallet passwords have different jobs. I do not need to make the source and destination wallet passwords match.
STEP #1 - Check the TDE wallet configuration
On each CDB, I first check the configuration and wallet status.
-- SQL*Plus, connected to the appropriate CDB root
SHOW CON_NAME
SHOW PARAMETER wallet_root
SHOW PARAMETER tde_configuration
SELECT con_id, status, wallet_type, keystore_mode,
wrl_parameter
FROM v$encryption_wallet
ORDER BY con_id;
The examples assume TDE_CONFIGURATION='KEYSTORE_CONFIGURATION=FILE', with the united wallet under WALLET_ROOT/tde. A PDB row should show UNITED; the root can show NONE. An OPEN wallet is a useful first check, but it does not prove that it contains every key needed for this refresh. Wallet status reference
For a password wallet that is closed, open it from the root. Use the password belonging to that CDB.
ADMINISTER KEY MANAGEMENT SET KEYSTORE OPEN
IDENTIFIED BY "SourceWalletPassword"
CONTAINER = ALL;
If auto-login already has the wallet open, check its state rather than blindly repeating the open. The key-management examples below use FORCE KEYSTORE where supported to allow access to the password wallet. Have the ewallet.p12 password available even when normal database startup uses cwallet.sso. Configuring TDE
I also record the source PDB's keys before the move.
ALTER SESSION SET CONTAINER = APPPDB;
SELECT key_id, creation_time, activation_time
FROM v$encryption_keys
ORDER BY activation_time;
This gives me key identifiers to compare later. I am recording metadata here, not displaying the secret key material. Encryption key view
Run the key operations as an appropriately privileged key administrator (SYSKM or ADMINISTER KEY MANAGEMENT), and the PDB operations with the necessary database privileges. The examples assume both are available to the administrative session.
METHOD #1 - Unplug and plug an encrypted PDB with its TDE keys
For a planned unplug/plug, I can protect the PDB's keys with a transport secret as part of the unplug operation.
1) Unplug on the source
-- PRODCDB root; source wallet must be open
ALTER SESSION SET CONTAINER = CDB$ROOT;
ALTER PLUGGABLE DATABASE APPPDB CLOSE IMMEDIATE;
ALTER PLUGGABLE DATABASE APPPDB
UNPLUG INTO '/secure_stage/apppdb.xml'
ENCRYPT USING "TransportSecret";
ENCRYPT USING protects the transported keys. The XML file is a manifest; the PDB's datafiles still have to be made available on the destination host. A .pdb archive is an alternative packaging format. Unplugging PDBs
NOTE: Unplugging takes this source PDB out of service. For a refresh that must leave production running, use the backup/auxiliary workflow below and unplug the restored copy.
2) Check compatibility at the destination
For this example, the XML and datafiles are accessible at the paths recorded in the XML. If the files were staged at different paths, account for that with SOURCE_FILE_NAME_CONVERT; FILE_NAME_CONVERT controls the destination copies.
-- TESTCDB root
SET SERVEROUTPUT ON
DECLARE
can_plug BOOLEAN;
BEGIN
can_plug := DBMS_PDB.CHECK_PLUG_COMPATIBILITY(
pdb_descr_file => '/secure_stage/apppdb.xml',
pdb_name => 'APP_REFRESH');
IF can_plug THEN
DBMS_OUTPUT.PUT_LINE('Compatible');
ELSE
RAISE_APPLICATION_ERROR(-20001,
'Review PDB_PLUG_IN_VIOLATIONS before continuing');
END IF;
END;
/
Resolve compatibility errors before creating the PDB, including applicable patch, component, and character-set issues. Plug compatibility check
3) Plug in the copy
The destination wallet must already exist and be open. APP_REFRESH must be a new PDB name in TESTCDB.
-- TESTCDB root; filesystem paths used for this example
CREATE PLUGGABLE DATABASE APP_REFRESH AS CLONE
USING '/secure_stage/apppdb.xml'
COPY
FILE_NAME_CONVERT =
('/u02/oradata/PRODCDB/APPPDB/',
'/u02/oradata/TESTCDB/APP_REFRESH/')
KEYSTORE IDENTIFIED BY "TargetWalletPassword"
DECRYPT USING "TransportSecret";
ALTER PLUGGABLE DATABASE APP_REFRESH OPEN;
Here, AS CLONE gives the plugged copy a new identity, and COPY creates its datafiles at the destination. DECRYPT USING supplies the transport secret, while KEYSTORE IDENTIFIED BY supplies the destination wallet password. Adapt file placement for OMF or ASM. CREATE PLUGGABLE DATABASE reference
Finish with the rekey and verification steps below. An initial restricted open is not the end of the process.
METHOD #2 - Export and import TDE keys for an Oracle 19c PDB
This is an alternative to Method #1. I use a separate export file when I want the key transfer to be an explicit step in the refresh process.
1) Export from inside the source PDB
-- PRODCDB, or AUXCDB after restoring the PDB
ALTER SESSION SET CONTAINER = APPPDB;
ADMINISTER KEY MANAGEMENT EXPORT ENCRYPTION KEYS
WITH SECRET "TransportSecret"
TO '/secure_stage/apppdb_keys.exp'
FORCE KEYSTORE
IDENTIFIED BY "SourceWalletPassword";
ALTER SESSION SET CONTAINER = CDB$ROOT;
ALTER PLUGGABLE DATABASE APPPDB CLOSE IMMEDIATE;
ALTER PLUGGABLE DATABASE APPPDB
UNPLUG INTO '/secure_stage/apppdb.xml';
Export inside the PDB. Do not add a WITH IDENTIFIER IN filter: a PDB export carries its keys and the metadata identifying the active key. Protect the export file and deliver its secret separately. Exporting keys leaves the source keys in place. Key export syntax
2) Import into the destination root before plugging, when required
If SYSTEM, SYSAUX, UNDO, or TEMP is encrypted, first import into TESTCDB's root. Then import again inside the new PDB to associate the keys with it. For a PDB without those encrypted system tablespaces, the pre-plug root import can be skipped. Oracle's united-mode PDB procedure
-- TESTCDB root: conditional pre-plug import
ALTER SESSION SET CONTAINER = CDB$ROOT;
ADMINISTER KEY MANAGEMENT IMPORT ENCRYPTION KEYS
WITH SECRET "TransportSecret"
FROM '/secure_stage/apppdb_keys.exp'
FORCE KEYSTORE
IDENTIFIED BY "TargetWalletPassword"
WITH BACKUP USING 'before_refresh_root_import';
3) Plug in, open the PDB wallet, and import inside the PDB
Run the compatibility check from Method #1 first. This manifest was created without ENCRYPT USING, so this branch does not use DECRYPT USING.
-- TESTCDB root
CREATE PLUGGABLE DATABASE APP_REFRESH AS CLONE
USING '/secure_stage/apppdb.xml'
COPY
FILE_NAME_CONVERT =
('/u02/oradata/PRODCDB/APPPDB/',
'/u02/oradata/TESTCDB/APP_REFRESH/')
KEYSTORE IDENTIFIED BY "TargetWalletPassword";
ALTER SESSION SET CONTAINER = APP_REFRESH;
-- If the PDB's password wallet is not already open:
ADMINISTER KEY MANAGEMENT SET KEYSTORE OPEN
FORCE KEYSTORE
IDENTIFIED BY "TargetWalletPassword";
ALTER SESSION SET CONTAINER = CDB$ROOT;
ALTER PLUGGABLE DATABASE APP_REFRESH OPEN;
ALTER SESSION SET CONTAINER = APP_REFRESH;
ADMINISTER KEY MANAGEMENT IMPORT ENCRYPTION KEYS
WITH SECRET "TransportSecret"
FROM '/secure_stage/apppdb_keys.exp'
FORCE KEYSTORE
IDENTIFIED BY "TargetWalletPassword"
WITH BACKUP USING 'before_refresh_pdb_import';
The PDB may initially open restricted. Complete the PDB import, rekey, and reopen before releasing it to the application. The root import and PDB import serve different purposes; do not treat the root import as completion of both steps.
Refresh an encrypted PDB from RMAN backups
I separate this into two handoffs: backup to auxiliary CDB, followed by auxiliary PDB to the existing destination CDB.
Figure 2. A backup refresh has two key handoffs: recovery into AUXCDB, then plug-in and key transfer into TESTCDB.
1) Make the historical keys available to the auxiliary
The auxiliary needs the keys required by the backup and recovery interval. Rotating the production master key does not eliminate the need for older keys. A wallet backup that predates a required rotation may be missing a key; a later wallet can contain the historical keys if they have been retained. TDE key history
For a newly prepared auxiliary, securely provision a copy of the source password wallet at its configured WALLET_ROOT/tde location. Use a wallet with the required CDB and PDB key history, not just a PDB key export. The duplicate also restores root and seed files. Keep the auxiliary's wallet separate from the live source and from TESTCDB's wallet. Preparing the auxiliary keystore
For example, the auxiliary initialization settings include:
db_name='AUXCDB'
db_unique_name='AUXCDB'
enable_pluggable_database=TRUE
wallet_root='/u01/app/oracle/admin/AUXCDB/wallet'
tde_configuration='KEYSTORE_CONFIGURATION=FILE'
This is only the relevant parameter excerpt. Prepare the auxiliary in NOMOUNT, with its own controlfile, datafile, redo, and recovery-area destinations, plus the password file and Oracle Net configuration required by your duplication method.
2) Restore the PDB into the temporary CDB
Here is a backup-based duplication example, with RMAN connected to PRODCDB as TARGET and AUXCDB as AUXILIARY. Both connections are to the root. The source backup pieces must be accessible to the auxiliary, including root, seed, PDB, controlfile, and the archived redo needed to reach the chosen SCN.
-- RMAN: connected to the two CDB roots
SET DECRYPTION WALLET OPEN
IDENTIFIED BY 'SourceWalletPassword';
RUN {
SET UNTIL SCN 123456789;
SET NEWNAME FOR DATABASE TO '/u02/oradata/AUXCDB/%U';
DUPLICATE TARGET DATABASE TO AUXCDB
PLUGGABLE DATABASE APPPDB
LOGFILE
GROUP 1 ('/u02/oradata/AUXCDB/redo01.log') SIZE 200M,
GROUP 2 ('/u02/oradata/AUXCDB/redo02.log') SIZE 200M,
GROUP 3 ('/u02/oradata/AUXCDB/redo03.log') SIZE 200M;
}
Replace the SCN and file layout with values for your restore. This creates a new auxiliary CDB containing the selected PDB, not a PDB directly inside TESTCDB. Oracle 19c's DUPLICATE PLUGGABLE DATABASE ... TO existing_cdb form supports active duplication; it is not the backup-based command used here. RMAN DUPLICATE reference
SET DECRYPTION WALLET OPEN supplies the wallet password across auxiliary restarts during duplication. It does not transfer missing keys. If the backup pieces have a separate password-encryption layer, also supply the applicable backup password using SET DECRYPTION IDENTIFIED BY. That password does not replace the keys needed for the TDE-encrypted datafiles. RMAN SET reference
3) Transfer from AUXCDB into TESTCDB
After successful recovery and opening the restored PDB, use either Method #1 or Method #2, with AUXCDB as the source.
Create a fresh manifest from the restored PDB, and export its keys there if using Method #2. Update FILE_NAME_CONVERT to match the restored files' actual paths. In the RMAN example, %U generates filenames directly under /u02/oradata/AUXCDB/; inspect those filenames rather than assuming an APPPDB subdirectory exists.
I would create APP_REFRESH alongside the current test PDB, validate it, and then handle the application/service cutover. The destructive replacement of an existing PDB is a separate decision. This is a repeated refresh from backups, not a REFRESH MODE clone.
Rekey the cloned PDB, reopen, and verify
Once the imported keys are available, create and activate a destination master key inside APP_REFRESH.
-- TESTCDB, inside the new PDB only
ALTER SESSION SET CONTAINER = APP_REFRESH;
ADMINISTER KEY MANAGEMENT SET KEY
FORCE KEYSTORE
IDENTIFIED BY "TargetWalletPassword"
WITH BACKUP USING 'app_refresh_rekey'
CONTAINER = CURRENT;
ALTER SESSION SET CONTAINER = CDB$ROOT;
ALTER PLUGGABLE DATABASE APP_REFRESH CLOSE IMMEDIATE;
ALTER PLUGGABLE DATABASE APP_REFRESH OPEN;
Use CONTAINER=CURRENT for this PDB; there is no reason for a refresh to rotate every PDB's key. Changing the wallet password is also a different operation from generating a new master key. Key-management operations
Rekeying gives the destination a new master key. It does not make historical backups independent of their original keys. Retain those keys for as long as the corresponding recovery requirements exist. TDE key architecture FAQ
Now I check the result.
-- TESTCDB root
SELECT name, open_mode, restricted
FROM v$pdbs
WHERE name = 'APP_REFRESH';
SELECT time, name, cause, type, message, status, action
FROM pdb_plug_in_violations
WHERE name = 'APP_REFRESH'
AND status <> 'RESOLVED'
ORDER BY time;
ALTER SESSION SET CONTAINER = APP_REFRESH;
SELECT status, keystore_mode, fully_backed_up
FROM v$encryption_wallet;
SELECT key_id, creation_time, activation_time
FROM v$encryption_keys
ORDER BY activation_time;
SELECT tablespace_name, encrypted
FROM dba_tablespaces
ORDER BY tablespace_name;
I want READ WRITE, RESTRICTED=NO, and all blocking plug-in violations resolved. I also read known application data from an encrypted tablespace and check the expected refresh timestamp or business totals. Opening the PDB alone is not my application validation. Completing a PDB plug-in
Finally, back up the updated destination wallet and the refreshed PDB. WITH BACKUP protects the wallet before a change; I also want a retained copy containing the newly created key.
-- TESTCDB root, after successful rekey
ALTER SESSION SET CONTAINER = CDB$ROOT;
ADMINISTER KEY MANAGEMENT BACKUP KEYSTORE
USING 'after_app_refresh'
FORCE KEYSTORE
IDENTIFIED BY "TargetWalletPassword"
TO '/secure_wallet_backups/TESTCDB';
Keep password-wallet backups and their passwords recoverable, with access controlled separately from the database backups. An auto-login wallet stored beside encrypted backups weakens that separation. RMAN encryption and wallet backup guidance
Migrate TDE wallet keys with MERGE KEYSTORE
For one PDB, I would normally use the PDB export/import above. A wallet merge has broader scope: it adds the source wallet's keys to another wallet.
Here is a merge example using staged wallet copies, outside the live wallet directories:
-- Optional wallet administration example; not a PDB refresh step
ADMINISTER KEY MANAGEMENT
MERGE KEYSTORE '/secure_stage/source_wallet'
IDENTIFIED BY "SourceWalletPassword"
INTO EXISTING KEYSTORE '/secure_stage/target_wallet_copy'
IDENTIFIED BY "TargetWalletPassword"
WITH BACKUP USING 'before_wallet_merge';
This updates the staged destination password wallet and leaves the source unchanged. It does not switch the live database to that staged wallet. A planned wallet migration must also configure the final location, reopen the merged wallet, and update or recreate its auto-login companion as appropriate. Coordinate that change across all consumers of the wallet. Merging and relocating wallets
NOTE: Do not copy the source ewallet.p12 over the wallet of an existing destination CDB. That destination may need keys for other PDBs. Also, MOVE KEYS removes selected keys from the source keystore and is not a substitute for a clone's export/import. MIGRATE USING is for changing the keystore provider, such as migrating to Oracle Key Vault. Key-management SQL reference
The operational check I would add to every refresh job is simple: record which backup was restored, which keys were supplied, where the destination wallet was backed up, and whether encrypted application data was successfully read. Those four items make the next refresh, and the next recovery exercise, much easier to review.

