New initialisation parameters for Transparent Data Encryption beginning in 19c

This blog post is a short summary of my attempt to retrofit Transparent Data Encryption (TDE) into an existing Data Guard environment featuring a single-instance Oracle 19c Container Database (CDB) on a file system. Neither Oracle Restart nor Real Application Clusters are in use to keep the size of the article a little more manageable.

Setting the stage

Reading the documentation I noticed that the way I used to configure TDE changed, there are two new initialisation parameters to be used; SQLNET.ENCRYPTION_WALLET_LOCATION has been deprecated and should no longer be used. The 2 parameters in question are:

  • wallet_root
  • tde_configuration

This article assumes you have prior knowledge of TDE, if not I suggest you head over to the Advanced Security Guide for Oracle 19c. I won’t be so bold as to make the claim my configuration is perfect or even 100% correct; it does seem to work though. Please make sure that you get sign-off from your security team once you completed your TDE PoC.

If you are interested in Transparent Data Encryption please ensure your database is appropriately licensed to use the feature!

Reference

I used the following chapters in the Oracle Advanced Security Guide as reference:

  • Chapter 8: Using Transparent Data Encryption with Other Oracle Features
  • Chapter 5: Managing Keystores and TDE Master Encryption Keys in United Mode
  • Chapter 3: Configuring Transparent Data Encryption

Current Environment

Key properties of the environment I’m creating in the lab:

  • I’ll use a software keystore
  • Oracle Database 19c Enterprise Edition, with the 19.20.0 RU applied
  • I use a Container Database with 1 PDB (pdb1). The single-instance database has just been created by dbca without TDE. Data Guard has already been configured
  • The database’s global database name is ORCL with db_unique_names SITEA and SITEB; SITEA is currently running in primary role. Each database’s ORACLE_SID matches its unique name
  • Configuration of “united mode” where all PDBs share the same keystore with the CDB$ROOT
  • I would like to use an auto-login keystore, trading security for convenience
  • All Pluggable Databases are open read-write all the time to reduce complexity

Note that in united mode, you create the keystore and TDE master encryption key for CDB and PDBs that reside in the same keystore.

Configure location and keystore type

This step is significantly different from previous releases, and the main new thing in 19c from what I understand. Here I create a new wallet_root directory on the file system for use with the database.

[oracle@dgnode1 ~]$ mkdir -p /u01/app/oracle/admin/${ORACLE_SID}/wallets || 
> echo "the directory exists, check your configuration"

Repeat this step for the standby database(s), adapt the path as needed.

Connect to CDB$ROOT and set wallet_location and tde_configuration in the server parameter file. Setting wallet_root is preferred over setting ENCRYPTION_WALLET_LOCATION in sqlnet.ora. The documentation reads:

Starting with Oracle Database release 19c, the ENCRYPTION_WALLET_LOCATION parameter is deprecated. Instead, use the WALLET_ROOT static initialization parameter and the TDE_CONFIGURATION dynamic initialization parameter to configure the wallet location. WALLET_ROOT and TDE_CONFIGURATION can be used for a regular file system, multiple database access, and ASM

Additionally you should consider setting tablespace_encryption to auto_enable. That way you don’t miss encrypting new tablespaces.

[oracle@dgnode1 ~]$ sqlplus / as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Tue Oct 31 14:05:17 2023                                                                                 
Version 19.20.0.0.0

Copyright (c) 1982, 2022, Oracle.  All rights reserved.

                                                                            
Connected to:                                                                                                                       
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production                                                                                
Version 19.20.0.0.0                                                                                

SQL> alter system set tablespace_encryption=auto_enable scope=spfile;

System altered.                                                                                                                                                 

SQL> alter system set wallet_root='/u01/app/oracle/admin/$ORACLE_SID/wallets' scope=spfile;
                                                                                                                                                  
System altered. 

Unfortunately it is necessary to bounce the instance as wallet_root is a static parameter. You have to set wallet_root before you can change tde_configuration. Apply the finishing touches after you restarted the database during a convenient change window.

SQL> alter system set tde_configuration="keystore_configuration=file" scope=both;

System altered.

SQL> select name, value from v$parameter
  2  where name in ('wallet_root', 'tde_configuration');

NAME                 VALUE
-------------------- ------------------------------------------------------
wallet_root          /u01/app/oracle/admin/SITEA/wallets
tde_configuration    keystore_configuration=file

Create the software keystore

Once the initialisation parameters are set connect to the primary database and create a local auto-login keystore. Make sure you are connected to CDB$ROOT. I prefer to open all PDBs read-write, too.

SQL> show con_name

CON_NAME
------------------------------
CDB$ROOT

SQL> select database_role, name, db_unique_name from v$database;

DATABASE_ROLE    NAME                           DB_UNIQUE_NAME
---------------- ------------------------------ ------------------------------
PRIMARY          ORCL                           SITEA

SQL> show pdbs

    CON_ID CON_NAME                       OPEN MODE  RESTRICTED
---------- ------------------------------ ---------- ----------
         2 PDB$SEED                       READ ONLY  NO
         3 PDB1                           READ WRITE NO

