All Products
Search
Document Center

ApsaraDB RDS:Manage authorization

Last Updated:Nov 10, 2024

If you enable the always-confidential database feature for your ApsaraDB RDS for PostgreSQL instance, the ciphertext data of different users on the RDS instance is automatically isolated. This topic describes how to manage permissions to implement integrated computing of multi-user data and how to authorize database administrators (DBAs) to perform high-risk data operations.

Feature description

In an always-confidential database, user data is encrypted by using the data encryption keys (DEKs) of the users. The ciphertext data of different users is automatically isolated. If a joint query involves data from multiple users, the data owner must grant the permissions to the subject that wants to query data to access the ciphertext data of the data owner. This process is called multi-user authorization management.

In an always-confidential database, you can use a behavior control list (BCL) to grant access permissions on multi-user data. After a BCL is issued, the subject can perform joint queries without the need to understand the data content of the issuer that owns the data. The issuer does not need to worry about data leaks and can issue or revoke the BCL to limit the behavior of the subject. This promptly and effectively prevents the data from being used unexpectedly.

If you want to authorize a DBA to perform high-risk operations on an always-confidential database, such as creating a custom key or converting data between plaintext and ciphertext, you can issue a BCL. This way, the DBA can perform relevant operations by using an account. In this case, the subject and the issuer use the same account.

Prerequisites

Step 1: Initialize the public and private keys

    1. Install OpenSSL.

      In this topic, OpenSSL, an open source cryptographic toolkit, is used to obtain the public and private keys. Linux operating systems are provided with OpenSSL. If you use a Linux operating system, you do not need to install OpenSSL. If you use a Windows operating system, you must download the OpenSSL software package and install OpenSSL. For more information, see Win32/Win64 OpenSSL.

      Note

      If you use an RDS instance for which the always-confidential database (basic edition) feature is enable, you must use the SM2 algorithm to generate public and private keys. You can run the openssl ecparam -list_curves command to check whether the SM2 algorithm is supported by the current OpenSSL version. RDS instances for which the always-confidential database (basic edition) feature is enabled are the RDS instances that use non-Intel SGX-based security-enhanced instance types. If SM2 is included in the command output, the SM2 algorithm is supported. If SM2 is not included in the command output, you must upgrade OpenSSL to a version that supports the SM2 algorithm. You can upgrade OpenSSL to V1.1.1 or later.

    2. Obtain the public and private keys of the subject and the issuer based on the instance type of the RDS instance for which the always-confidential database feature is enabled.

      Two key files are generated. The pub_key.pem file is the public key file. The pri_key_pkcs8.pem file is the private key file.

      Hardware-enhanced edition of the always-confidential database feature (Intel SGX-based)

      RDS instances for which the always-confidential database (hardware-enhanced edition) feature is enabled are the RDS instances that use Intel SGX-based security-enhanced instance types. For more information, see Instance types for primary ApsaraDB RDS for PostgreSQL instances.

      You can perform the following operations to obtain the public and private keys:

      1. Use OpenSSL to generate a Rivest-Shamir-Adleman (RSA) private key.

        openssl genpkey -algorithm RSA -out pri_key_pkcs8.pem -pkeyopt rsa_keygen_bits:3072
        Note

        3072: the key length. You can change the key length to a different value that complies with the security standards based on your business requirements.

      2. Use OpenSSL to generate an RSA public key.

        openssl rsa -in pri_key_pkcs8.pem -pubout -out pub_key.pem

      Basic edition of the always-confidential database feature

      RDS instances for which the always-confidential database (basic edition) feature is enabled are the RDS instances that use non-Intel SGX-based security-enhanced instance types. For more information, see Instance types for primary ApsaraDB RDS for PostgreSQL instances.

      You can perform the following operations to obtain the public and private keys:

      1. Use OpenSSL to generate an SM2 private key.

        # Generate a private key in the Public-Key Cryptography Standards (PKCS)#1 format.
        openssl ecparam -out ec_param.pem -name SM2 -param_enc explicit -genkey
        # Convert the private key into the PKCS#8 format.
        openssl pkcs8 -topk8 -inform PEM -in ec_param.pem -outform pem -nocrypt -out pri_key_pkcs8.pem
      2. Use OpenSSL to generate an SM2 public key.

        openssl ec -in ec_param.pem -pubout -out pub_key.pem
  1. Separately initialize the public and private keys of the subject and the issuer.

    Initialize the public and private keys of the subject and the issuer in their business code.

    Note

    Newlines are automatically added in PEM files that are generated by using OpenSSL. In specific editors, the display of the newlines may be optimized. As a result, when you manually copy the content of a PEM file, newlines are omitted. We recommend that you use program code to read the content of the PEM file to ensure data accuracy.

    //The private keys of the subject and the issuer.
    String userPrkPemString = readPemFile("path/to/pri_key_pkcs8.pem");
    //The public keys of the subject and the issuer.
    String userPukPemString = readPemFile("path/to/pub_key.pem");
    //Key initialization. The keys need to be initialized only once.
    KeyManager km = sdk.getKeyManager();
    km.registerCertificate(userPrkPemString, userPukPemString);

