In an always-confidential database, each user's data is encrypted with their own data encryption keys (DEKs). Ciphertext data from different users is automatically isolated. To run joint queries across users' encrypted data, or to authorize a database administrator (DBA) to perform high-risk operations such as creating a custom key or converting data between plaintext and ciphertext, use a behavior control list (BCL) to grant the necessary access.
How it works
A BCL is a signed authorization artifact issued by a data owner (the issuer) to a party that needs access (the subject). After a BCL is issued and accepted, the subject can operate on the issuer's ciphertext data within the boundaries defined in the BCL — without ever seeing the underlying plaintext. The issuer can revoke the BCL at any time to stop the subject's access.
For high-risk operations such as creating a custom key or converting data between plaintext and ciphertext, the issuer and subject can be the same account (a self-BCL).
Prerequisites
Before you begin, ensure that you have:
The always-confidential database feature enabled. For more information, see Enable the always-confidential database feature.
The minor engine version of the RDS instance must be 20230830 or later. To update the minor engine version, see Update the minor engine version.
Separate accounts created for the issuer and the subject (unless using a self-BCL). For more information, see Create an account
Sensitive data defined. For more information, see Define sensitive data
The EncDB SDK configured for both the issuer and the subject, with test data in the database. For more information, see Use the always-confidential database feature from a client
Choose your scenario
Identify your use case before following the steps below:
| Scenario | Description | Issuer and subject |
|---|---|---|
| Joint queries of multi-user data | A party (subject) needs to run queries that join the issuer's encrypted columns with its own data | Different accounts |
| DBA high-risk operations | A DBA needs to convert plaintext data to ciphertext (or vice versa) on an always-confidential database | Same account (self-BCL) |
The steps for issuing a BCL are the same in both scenarios. The key difference is in the BCL parameters — see Scenario-specific BCL configuration.
Step 1: Generate public and private keys
Both the issuer and the subject need a key pair. The algorithm depends on your instance edition.
Determine your edition:
| Edition | Instance type | Algorithm |
|---|---|---|
| Hardware-enhanced edition | Intel Software Guard Extensions (SGX)-based security-enhanced | RSA |
| Basic edition | Non-Intel SGX-based security-enhanced | SM2 |
To identify your instance type, see Instance types for primary ApsaraDB RDS for PostgreSQL instances.
Hardware-enhanced edition (Intel SGX-based): RSA keys
Run the following commands to generate an RSA key pair:
# Generate an RSA private key (3072-bit)
openssl genpkey -algorithm RSA -out pri_key_pkcs8.pem -pkeyopt rsa_keygen_bits:3072
# Derive the RSA public key
openssl rsa -in pri_key_pkcs8.pem -pubout -out pub_key.pem3072 is the key length in bits. Adjust this value to meet your security standards.Basic edition: SM2 keys
Before generating SM2 keys, verify that your OpenSSL version supports SM2:
openssl ecparam -list_curvesIf SM2 appears in the output, proceed. If not, upgrade OpenSSL to V1.1.1 or later.
Run the following commands to generate an SM2 key pair:
# Generate an SM2 private key in PKCS#1 format
openssl ecparam -out ec_param.pem -name SM2 -param_enc explicit -genkey
# Convert the private key to PKCS#8 format
openssl pkcs8 -topk8 -inform PEM -in ec_param.pem -outform pem -nocrypt -out pri_key_pkcs8.pem
# Derive the SM2 public key
openssl ec -in ec_param.pem -pubout -out pub_key.pemTwo files are produced: pub_key.pem (public key) and pri_key_pkcs8.pem (private key).
Initialize keys in your application
Performed by: both the issuer and the subject, independently
Load and register the key pair in your application code. Do this once during initialization.
PEM files generated by OpenSSL contain newlines. Some editors display these differently, which can cause newlines to be lost if you copy content manually. Read the PEM file programmatically to avoid this issue.
// Read the private and public keys from files
String userPrkPemString = readPemFile("path/to/pri_key_pkcs8.pem");
String userPukPemString = readPemFile("path/to/pub_key.pem");
// Register the keys — required only once during initialization
KeyManager km = sdk.getKeyManager();
km.registerCertificate(userPrkPemString, userPukPemString);Step 2: Issue a BCL
Define the BCL content
Performed by: the issuer
Construct the BCL JSON body. The following example shows a cross-user authorization (scenario: joint queries). For high-risk self-authorization, see Scenario-specific BCL configuration.
String bclBodyJsonString = """
{
"version": 1,
"serial_num": "a121bac0-5cb3-4463-a2b2-1155ff29f4c8",
"issuer_pukid": "p81x+WqYb7BR0yP0LK0qiEaxgLDqwuIjfJhgC0mMJcE=",
"subject_pukid": "qIPPfgTJEEG/9WkjP0E5LLAijZ14h/Qgb2EfmBZCWSo=",
"validity": {
"not_before": "20240820111111+0800",
"not_after": "20250820111111+0800"
},
"policies": {
"issuer_dek_group": [
{
"min": 1,
"max": 100000,
"groupid": "5bc60759-5b05-45ec-afc1-ffca1229e554"
}
],
"result_dek": "SUBJECT",
"subject_dek_group": [
{
"min": 1,
"max": 100000,
"groupid": "53413af9-f90a-48a9-93b6-49847861b823"
}
],
"operation": ["*"],
"preproc": "NULL",
"postproc": "NULL"
}
}
""";Parameter reference
| Parameter | Example value | Description |
|---|---|---|
version | 1 | Version number. Set to 1. |
serial_num | "a121bac0-5cb3-4463-a2b2-1155ff29f4c8" | A unique UUID. Generate one using any UUID tool. |
issuer_pukid | "p81x+WqYb7BR0yP0LK0qiEaxgLDqwuIjfJhgC0mMJcE=" | Public key digest of the issuer. Run the command below to compute it. |
subject_pukid | "qIPPfgTJEEG/9WkjP0E5LLAijZ14h/Qgb2EfmBZCWSo=" | Public key digest of the subject. Computed the same way as issuer_pukid. |
validity.not_before | "20240820111111+0800" | Start of the validity period, in GeneralizedTime format. |
validity.not_after | "20250820111111+0800" | End of the validity period, in GeneralizedTime format. |
policies.issuer_dek_group | [{"min": 1, "max": 100000, "groupid": "5bc60759..."}] | DEK group that the issuer authorizes for access. DEK IDs within the group must increase monotonically. Run the SQL query below to get the groupid. |
policies.subject_dek_group | [{"min": 1, "max": 100000, "groupid": "53413af9..."}] | DEK group that the subject is allowed to use. Same structure as issuer_dek_group. Run the SQL query below to get the groupid. |
policies.result_dek | "SUBJECT" | DEK used to encrypt the computation result. Options: "SUBJECT", "ISSUER", or a specific DEK ID (without quotes). |
policies.operation | ["*"] | Allowed operations: "encrypt", "decrypt", "cmp", or "*" (all). |
policies.preproc | "NULL" | Pre-processing operation before computation. Set to NULL to skip. |
policies.postproc | "NULL" | Post-processing operation after computation. Set to NULL to skip. |
Compute `issuer_pukid` and `subject_pukid`
Run the command that matches your instance edition:
Hardware-enhanced edition:
openssl sha256 -binary pub_key.pem | openssl base64Basic edition:
openssl sm3 -binary pub_key.pem | openssl base64
Find the `groupid` for a DEK
SELECT encdb_get_cc_entry_by_name(
encdb.keyname_generate('<user_name>', '<database_name>', '<schema_name>', '<table_name>', '<column_name>')
);Replace the placeholders with the actual username, database name, schema name, table name, and column name.
To authorize access to multiple columns, add all the relevant DEKs in a single BCL under issuer_dek_group and subject_dek_group.
Each BCL must have a unique serial_num. If multiple BCLs share the same serial number, only the last one is retained, which may cause computation errors.
Sign and submit the BCL
Performed by: the issuer, then the subject
Both parties must call issueBCL() to sign the BCL — the issuer first, then the subject.
// Issuer signs the BCL (isIssuer = true)
boolean isIssuer = true;
bclBodyJsonString = km.issueBCL(bclBodyJsonString, userPukPemString, userPrkPemString, isIssuer);
// Subject signs the BCL (isIssuer = false)
isIssuer = false;
bclBodyJsonString = km.issueBCL(bclBodyJsonString, userPukPemString, userPrkPemString, isIssuer);After both parties sign, the subject can run joint queries against the issuer's authorized columns.
(Optional) Step 3: Revoke the authorization
Performed by: the issuer
To revoke a previously issued BCL, construct a behavior revocation list (BRL) and submit it.
Define the revocation content
String brlBodyJsonString = """
{
"version": 1,
"pukid": "dYJ3Wfj/n0eZbuqgQjv8bnBdPXGyWGOlxE/uMy16NXo=",
"this_update": "20220819111128+0800",
"next_update": "20220919111128+0800",
"revoked": [
{
"revocation_date": "20220819111128+0800",
"serial_num": "a121bac0-5cb3-4463-a2b2-1155ff29f4c8"
}
]
}
""";| Parameter | Example value | Description |
|---|---|---|
version | 1 | Version number. Set to 1. |
pukid | "dYJ3Wfj/..." | Public key digest of the issuer — same value as used in the original BCL. |
this_update | "20220819111128+0800" | Time when this revocation list is issued, in GeneralizedTime format. |
next_update | "20220919111128+0800" | Next scheduled update time for the revocation list, in GeneralizedTime format. |
revoked.revocation_date | "20220819111128+0800" | Time when the authorization is revoked, in GeneralizedTime format. |
revoked.serial_num | "a121bac0-5cb3-4463-a2b2-1155ff29f4c8" | Serial number of the BCL to revoke — must match the serial_num in the original BCL. |
Submit the revocation
brlBodyJsonString = km.revokeBCL(brlBodyJsonString, userPukPemString, userPrkPemString);Scenario-specific BCL configuration
The two scenarios differ only in how issuer_pukid, subject_pukid, issuer_dek_group, and subject_dek_group are set:
| Parameter | Joint queries (different users) | DBA high-risk ops (self-BCL) |
|---|---|---|
issuer_pukid | Issuer's public key digest | Same as subject_pukid |
subject_pukid | Subject's public key digest | Same as issuer_pukid |
issuer_dek_group | Issuer's DEK group | Same as subject_dek_group |
subject_dek_group | Subject's DEK group | Same as issuer_dek_group |
Scenario 1: DBA high-risk operations (data conversion between plaintext and ciphertext)
By default, no user — including DBAs — can convert data between plaintext and ciphertext on an always-confidential database. This operation is classified as a high-risk operation. To enable it, issue a self-BCL where the issuer and subject use the same account and the same key pair.
Set issuer_pukid equal to subject_pukid, and issuer_dek_group equal to subject_dek_group. Then sign the BCL using the same key pair for both the issuer and subject calls:
// Both issuer and subject use the same key pair
bclBodyJsonString = km.issueBCL(bclBodyJsonString, userPukPemString, userPrkPemString, true);
bclBodyJsonString = km.issueBCL(bclBodyJsonString, userPukPemString, userPrkPemString, false);For more information about data conversion, see Convert data between plaintext and ciphertext.
Full example: authorize and convert a plaintext column to ciphertext
Scenario 2: Joint queries of multi-user data
A common use case: a data platform grants an insurance company access to specific encrypted columns, enabling joint analysis without exposing the underlying data. For more information, see Multi-party data integration and computing.
Full example: cross-user BCL and joint query