SQL> set verify off

Create the keystore next, at first it’s protected by a password. In the next step you’ll create a local auto-login keystore.

administer key management 
create keystore
identified by &password;

The keystore is now present, as seen by v$encryption_wallet:

SQL> select wrl_parameter, status, wallet_type, keystore_mode, con_id from v$encryption_wallet;

WRL_PARAMETER                                STATUS                         WALLET_TYPE          KEYSTORE     CON_ID
-------------------------------------------- ------------------------------ -------------------- -------- ----------
/u01/app/oracle/admin/SITEA/wallets/tde/     CLOSED                         UNKNOWN              NONE              1
                                             CLOSED                         UNKNOWN              UNITED            2
                                             CLOSED                         UNKNOWN              UNITED            3

The value of NONE in KEYSTORE_MODE for CON_ID 1 is expected, as is the absence of wallet locations for the PDBs. CON_ID 1 (aka CDB$ROOT) owns the keystore for all PDBs (aka united mode).

Note how the keystore is created, but it’s closed. Here I had to make a choice whether to keep the keystore password protected or transforming it to a local auto login keystore. The higher degree of security in the first case is offset by the necessity to enter the keystore password when starting the database (well you could store the password in an additional secure external password store, but that’s not in scope of this article). In my lab environment I wanted to be able to start the database without manual intervention, so a (local) auto login keystore it is.

Deciding between an auto login keystore and a password protected keystore isn’t a decision for the DBA to make. This is one for the security team!

Create a local auto-login keystore on the primary

In this step the local auto-login keystore is created.

SQL> administer key management
 create local auto_login keystore
 from keystore identified by &password;

SQL> select wrl_parameter, status, wallet_type, keystore_mode, con_id from v$encryption_wallet;

WRL_PARAMETER                                STATUS                         WALLET_TYPE          KEYSTORE     CON_ID
-------------------------------------------- ------------------------------ -------------------- -------- ----------
/u01/app/oracle/admin/SITEA/wallets/tde/     OPEN_NO_MASTER_KEY             LOCAL_AUTOLOGIN      NONE              1
                                             OPEN_NO_MASTER_KEY             LOCAL_AUTOLOGIN      UNITED            2
                                             OPEN_NO_MASTER_KEY             LOCAL_AUTOLOGIN      UNITED            3

Since you created a local auto-login password store it is not necessary to open it. A master key is missing though.

Interlude: configuring an External Store for the keystore password

At this point you can decide to move the keystore password to a secure external password store (SEPS), rather than referencing it verbatim in the identified by password clause. There are many advantages of doing so, the least of which is not having to store the password in any scripts – a huge security gain.

I decided to take this approach, you are going to see the identified by external store clause from now on, instead of a password. Before you can crate the SEPS you must provide a location for it. By default it goes to wallet_root/tde_seps. Unlike wallet_root/tde Oracle won’t create this directory for you – it must be created first before you can create the actual SEPS.

SQL> !mkdir /u01/app/oracle/admin/${ORACLE_SID}/wallets/tde_seps
SQL> administer key management add secret '&password'
for client 'TDE_WALLET'
to local auto_login keystore '/u01/app/oracle/admin/${ORACLE_SID}/wallets/tde_seps'

Create the TDE master encryption key in CDB$ROOT

Make sure to connect to CDB$ROOT as SYSDBA or SYSKM to set the first key on the primary.

SQL> administer key management 
  set key 
  force keystore 
  identified by external store
  with backup 
  container=current;

You’ll notice that the key was created in CDB$ROOT and the seed-PDB:

SQL> select wrl_parameter, status, wallet_type, keystore_mode, con_id from v$encryption_wallet;

WRL_PARAMETER                                STATUS                         WALLET_TYPE          KEYSTORE     CON_ID
-------------------------------------------- ------------------------------ -------------------- -------- ----------
/u01/app/oracle/admin/SITEA/wallets/tde/     OPEN                           LOCAL_AUTOLOGIN      NONE              1
                                             OPEN                           LOCAL_AUTOLOGIN      UNITED            2
                                             OPEN_NO_MASTER_KEY             LOCAL_AUTOLOGIN      UNITED            3

Next it’s time to set keys in all PDBs. There is only one so that’s not too hard. The following command does that for me:

alter session set container = pdb1;

show con_name

administer key management set key
using tag 'PDB1 2023-10-31 14:38:11Z'
force keystore
identified by external store
with backup;

With all the PDBs dealt with it time to hop back to CDB$ROOT on the primary. Everything looks all right:

SQ> alter session set container = CDB$ROOT;

SQL> select wrl_parameter, status, wallet_type, keystore_mode, con_id from v$encryption_wallet;

WRL_PARAMETER                                STATUS                         WALLET_TYPE          KEYSTORE     CON_ID
-------------------------------------------- ------------------------------ -------------------- -------- ----------
/u01/app/oracle/admin/SITEA/wallets/tde/     OPEN                           LOCAL_AUTOLOGIN      NONE              1
                                             OPEN                           LOCAL_AUTOLOGIN      UNITED            2
                                             OPEN                           LOCAL_AUTOLOGIN      UNITED            3