Step 2: Grant access permissions on multi-user data

  1. Define the authorization content.

    String bclBodyJsonString = """{
      "version": 1,
      "serial_num": "a121bac0-5cb3-4463-a2b2-1155ff29f4c8",
      "issuer_pukid": "p81x+WqYb7BR0yP0LK0qiEaxgLDqwuIjfJhgC0mMJcE=",
      "subject_pukid": "qIPPfgTJEEG/9WkjP0E5LLAijZ14h/Qgb2EfmBZCWSo=",
      "validity": {
        "not_after": "20250820111111+0800",
        "not_before": "20240820111111+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": [
          "*"
        ],
        "postproc": "NULL",
        "preproc": "NULL"
      }
    }
      """;

    Parameter description

    Parameter

    Example value

    Description

    version

    1

    The version number. Set the value to 1.

    serial_num

    "a121bac0-5cb3-4463-a2b2-1155ff29f4c8"

    A unique, random serial number in the UUID format.

    Note

    You can use the UUID generation tool to generate serial numbers.

    issuer_pukid

    "p81x+WqYb7BR0yP0LK0qiEaxgLDqwuIjfJhgC0mMJcE="

    The public key digest of the issuer. You can run the following code to obtain the public key digest based on the edition of the always-confidential database feature and the generated public key file pub_key.pem:

    • Hardware-enhanced edition (Intel SGX-based)

      openssl sha256 -binary pub_key.pem | openssl base64

    • Basic edition

      openssl sm3 -binary pub_key.pem | openssl base64

    subject_pukid

    "qIPPfgTJEEG/9WkjP0E5LLAijZ14h/Qgb2EfmBZCWSo="

    The public key digest of the subject. You can configure this parameter the same way you configure the issuer_pukid parameter.

    validity

    { "not_before": "20240820111111+0800", "not_after": "20250820111111+0800"}

    The validity period of the authorization. The value must be in the GeneralizedTime time format.

    • "not_before": the beginning of the validity period.

    • "not_after": the end of the validity period.

    policies

    issuer_dek_group

    [ { "min": 1, "max": 100000, "groupid": "5bc60759-5b05-45ec-afc1-ffca1229e554" }]

    The group of DEKs that are allowed by the issuer.

    • "groupid": the ID of the DEK group

    • "min": the smallest DEK ID in the DEK group

    • "max": the largest DEK ID in the DEK group

    Note

    In a DEK group, the values of the DEK IDs must monotonically increase.

    You can run the following code to obtain the value of the groupid parameter:

    SELECT encdb_get_cc_entry_by_name(encdb.keyname_generate('<user_name>', '<database_name>', '<schema_name>', '<table_name>', '<column_name>'));

    Note
    • <user_name>: the username of the issuer

    • <table_name>: the name of the table that the issuer allows the subject to access

    • <column_name>: the name of the column that the issuer allows the subject to access

    subject_dek_group

    [ { "min": 1, "max": 100000, "groupid": "53413af9-f90a-48a9-93b6-49847861b823" }]

    The DEK group that the subject is allowed to access.

    • "groupid": the ID of the DEK group

    • "min": the smallest DEK ID in the DEK group

    • "max": the largest DEK ID in the DEK group

    Note

    In a DEK group, the values of the DEK IDs must monotonically increase.

    You can run the following code to obtain the value of the groupid parameter:

    SELECT encdb_get_cc_entry_by_name(encdb.keyname_generate('<user_name>', '<database_name>', '<schema_name>', '<table_name>', '<column_name>'));

    Note
    • <user_name>: the username of the subject

    • <table_name>: the name of the table that the subject is allowed to access.

    • <column_name>: the name of the column that the subject is allowed to access.

    result_dek

    "SUBJECT"

    The DEK that is used to encrypt the computation result.

    • "SUBJECT": uses the DEK of the subject in the current computation.

    • "ISSUER": uses the DEK of the issuer in the current computation.

    • DEK ID: uses the DEK with the specified DEK ID.

      Important

      If you want to use a specific DEK, set this parameter to the ID of the DEK. You can directly specify the DEK ID without the need to enclose the DEK ID in double quotation marks ("").

    operation

    [ "*"]

    The allowed computation operations.

    • "encrypt": encryption

    • "decrypt": decryption

    • "cmp": comparison

    • "*": all operations

    postproc

    "NULL"

    The pre-processing operation before the computation. Set the value to NULL, which specifies that no pre-processing operations are required.

    preproc

    "NULL"

    The post-processing operation after the computation. Set the value to NULL, which specifies that no post-processing operations are required.

  2. Grant access permissions on multi-user data

    • The issuer uses its public and private keys to issue a BCL to authorize the subject to access the data in the permitted column.

      boolean isIssuer = true;
      bclBodyJsonString = km.issueBCL(bclBodyJsonString, userPukPemString, userPrkPemString, isIssuer);
    • The subject uses its public and private keys to issue a BCL and access the permitted data.

      boolean isIssuer = false;
      bclBodyJsonString = km.issueBCL(bclBodyJsonString, userPukPemString, userPrkPemString, isIssuer);

