All Products
Search
Document Center

ApsaraDB RDS:Manage authorization

Last Updated:Mar 28, 2026

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:

Choose your scenario

Identify your use case before following the steps below:

ScenarioDescriptionIssuer and subject
Joint queries of multi-user dataA party (subject) needs to run queries that join the issuer's encrypted columns with its own dataDifferent accounts
DBA high-risk operationsA DBA needs to convert plaintext data to ciphertext (or vice versa) on an always-confidential databaseSame 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:

EditionInstance typeAlgorithm
Hardware-enhanced editionIntel Software Guard Extensions (SGX)-based security-enhancedRSA
Basic editionNon-Intel SGX-based security-enhancedSM2

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.pem
3072 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_curves

If 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.pem

Two 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

ParameterExample valueDescription
version1Version 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 base64

  • Basic 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.

Important

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"
        }
      ]
    }
    """;
ParameterExample valueDescription
version1Version 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:

ParameterJoint queries (different users)DBA high-risk ops (self-BCL)
issuer_pukidIssuer's public key digestSame as subject_pukid
subject_pukidSubject's public key digestSame as issuer_pukid
issuer_dek_groupIssuer's DEK groupSame as subject_dek_group
subject_dek_groupSubject's DEK groupSame 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

High-risk operations: data conversion between plaintext and ciphertext

package org.example;

import com.alibaba.encdb.EncdbSDK;
import com.alibaba.encdb.crypto.EncdbSDKBuilder;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class Main {
    public static void main(String[] args) {
        Main main = new Main();
        main.testSelfBcl();
    }

    private String readPemFile(String filename) throws IOException {
        return new String(Files.readAllBytes(Paths.get(filename)));
    }

    public void testSelfBcl() {
        // Replace with your RDS instance details
        String hostname = "pgm-****.pg.rds.aliyuncs.com";
        String port = "5432";
        String dbname = "testdb";
        String username = "testdbuser";
        String password = "****";
        String mek = "00112233445566778899aabbccddeeff";  // Sample value. Use a more complex MEK in production.
        String prikeyFilename = "D:\\test\\Issuer\\pri_key_pkcs8.pem"; // Replace with your actual key file path
        String pubkeyFilename = "D:\\test\\Issuer\\pub_key.pem";       // Replace with your actual key file path

        try (Connection conn = DriverManager.getConnection(
                "jdbc:postgresql://" + hostname + ":" + port + "/" + dbname + "?binaryTransfer=true",
                username, password)) {

            System.out.println("connect success");
            String tblname = "tbl_alter_" + conn.getMetaData().getUserName();

            // Create a table and insert plaintext data
            conn.createStatement().executeUpdate("DROP TABLE IF EXISTS " + tblname);
            conn.createStatement().executeUpdate("CREATE TABLE " + tblname + " (id int)");
            try (PreparedStatement stmt = conn.prepareStatement("INSERT INTO " + tblname + " VALUES(?)")) {
                stmt.setInt(1, 231);
                stmt.executeUpdate();
            }

            // Optional: clear historical BCL records
            conn.createStatement().executeUpdate("DELETE FROM encdb.encdb_internal_bcl_table");
            conn.createStatement().executeUpdate("DELETE FROM encdb.encdb_internal_brl_map_table");
            conn.createStatement().executeUpdate("DELETE FROM encdb.encdb_internal_brl_table");

            // Initialize the SDK and distribute the MEK
            EncdbSDK sdk = EncdbSDKBuilder.newInstance()
                    .setDbConnection(conn)
                    .setMek(mek)
                    .build();
            System.out.println("init success");

            // Load and register the key pair
            String userPrkPemString = readPemFile(prikeyFilename);
            String userPukPemString = readPemFile(pubkeyFilename);
            sdk.getKeyManager().registerCertificate(userPrkPemString, userPukPemString);
            System.out.println("register certificate success");

            // Build the ALTER statement to convert the plaintext column to ciphertext
            String keyname = "'|" + username + "|" + dbname + "|'";
            String alterStmString = "ALTER TABLE " + tblname +
                    " ALTER COLUMN id SET DATA TYPE enc_int4 USING encdb.enc_int4_encrypt(id, " + keyname + ")";

            // Verify that conversion fails before authorization
            try {
                conn.createStatement().executeUpdate(alterStmString);
            } catch (SQLException exception) {
                if (exception.getMessage().contains("fa030000") || exception.getMessage().contains("fa020000")) {
                    System.out.println("alter column to enc_int4 failed without authorized");
                } else {
                    throw exception;
                }
            }

            // Issue a self-BCL (issuer and subject are the same account)
            String bclBodyJsonString = """
                    {
                        "version": 1,
                        "serial_num": "fdbed057-7fe5-4c31-97ae-afdd615732fe",
                        "issuer_pukid": "p81x+WqYb7BR0yP0LK0qiEaxgLDqwuIjfJhgC0mMJcE=",
                        "subject_pukid": "p81x+WqYb7BR0yP0LK0qiEaxgLDqwuIjfJhgC0mMJcE=",
                        "validity": {
                            "not_before": "20240920111111+0800",
                            "not_after": "20250920111111+0800"
                        },
                        "policies": {
                            "issuer_dek_group": [
                                {"min": 1, "max": 100000, "groupid": "93165651-609f-47db-91aa-c3a2ab7084c4"}
                            ],
                            "result_dek": "SUBJECT",
                            "subject_dek_group": [
                                {"min": 1, "max": 100000, "groupid": "93165651-609f-47db-91aa-c3a2ab7084c4"}
                            ],
                            "operation": ["*"],
                            "preproc": "NULL",
                            "postproc": "NULL"
                        }
                    }
                    """;

            // Sign the BCL as both issuer and subject using the same key pair
            bclBodyJsonString = sdk.getKeyManager().issueBCL(bclBodyJsonString, userPukPemString, userPrkPemString, true);
            bclBodyJsonString = sdk.getKeyManager().issueBCL(bclBodyJsonString, userPukPemString, userPrkPemString, false);
            System.out.println("issue bcl success");

            // Conversion succeeds after authorization
            conn.createStatement().executeUpdate(alterStmString);
            System.out.println("alter column to enc_int4 success after authorized");

        } catch (SQLException | IOException e) {
            e.printStackTrace();
        }
    }
}

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

Joint queries of multi-user data

package org.example;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import com.alibaba.encdb.Cryptor;
import com.alibaba.encdb.EncdbSDK;
import com.alibaba.encdb.common.Constants.EncAlgo;
import com.alibaba.encdb.crypto.EncdbSDKBuilder;

public class BlcTest {
    private String readPemFile(String filename) throws IOException {
        return new String(Files.readAllBytes(Paths.get(filename)));
    }

    private void prepareData(Connection conn, EncdbSDK sdk) throws SQLException {
        String tblname = "tbl_" + conn.getMetaData().getUserName();
        conn.createStatement().executeUpdate("DROP TABLE IF EXISTS " + tblname);
        conn.createStatement().executeUpdate("CREATE TABLE " + tblname
                + " (id INTEGER, name VARCHAR, price enc_int4, miles enc_float4, secret enc_text, PRIMARY KEY (id))");

        PreparedStatement stmt = conn.prepareStatement(
                "INSERT INTO " + tblname + " (id, name, price, miles, secret) VALUES(?,?,?,?,?)");
        stmt.setInt(1, 1);
        stmt.setString(2, "name");
        Cryptor cryptor = sdk.getCryptor();
        stmt.setBytes(3, cryptor.encrypt(tblname, "price", 1234));
        stmt.setBytes(4, cryptor.encrypt(tblname, "miles", 12.34f));
        stmt.setBytes(5, cryptor.encrypt(tblname, "secret", "aliyun"));
        stmt.execute();
    }

    private void validateResult(ResultSet rs, EncdbSDK sdk) throws SQLException {
        Cryptor cryptor = sdk.getCryptor();
        while (rs.next()) {
            Assertions.assertEquals(1, rs.getInt(1));
            Assertions.assertEquals("name", rs.getString(2));
            Assertions.assertEquals(1234, cryptor.decryptInt(rs.getBytes(3)));
            Assertions.assertEquals(12.34f, cryptor.decryptFloat(rs.getBytes(4)), 0.000001f);
            Assertions.assertEquals("aliyun", cryptor.decryptString(rs.getBytes(5)));
        }
    }

    private void simpleQuery(Connection conn, EncdbSDK sdk) throws SQLException {
        String tblname = "tbl_" + conn.getMetaData().getUserName();
        PreparedStatement stmt = conn.prepareStatement("SELECT * FROM " + tblname + " WHERE price > ?");
        stmt.setBytes(1, sdk.getCryptor().encrypt(tblname, "price", 100));
        validateResult(stmt.executeQuery(), sdk);
    }

    private void subjectJoinQuery(Connection subjectConn, EncdbSDK subjectSdk, Connection issuerConn)
            throws SQLException {
        String issuerTblname = "tbl_" + issuerConn.getMetaData().getUserName();
        String subjectTblname = "tbl_" + subjectConn.getMetaData().getUserName();

        // Subject runs a JOIN query against the issuer's encrypted table
        String sqlCmd = "SELECT subject.id, subject.name, subject.price, subject.miles, subject.secret "
                + "FROM " + issuerTblname + " issuer, " + subjectTblname + " subject "
                + "WHERE issuer.price = subject.price AND subject.price > ?";

        PreparedStatement stmt = subjectConn.prepareStatement(sqlCmd);
        stmt.setBytes(1, subjectSdk.getCryptor().encrypt(subjectTblname, "price", 100));
        validateResult(stmt.executeQuery(), subjectSdk);
    }

    @Test
    public void testBcl() throws SQLException, IOException {
        // Replace with your RDS instance details
        String hostname = "pgm-****.pg.rds.aliyuncs.com";
        String port = "5432";
        String dbname = "testdb";

        Connection issuerConn;
        EncdbSDK issuerSdk;
        String issuerPriKeyPemString;
        String issuerPubKeyPemString;

        Connection subjectConn;
        EncdbSDK subjectSdk;
        String subjectPriKeyPemString;
        String subjectPubKeyPemString;

        // ── Initialize the issuer ──────────────────────────────────────────────
        {
            String username = "testdbuser";
            String password = "****";
            String mek = "00112233445566778899aabbccddeeff"; // Sample value. Use a more complex MEK in production.
            String dbUrl = String.format("jdbc:postgresql://%s:%s/%s?binaryTransfer=true", hostname, port, dbname);
            issuerConn = DriverManager.getConnection(dbUrl, username, password);
            issuerSdk = EncdbSDKBuilder.newInstance()
                    .setDbConnection(issuerConn)
                    .setMek(mek)
                    .setEncAlgo(EncAlgo.SM4_128_CBC)
                    .build();
            System.out.println("issuer init success");
        }

        // ── Initialize the subject ─────────────────────────────────────────────
        {
            String username = "testdbuser02";
            String password = "****";
            String mek = "ffeeddccbbaa99887766554433221100"; // Sample value. Use a more complex MEK in production.
            String dbUrl = String.format("jdbc:postgresql://%s:%s/%s?binaryTransfer=true", hostname, port, dbname);
            subjectConn = DriverManager.getConnection(dbUrl, username, password);
            subjectSdk = EncdbSDKBuilder.newInstance()
                    .setDbConnection(subjectConn)
                    .setMek(mek)
                    .setEncAlgo(EncAlgo.SM4_128_CBC)
                    .build();
            System.out.println("subject init success");
        }

        // ── Prepare test data ──────────────────────────────────────────────────
        {
            // Issuer writes and verifies own data
            prepareData(issuerConn, issuerSdk);
            Assertions.assertDoesNotThrow(() -> simpleQuery(issuerConn, issuerSdk));
            System.out.println("issuer query own data success");

            // Subject writes and verifies own data
            prepareData(subjectConn, subjectSdk);
            Assertions.assertDoesNotThrow(() -> simpleQuery(subjectConn, subjectSdk));
            System.out.println("subject query own data success");

            // Grant the subject table-level SELECT permission (data remains encrypted without a BCL)
            String tblname = "tbl_" + issuerConn.getMetaData().getUserName();
            issuerConn.createStatement().execute(
                    "GRANT SELECT ON TABLE " + tblname + " TO " + subjectConn.getMetaData().getUserName());

            // Clear historical BCL records
            subjectConn.createStatement().execute("DELETE FROM encdb.encdb_internal_bcl_table");
            subjectConn.createStatement().execute("DELETE FROM encdb.encdb_internal_brl_map_table");
            subjectConn.createStatement().execute("DELETE FROM encdb.encdb_internal_brl_table");
        }

        // ── Register key pairs ─────────────────────────────────────────────────
        {
            // Issuer registers keys
            issuerPriKeyPemString = readPemFile("D:\\test\\Issuer\\pri_key_pkcs8.pem"); // Replace with your actual path
            issuerPubKeyPemString = readPemFile("D:\\test\\Issuer\\pub_key.pem");       // Replace with your actual path
            issuerSdk.getKeyManager().registerCertificate(issuerPriKeyPemString, issuerPubKeyPemString);
            System.out.println("issuer register certificate success");

            // Subject registers keys
            subjectPriKeyPemString = readPemFile("D:\\test\\Subject\\pri_key_pkcs8.pem"); // Replace with your actual path
            subjectPubKeyPemString = readPemFile("D:\\test\\Subject\\pub_key.pem");        // Replace with your actual path
            subjectSdk.getKeyManager().registerCertificate(subjectPriKeyPemString, subjectPubKeyPemString);
            System.out.println("subject register certificate success");
        }

        // ── Verify access is denied without a BCL ─────────────────────────────
        {
            final Connection tmpSubjectConn = subjectConn;
            final EncdbSDK tmpSubjectSdk = subjectSdk;
            final Connection tmpIssuerConn = issuerConn;
            SQLException exception = Assertions.assertThrows(SQLException.class,
                    () -> subjectJoinQuery(tmpSubjectConn, tmpSubjectSdk, tmpIssuerConn));
            Assertions.assertEquals("ERROR: encdb_get_hash_from_bytea: errno:fa030000", exception.getMessage());
            System.out.println("subject query issuer data failed without authorized");
        }

        // ── Issue the cross-user BCL ──────────────────────────────────────────
        {
            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": "e4a92b05-f64d-4665-aadd-cd1336d0c0cc"},
                                {"min": 1, "max": 100000, "groupid": "1010b43b-50da-4473-81ac-ce84657eb4f9"},
                                {"min": 1, "max": 100000, "groupid": "b99e70cf-c6b0-444f-a404-81a721e38734"}
                            ],
                            "result_dek": "SUBJECT",
                            "subject_dek_group": [
                                {"min": 1, "max": 100000, "groupid": "d1cbe5a6-49f0-42e0-ba07-572e2a5e2f5f"},
                                {"min": 1, "max": 100000, "groupid": "9c772fa3-4712-4034-9447-0f1e9e18fbeb"},
                                {"min": 1, "max": 100000, "groupid": "e2541c31-891e-4f8d-8389-09c5631f1e66"}
                            ],
                            "operation": ["*"],
                            "preproc": "NULL",
                            "postproc": "NULL"
                        }
                    }
                    """;

            // Issuer signs first, then subject
            bclBodyJsonString = issuerSdk.getKeyManager().issueBCL(
                    bclBodyJsonString, issuerPubKeyPemString, issuerPriKeyPemString, true);
            System.out.println("issuer issue bcl success");

            bclBodyJsonString = subjectSdk.getKeyManager().issueBCL(
                    bclBodyJsonString, subjectPubKeyPemString, subjectPriKeyPemString, false);
            System.out.println("subject issue bcl success");
        }

        // ── Verify access succeeds after authorization ─────────────────────────
        {
            final Connection tmpSubjectConn = subjectConn;
            final EncdbSDK tmpSubjectSdk = subjectSdk;
            final Connection tmpIssuerConn = issuerConn;
            Assertions.assertDoesNotThrow(
                    () -> subjectJoinQuery(tmpSubjectConn, tmpSubjectSdk, tmpIssuerConn));
            System.out.println("subject query issuer data success after authorized");
        }
    }
}

FAQ

  • What do I do if I get `ERROR: permission denied for table <table_name>`?

    This error means the subject doesn't have SQL-level access to the table. The table owner or administrator must run:

    GRANT SELECT ON <table_name> TO <username>;

    Note that granting SQL table permissions alone is not enough — a BCL is still required to access the encrypted data.

  • How do I verify that authorization succeeded?

    After issuing the BCL, run a query as the subject against the issuer's data. If the query returns results, authorization is working. If it fails with error code fa030000, no authorization records were found — re-check the BCL content and reissue it. If it fails with fa020000, access is denied — the BCL content may be incorrect.

    For high-risk operation authorization, perform the high-risk operation directly. If it succeeds, authorization is in place.

  • How do I authorize access to multiple columns?

    Add all the relevant DEKs in a single BCL under issuer_dek_group and subject_dek_group. Bundling all columns in one BCL avoids serial number conflicts.

    If you must use separate BCLs for individual columns, assign a unique serial_num to each. BCLs with the same serial number overwrite each other, which can cause computation errors on the affected data.

What's next