This concludes the setup on the primary database. Next the keystore (and secure external password store containing the keystore password if you have one) must be made available on the standby.

Configuring the standby database

The easiest way to transfer the keystore to the standby is to use scp or rsync. The local auto-login wallet is tied to the host and can’t be used on the standby. There is no point copying it over, the following rsync command makes sure it’s not transferred:

rsync -rvpt --exclude '*.sso' \
/u01/app/oracle/admin/SITEA/wallets/tde/ \
dgnode2:/u01/app/oracle/admin/SITEB/wallets/tde/

Remember that rsync is picky about trailing slashes :) A new local auto-login keystore must be created. Making sure I’m connected to the right system:

SQL> select database_role, name, db_unique_name, open_mode from v$database;

DATABASE_ROLE    NAME      DB_UNIQUE_NAME                 OPEN_MODE
---------------- --------- ------------------------------ --------------------
PHYSICAL STANDBY ORCL      SITEB                          MOUNTED

SQL> show pdbs

    CON_ID CON_NAME                       OPEN MODE  RESTRICTED
---------- ------------------------------ ---------- ----------
         2 PDB$SEED                       MOUNTED
         3 PDB1                           MOUNTED

SQL> select wrl_parameter, status, wallet_type, keystore_mode, con_id from v$encryption_wallet;

WRL_PARAMETER                                      STATUS                         WALLET_TYPE          KEYSTORE     CON_ID
-------------------------------------------------- ------------------------------ -------------------- -------- ----------
/u01/app/oracle/admin/SITEB/wallets/tde/           CLOSED                         UNKNOWN              NONE              1

Let’s create the local auto-login keystore:

administer key management
create local auto_login keystore
from keystore identified by &password;

And here it is:

SQL> select wrl_parameter, status, wallet_type, keystore_mode, con_id from v$encryption_wallet;

WRL_PARAMETER                                      STATUS                         WALLET_TYPE          KEYSTORE     CON_ID
-------------------------------------------------- ------------------------------ -------------------- -------- ----------
/u01/app/oracle/admin/SITEB/wallets/tde/           OPEN                           LOCAL_AUTOLOGIN      NONE              1

The standby should now be ready. Jump back to the primary to start encrypting data.

Online encryption of tablespaces on the primary

Consult your security team as to which tablespaces must be encrypted. For CDB$ROOT this typically involves the USERS tablespace if it is used. SYSTEM and SYSAUX can optionally be encrypted, too. Here’s an example how to encrypt the SYSTEM tablespace online using the AES256 algorithm. Connect to the primary using an account granted the SYSKM privilege and run the following command for example.

SQL> alter tablespace SYSTEM encryption online using 'AES256' encrypt;                            
                                                                                                                                                      
Tablespace altered.

If you are transporting tablespaces you should spend some more time evaluating the algorithm; Oracle 19c defaults to AES128. I like AES256.

You can also encrypt the credentials of named user database links in the root container if needed:

alter database dictionary encrypt credentials container = current;

Repeat the online encryption operations – keeping an eye on space – for each PDB. You’d typically want to encrypt SYSTEM, SYSAUX, UNDO and any user-data tablespace. Maybe even TEMP. New tablespaces should automatically be encrypted, too, thanks to the correct setting of tablespace_encryption=auto_enable. You can influence the algorithm by setting _tablespace_encryption_default_algorithm. This parameter became an official init.ora parameter with 21c and later.

Ensure you TEST this procedure first before running these commands on systems you are about. Mainly for ETA but also space consumption and performance impact.

Verification of the encryption work

Once all the tablespaces in your PDBs and CDB$ROOT are encrypted it’s time for a quick check:

SELECT
    c.name          AS pdb_name,
    t.name          AS tbs_name,
    e.encryptionalg AS alg,
    e.status
FROM
    v$tablespace            t,
    v$encrypted_tablespaces e,
    v$containers            c
WHERE
    e.ts# = t.ts#
    AND e.con_id = t.con_id
    AND e.con_id = c.con_id
ORDER BY
    e.con_id,
    t.name
/

PDB_NAME             TBS_NAME                       ALG     STATUS
-------------------- ------------------------------ ------- ----------
CDB$ROOT             SYSAUX                         AES256  NORMAL
CDB$ROOT             SYSTEM                         AES256  NORMAL
CDB$ROOT             UNDOTBS1                       AES256  NORMAL
CDB$ROOT             USERS                          AES256  NORMAL
PDB1                 SYSAUX                         AES256  NORMAL
PDB1                 SYSTEM                         AES256  NORMAL
PDB1                 UNDOTBS1                       AES256  NORMAL
PDB1                 USERS                          AES256  NORMAL

8 rows selected.

Thanks to redo transport the standby database’s tablespaces are equally encrypted. Looking good!

Summary

There were a few steps required for me to enable TDE in Data Guard, but they didn’t impact the availability of my primary database. I like online encryption of systems while users are still connected and doing work.