(Optional) Step 3: Revoke the authorization

If the issuer wants to revoke the authorization, perform the following operations:

  1. Define the authorization that is to be revoked.

    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 description

    Parameter

    Example value

    Description

    version

    1

    The version number. Set the value to 1.

    pukid

    "dYJ3Wfj/n0eZbuqgQjv8bnBdPXGyWGOlxE/uMy16NXo="

    The public key digest of the issuer. Set the value to the public key digest of the issuer in the BCL authorization application. You can configure this parameter the same way you configure the parameter described in Define the authorization content.

    this_update

    "20220819111128+0800"

    The point in time at which the revocation list is updated. The value must be in the GeneralizedTime time format.

    next_update

    "20220919111128+0800"

    The next update time of the revocation list. The value must be in the GeneralizedTime time format.

    revoked

    revocation_date

    "20220819111128+0800"

    The point in time at which the authorization is revoked. The value must be in the GeneralizedTime time format.

    serial_num

    "a121bac0-5cb3-4463-a2b2-1155ff29f4c8"

    The serial number in the UUID format. You must set this parameter to the serial number that is specified in the BCL authorization application.

  2. The issuer approves and issues the revocation.

    brlBodyJsonString = km.revokeBCL(brlBodyJsonString, userPukPemString, userPrkPemString);

Scenarios

Scenario 1: High-risk operations: data conversion between plaintext and ciphertext

If you want to enable the always-confidential database feature for an existing plaintext database without the need to migrate data, you can directly convert the plaintext data on the database to ciphertext data. For more information, see Convert data between plaintext and ciphertext. By default, users including DBAs cannot perform data conversion between plaintext and ciphertext on always-confidential databases because this operation is considered as a high-risk operation.

If you understand and acknowledge data security risks, you can issue a BCL to authorize the required user to directly convert data between plaintext and ciphertext on an always-confidential database. In this case, the subject and the issuer use the same account.

  • When you define the content of a BCL, you must set the issuer_pukid parameter to the same value as the subject_pukid parameter and the issuer_dek_group parameter to the same value as the subject_dek_group parameter. For more information, see Define the authorization content.

    Define the authorization content

    String bclBodyJsonString = """{
      "version": 1,
      "serial_num": "fdbed057-7fe5-4c31-97ae-afdd615732fe",
      "issuer_pukid": "p81x+WqYb7BR0yP0LK0qiEaxgLDqwuIjfJhgC0mMJcE=",
      "subject_pukid": "p81x+WqYb7BR0yP0LK0qiEaxgLDqwuIjfJhgC0mMJcE=",
      "validity": {
        "not_after": "20250920111111+0800",
        "not_before": "20240920111111+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": [
          "*"
        ],
        "postproc": "NULL",
        "preproc": "NULL"
      }
    }
     """;
  • When you initialize the public and private keys, the subject and the issuer use the same public and private keys. The public key is specified by the userPukPemString parameter, and the private key is specified by the userPrkPemString parameter. The values of the userPukPemString and userPrkPemString parameters for the issuer are the same as the values of those for the subject. For more information, see Initialize the public and private keys.

    Issue a BCL

    // The subject and the issuer use the same public and private keys to issue a BCL.
    bclBodyJsonString = km.issueBCL(bclBodyJsonString, userPukPemString, userPrkPemString, true); 
    bclBodyJsonString = km.issueBCL(bclBodyJsonString, userPukPemString, userPrkPemString, false);

After you issue a BCL, you can perform the required operations by using the authorized account. Sample code:

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 the parameter values with the actual information about the RDS instance.
        String hostname = "pgm-****.pg.rds.aliyuncs.com";
        String port = "5432";
        String dbname = "testdb";
        String username = "testdbuser";
        String password = "****";
        String mek = "00112233445566778899aabbccddeeff";  // This is a sample value. We recommend that you use a more complex master encryption key (MEK).
        String prikeyFilename = "D:\\test\\Issuer\\pri_key_pkcs8.pem"; // This is a sample value. You must replace the value with the path of the key file.
        String pubkeyFilename = "D:\\test\\Issuer\\pub_key.pem"; // This is a sample value. You must replace the value with the path of the key file.
        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.
            conn.createStatement().executeUpdate("DROP TABLE IF EXISTS " + tblname);
            conn.createStatement().executeUpdate("CREATE TABLE " + tblname + " (id int)");
            // Write plaintext data into the table.
            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");
            // Read the public and private key content from the key files.
            String userPrkPemString = readPemFile(prikeyFilename);
            String userPukPemString = readPemFile(pubkeyFilename);
            // Register the public and private keys.
            sdk.getKeyManager().registerCertificate(userPrkPemString, userPukPemString);
            System.out.println("register certificate success");

            // Use the specified DEK to convert the plaintext column into a ciphertext column. For example, you can use encdb.dek_generate to generate a DEK.
            // The default DEK is used. You can use encdb.keyname_generate to generate the required keyname.
            String keyname = "'|" + username + "|" + dbname + "|'";
            String alterStmString = "ALTER TABLE " + tblname + " ALTER COLUMN id SET DATA TYPE enc_int4 USING encdb.enc_int4_encrypt(id, " + keyname + ")";

            // The plaintext change fails before the 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; // Re-throw other exceptions.
                }
            }
            // Perform multi-user authorization.
            String bclBodyJsonString = """
                    {
                        "version": 1,
                        "serial_num": "fdbed057-7fe5-4c31-97ae-afdd615732fe",
                        "issuer_pukid": "p81x+WqYb7BR0yP0LK0qiEaxgLDqwuIjfJhgC0mMJcE=",
                        "subject_pukid": "p81x+WqYb7BR0yP0LK0qiEaxgLDqwuIjfJhgC0mMJcE=",
                        "validity": {
                            "not_after": "20250920111111+0800",
                            "not_before": "20240920111111+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": [
                                "*"
                            ],
                            "postproc": "NULL",
                            "preproc": "NULL"
                        }
                    }
                    """;
            // Issue the authorization.
            {
                bclBodyJsonString = sdk.getKeyManager().issueBCL(bclBodyJsonString, userPukPemString, userPrkPemString, true);
                bclBodyJsonString = sdk.getKeyManager().issueBCL(bclBodyJsonString, userPukPemString, userPrkPemString, false);
                System.out.println("issue bcl success");
            }
            // The plaintext change succeeds after the authorization.
            conn.createStatement().executeUpdate(alterStmString);
            System.out.println("alter column to enc_int4 success after authorized");
        } catch (SQLException |
                 IOException e) {
            e.printStackTrace(); // Print exceptions.
        }
    }
}

Scenario 2: Joint queries of multi-user data

For example, a data platform company obtains the required permissions to use the data platform to collect user data and generate user profiles. Then, the data platform company authorizes a third party, such as an insurance company, to use the data for multi-party data integration and computing. This helps implement joint marketing. For more information, see Multi-party data integration and computing.

The following code provides an example on how to perform joint queries.

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 {
        int id = 1;
        String name = "name";
        int price = 1234;
        float miles = 12.34f;
        String secret = "aliyun";

        String tblname = "tbl_" + conn.getMetaData().getUserName();
        // Create a table.
        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))");

        // Write data.
        PreparedStatement stmt = conn.prepareStatement(
                "INSERT INTO " + tblname + " (id, name, price, miles, secret) VALUES(?,?,?,?,?)");
        stmt.setInt(1, id);
        stmt.setString(2, name);
        // Encrypt and write data.
        Cryptor cryptor = sdk.getCryptor();
        stmt.setBytes(3, cryptor.encrypt(tblname, "price", price));
        stmt.setBytes(4, cryptor.encrypt(tblname, "miles", miles));
        stmt.setBytes(5, cryptor.encrypt(tblname, "secret", secret));
        stmt.execute();
    }

    private void validateResult(ResultSet rs, EncdbSDK sdk) throws SQLException {
        int id = 1;
        String name = "name";
        int price = 1234;
        float miles = 12.34f;
        String secret = "aliyun";

        Cryptor cryptor = sdk.getCryptor();
        while (rs.next()) {
            int idRs = rs.getInt(1);
            Assertions.assertEquals(id, idRs);
            String nameRs = rs.getString(2);
            Assertions.assertEquals(name, nameRs);
            int priceRs = cryptor.decryptInt(rs.getBytes(3));
            Assertions.assertEquals(price, priceRs);
            float milesRs = cryptor.decryptFloat(rs.getBytes(4));
            Assertions.assertEquals(miles, milesRs, 0.000001f);
            String secretRs = cryptor.decryptString(rs.getBytes(5));
            Assertions.assertEquals(secret, secretRs);
        }
    }

    private void simpleQuery(Connection conn, EncdbSDK sdk) throws SQLException {
        String tblname = "tbl_" + conn.getMetaData().getUserName();
        String sqlCmd = "SELECT * FROM " + tblname + " WHERE  price > ?";
        PreparedStatement stmt = conn.prepareStatement(sqlCmd);
        // Encrypt the query content.
        Cryptor cryptor = sdk.getCryptor();
        stmt.setBytes(1, cryptor.encrypt(tblname, "price", 100));
        ResultSet rs = stmt.executeQuery();

        validateResult(rs, sdk);
    }

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

        // Initiate a JOIN query as the subject to query data in the table of the issuer.
        String sqlCmd = "SELECT subject.id as id, subject.name as name, subject.price as price, subject.miles as miles, subject.secret as secret "
                + "FROM " + issuerTblname + " issuer, " + subjectTblname + " subject "
                + "WHERE issuer.price = subject.price and subject.price > ?";

        Connection conn = subjectConn;
        EncdbSDK sdk = subjectSdk;
        PreparedStatement stmt = conn.prepareStatement(sqlCmd);
        // Encrypt the query content.
        Cryptor cryptor = sdk.getCryptor();
        stmt.setBytes(1, cryptor.encrypt(subjectTblname, "price", 100));
        ResultSet rs = stmt.executeQuery();

        validateResult(rs, sdk);
    }

    @Test
    public void testBcl() throws SQLException, IOException {
        // Configure the hostname, port, and dbname parameters based on the information about the RDS instance.
        String hostname = "pgm-****.pg.rds.aliyuncs.com";
        String port = "5432";
        String dbname = "testdb";

        // Information about the issuer.
        Connection issuerConn;
        EncdbSDK issuerSdk;
        String issuerPriKeyPemString;
        String issuerPubKeyPemString;
        // Information about the subject.
        Connection subjectConn;
        EncdbSDK subjectSdk;
        String subjectPriKeyPemString;
        String subjectPubKeyPemString;

        // Initialize the database connection and complete MEK distribution. For more information, see EncDB SDK.
        {
            // Initialize the issuer information.
            {
                // Configure the username and password parameters based on the information about the RDS instance.
                String username = "testdbuser";
                String password = "****";
                String mek = "00112233445566778899aabbccddeeff"; // This is a sample value. We recommend that you use a more complex MEK.

                // Establish a database connection.
                String dbUrl = String.format("jdbc:postgresql://%s:%s/%s?binaryTransfer=true", hostname, port, dbname);
                issuerConn = DriverManager.getConnection(dbUrl, username, password);
                System.out.println("issuer connect success");

                // Initialize the SDK and distribute the MEK. For more information, see EncDB SDK.
                issuerSdk = EncdbSDKBuilder.newInstance()
                        .setDbConnection(issuerConn)
                        .setMek(mek)
                        .setEncAlgo(EncAlgo.SM4_128_CBC)
                        .build();
                System.out.println("issuer init success");
            }
            // Initialize the subject information.
            {
                // Configure the username and password parameters based on the information about the RDS instance.
                String username = "testdbuser02";
                String password = "****";
                String mek = "ffeeddccbbaa99887766554433221100"; // This is a sample value. We recommend that you use a more complex MEK.

                // Establish a database connection.
                String dbUrl = String.format("jdbc:postgresql://%s:%s/%s?binaryTransfer=true", hostname, port, dbname);
                subjectConn = DriverManager.getConnection(dbUrl, username, password);
                System.out.println("subject connect success");

                // Initialize the SDK and distribute the MEK. For more information, see EncDB SDK.
                subjectSdk = EncdbSDKBuilder.newInstance()
                        .setDbConnection(subjectConn)
                        .setMek(mek)
                        .setEncAlgo(EncAlgo.SM4_128_CBC)
                        .build();
                System.out.println("subject init success");
            }

            // Insert test data and verify that the data owner can access and view the plaintext data after decryption.
            {
                // Write data as the issuer and verify the data.
                {
                    Connection tmpConnection = issuerConn;
                    EncdbSDK tmpSdk = issuerSdk;

                    // Prepare test data as the issuer and encrypt specific fields.
                    prepareData(tmpConnection, tmpSdk);
                    System.out.println("issuer prepare data success");

                    // Query data as the issuer to check whether the issuer can access and view the plaintext data after decryption.
                    Assertions.assertDoesNotThrow(() -> {
                        simpleQuery(tmpConnection, tmpSdk);
                    });
                    System.out.println("issuer query own data success");
                }
                // Write data as the subject and verify the data.
                {
                    Connection tmpConnection = subjectConn;
                    EncdbSDK tmpSdk = subjectSdk;

                    // Prepare test data as the subject and encrypt specific fields.
                    prepareData(tmpConnection, tmpSdk);
                    System.out.println("subject prepare data success");

                    // Query data as the subject to check whether the subject can access and view the plaintext data after decryption.
                    Assertions.assertDoesNotThrow(() -> {
                        simpleQuery(tmpConnection, tmpSdk);
                    });
                    System.out.println("subject query own data success");
                }
                // Grant access permissions on the data table. The data content in the table cannot be accessed.
                {
                    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 the public and private keys of the subject and the issuer. You need to perform this operation only once during initialization.
        {
            // Register the public and private keys as the issuer.
            {
                // Read the public and private key content from the key files.
                String prikeyFilename = "D:\\test\\Issuer\\pri_key_pkcs8.pem"; // This is a sample value. You must replace the value with the path of the key file.
                String pubkeyFilename = "D:\\test\\Issuer\\pub_key.pem"; // This is a sample value. You must replace the value with the path of the key file.
                issuerPriKeyPemString = readPemFile(prikeyFilename);
                issuerPubKeyPemString = readPemFile(pubkeyFilename);
                // Register the public and private keys.
                issuerSdk.getKeyManager().registerCertificate(issuerPriKeyPemString, issuerPubKeyPemString);
                System.out.println("issuer register certificate success");

            }

            // Register the public and private key as the subject.
            {
                // Read the public and private key content from the key files.
                String prikeyFilename = "D:\\test\\Subject\\pri_key_pkcs8.pem"; // This is a sample value. You must replace the value with the path of the key file.
                String pubkeyFilename = "D:\\test\\Subject\\pub_key.pem"; // This is a sample value. You must replace the value with the path of the key file.
                subjectPriKeyPemString = readPemFile(prikeyFilename);
                subjectPubKeyPemString = readPemFile(pubkeyFilename);
                // Register the public and private keys.
                subjectSdk.getKeyManager().registerCertificate(subjectPriKeyPemString, subjectPubKeyPemString);
                System.out.println("subject register certificate success");
            }
        }

        // Before the multi-user data authorization, the subject attempts to access the data written by the issuer. An error message indicating that access is denied is displayed.
        {
            Connection tmpConnection = subjectConn;
            EncdbSDK tmpSdk = subjectSdk;
            Connection tmpIssuerConn = issuerConn;
            SQLException exception = Assertions.assertThrows(SQLException.class, () -> {
                subjectJoinQuery(tmpConnection, tmpSdk, tmpIssuerConn);
            });
            Assertions.assertEquals("ERROR: encdb_get_hash_from_bytea: errno:fa030000", exception.getMessage());
            System.out.println("subject query issuer data failed without authorized");
        }

        // Perform multi-user data authorization. Make sure that the authorization content is correct.
        {
            // Authorize the subject to access the DEKs of the columns specified by the issuer.
            // The DEK of the issuer is specified by issuer_dek_group. The DEK of the subject is specified by subject_dek_group.
            String bclBodyJsonString = """
                    {
                        "version": 1,
                        "serial_num": "a121bac0-5cb3-4463-a2b2-1155ff29f4c8",
                        "issuer_pukid": "p81x+WqYb7BR0yP0LK0qiEaxgLDqwuIjfJhgC0mMJcE=",
                        "subject_pukid": "qIPPfgTJEEG/9WkjP0E5LLAijZ14h/Qgb2EfmBZCWSo=",
                        "validity": {
                            "not_after": "20250820111111+0800",
                            "not_before": "20240820111111+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": [
                                "*"
                            ],
                            "postproc": "NULL",
                            "preproc": "NULL"
                        }
                    }
                    """;

            // The sequence of the following steps can be changed: The issuer actively performs authorization. The subject requests authorization.
            // The issuer issues a BCL to allow the subject to access data that is encrypted by using the DEK specified in the BCL.
            {
                bclBodyJsonString = issuerSdk.getKeyManager().issueBCL(bclBodyJsonString, issuerPubKeyPemString,
                        issuerPriKeyPemString, true);
                System.out.println("issuer issue bcl success");
            }
            // The subject issues a request to access to data that is encrypted by using the DEK specified in the BCL.
            {
                bclBodyJsonString = subjectSdk.getKeyManager().issueBCL(bclBodyJsonString, subjectPubKeyPemString,
                        subjectPriKeyPemString, false);
                System.out.println("subject issue bcl success");
            }
        }

        // After the multi-user data authorization is complete, the subject attempts to access the data written by the issuer. The subject successfully accesses and views the decrypted content.
        {
            Connection tmpConnection = subjectConn;
            EncdbSDK tmpSdk = subjectSdk;
            Connection tmpIssuerConn = issuerConn;
            Assertions.assertDoesNotThrow(() -> {
                subjectJoinQuery(tmpConnection, tmpSdk, tmpIssuerConn);
            });
            System.out.println("subject query issuer data success after authorized");
        }
    }
}

FAQ

  • What do I do if the ERROR: permission denied for table <table name> error message appears?

    The error message indicate that you do not have permissions to access the table. To resolve the error, the table owner or administrator needs to execute the GRANT SELECT ON <table> TO <user>; statement to grant access permissions.

  • How do I verify that the authorization is successful?

    After the authorization is complete, you can query the data of the issuer by using the account of the subject. If the query is successful, the authorization is successful. If the query fails, the fa030000 or fa020000 error code is returned. The fa030000 error code indicates that no authorization records are found. The fa020000 error code indicates that access is denied. If you want to check whether the authorization for high-risk operations is successful, you can perform a high-risk operation. If the high-risk operation is successful, the authorization is successful.

  • How do I grant access permissions on the DEKs of multiple columns?

    If a subject needs to access multiple columns of an issuer, we recommend that you add the DEKs of the columns in a single BCL for authorization. You can add the DEKs to the issuer_dek_group and subject_dek_group parameters.

    If you want to grant access permissions on individual DEKs, you must add the DEKs in different BCLs and assign different serial numbers to the BCLs. If you use the same serial number for different BCLs, the content of the BCLs are overwritten. In this case, errors may occur during the computation of specific data.