すべてのプロダクト
Search
ドキュメントセンター

ApsaraDB RDS:権限付与の管理

最終更新日:Nov 11, 2024

ApsaraDB RDS for PostgreSQLインスタンスの常時機密データベース機能を有効にすると、RDSインスタンス上のさまざまなユーザーの暗号文データが自動的に分離されます。 このトピックでは、マルチユーザーデータの統合コンピューティングを実装するためのアクセス許可を管理する方法と、データベース管理者 (DBA) にリスクの高いデータ操作を実行する権限を付与する方法について説明します。

説明

常時機密データベースでは、ユーザーデータはユーザーのデータ暗号化キー (DEK) を使用して暗号化されます。 異なるユーザーの暗号文データは自動的に分離されます。 共同クエリに複数のユーザーからのデータが含まれる場合、データ所有者は、データ所有者の暗号文データにアクセスするために、データをクエリするサブジェクトに権限を付与する必要があります。 このプロセスは、マルチユーザ認証管理と呼ばれる。

常時機密データベースでは、動作制御リスト (BCL) を使用して、マルチユーザーデータに対するアクセス許可を付与できます。 BCLが発行された後、サブジェクトは、データを所有する発行者のデータコンテンツを理解する必要なく、共同クエリを実行できます。 発行者は、データ漏洩について心配する必要はなく、BCLを発行または無効にして、サブジェクトの行動を制限することができる。 これにより、データが予期せず使用されることを迅速かつ効果的に防ぎます。

カスタムキーを作成したり、平文と暗号文の間でデータを変換したりするなど、常に機密性の高いデータベースに対してリスクの高い操作をDBAに許可する場合は、BCLを発行できます。 このように、DBAはアカウントを使用して関連する操作を実行できます。 この場合、サブジェクトと発行者は同じアカウントを使用します。

前提条件

ステップ1: 公開鍵と秘密鍵の初期化

    1. OpenSSLをインストールします。

      このトピックでは、オープンソースの暗号化ツールキットであるOpenSSLを使用して、公開鍵と秘密鍵を取得します。 LinuxオペレーティングシステムにはOpenSSLが提供されます。 Linuxオペレーティングシステムを使用している場合は、OpenSSLをインストールする必要はありません。 Windowsオペレーティングシステムを使用する場合は、OpenSSLソフトウェアパッケージをダウンロードしてOpenSSLをインストールする必要があります。 詳細については、「Win32/Win64 OpenSSL」をご参照ください。

      説明

      常時機密データベース (基本版) 機能が有効になっているRDSインスタンスを使用する場合は、SM2アルゴリズムを使用して公開鍵と秘密鍵を生成する必要があります。 openssl ecparam -list_curvesコマンドを実行して、SM2アルゴリズムが現在のOpenSSLバージョンでサポートされているかどうかを確認できます。 常時機密データベース (基本版) 機能が有効になっているRDSインスタンスは、非Intel SGXベースのセキュリティ強化インスタンスタイプを使用するRDSインスタンスです。 SM2がコマンド出力に含まれている場合、SM2アルゴリズムがサポートされます。 SM2がコマンド出力に含まれていない場合は、OpenSSLをSM2アルゴリズムをサポートするバージョンにアップグレードする必要があります。 OpenSSLをV1.1.1以降にアップグレードできます。

    2. 常時機密データベース機能が有効になっているRDSインスタンスのインスタンスタイプに基づいて、サブジェクトと発行者の公開鍵と秘密鍵を取得します。

      2つのキーファイルが生成される。 pub_key.pemファイルは公開鍵ファイルです。 pri_key_pkcs8.pemファイルは秘密鍵ファイルです。

      常に機密性の高いデータベース機能のハードウェア拡張版 (Intel SGXベース)

      常時機密データベース (ハードウェア拡張エディション) 機能が有効になっているRDSインスタンスは、Intel SGXベースのセキュリティ拡張インスタンスタイプを使用するRDSインスタンスです。 詳細については、「プライマリApsaraDB RDS For PostgreSQLインスタンスのインスタンスタイプ」をご参照ください。

      次の操作を実行して、公開鍵と秘密鍵を取得できます。

      1. OpenSSLを使用して、RSA (Rivest-Shamir-Adleman) 秘密鍵を生成します。

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

        3072: キーの長さ。 キーの長さは、ビジネス要件に基づいてセキュリティ標準に準拠した別の値に変更できます。

      2. OpenSSLを使用してRSA公開鍵を生成します。

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

      常に機密性の高いデータベース機能の基本版

      常時機密データベース (基本版) 機能が有効になっているRDSインスタンスは、非Intel SGXベースのセキュリティ強化インスタンスタイプを使用するRDSインスタンスです。 詳細については、「プライマリApsaraDB RDS For PostgreSQLインスタンスのインスタンスタイプ」をご参照ください。

      次の操作を実行して、公開鍵と秘密鍵を取得できます。

      1. OpenSSLを使用してSM2プライベートキーを生成します。

        # 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. OpenSSLを使用してSM2公開キーを生成します。

        openssl ec -in ec_param.pem -pubout -out pub_key.pem
  1. サブジェクトと発行者の公開鍵と秘密鍵を別々に初期化します。

    ビジネスコードでサブジェクトと発行者の公開鍵と秘密鍵を初期化します。

    説明

    OpenSSLを使用して生成されたPEMファイルに新ラインが自動的に追加されます。 特定のエディタでは、改行の表示を最適化することができる。 その結果、PEMファイルのコンテンツを手動でコピーすると、改行が省略されます。 データの正確性を確保するために、プログラムコードを使用してPEMファイルの内容を読み取ることを推奨します。

    //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);

手順2: マルチユーザーデータに対するアクセス許可の付与

  1. 承認コンテンツを定義します。

    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"
      }
    }
      """;

    パラメーターの説明

    パラメーター

    値の例

    説明

    version

    1

    バージョン番号。 値を1に設定します。

    serial_num

    「a121bac0-5cb3-4463-a2b2-1155ff29f4c8」

    UUID形式の一意のランダムなシリアル番号。

    説明

    UUID生成ツールを使用してシリアル番号を生成できます。

    issuer_pukid

    "p81x + WqYb7BR0yP0LK0qiEaxgLDqwuIjfJhgC0mMJcE="

    発行者の公開鍵ダイジェスト。 次のコードを実行すると、常時機密データベース機能のエディションと生成された公開鍵ファイルpub_key.pemに基づいて公開鍵ダイジェストを取得できます。

    • ハードウェア拡張エディション (インテルSGXベース)

      openssl sha256-バイナリpub_key.pem | openssl base64

    • ベーシックエディション

      openssl sm3-バイナリpub_key.pem | openssl base64

    subject_pukid

    "qIPPfgTJEEG/9WkjP0E5LLAijZ1 4h/Qgb2EfmBZCWSo="

    サブジェクトの公開鍵ダイジェスト。 このパラメーターは、issuer_pukidパラメーターの設定と同じ方法で設定できます。

    妥当性

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

    承認の有効期間。 値はGeneralizedTime時間形式である必要があります。

    • "not_before": 有効期間の始まり。

    • "not_after": 有効期間の終了。

    ポリシー

    issuer_dek_group

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

    発行者によって許可されているDECsのグループ。

    • "groupid": DEKグループのID

    • "min": DEKグループ内の最小のDEK ID

    • "max": DEKグループで最大のDEK ID

    説明

    DEKグループでは、DEK IDの値は単調に増加しなければならない。

    groupidパラメーターの値を取得するには、次のコードを実行します。

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

    説明
    • <user_name>: 発行者のユーザー名

    • <table_name>: 発行者がサブジェクトにアクセスを許可するテーブルの名前

    • <column_name>: 発行者がサブジェクトにアクセスを許可する列の名前

    subject_dek_group

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

    サブジェクトがアクセスを許可されているDEKグループ。

    • "groupid": DEKグループのID

    • "min": DEKグループ内の最小のDEK ID

    • "max": DEKグループで最大のDEK ID

    説明

    DEKグループでは、DEK IDの値は単調に増加しなければならない。

    groupidパラメーターの値を取得するには、次のコードを実行します。

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

    説明
    • <user_name>: サブジェクトのユーザー名

    • <table_name>: サブジェクトがアクセスを許可されているテーブルの名前。

    • <column_name>: サブジェクトがアクセスを許可されている列の名前。

    result_dek

    "件名"

    計算結果の暗号化に使用されるDEK。

    • "SUBJECT": 現在の計算でサブジェクトのDEKを使用します。

    • "ISSUER": 現在の計算で発行者のDEKを使用します。

    • DEK ID: 指定されたDEK IDを持つDEKを使用します。

      重要

      特定のDEKを使用する場合は、このパラメーターをDEKのIDに設定します。 DEK IDを二重引用符 ("") で囲む必要なく、DEK IDを直接指定できます。

    operation

    [ "*"]

    許可された計算操作。

    • "encrypt": 暗号化

    • "decrypt": 解読

    • "cmp": 比較

    • "*": すべての操作

    postproc

    "NULL"

    計算の前の前処理操作。 値をNULLに設定します。これは、前処理操作が必要ないことを指定します。

    preproc

    "NULL"

    計算後の後処理操作。 値をNULLに設定します。これは、後処理操作が不要であることを指定します。

  2. マルチユーザーデータに対するアクセス権限の付与

    • 発行者は、その公開鍵および秘密鍵を使用してBCLを発行し、許可された列のデータにアクセスすることを主体に許可する。

      boolean isIssuer = true;
      bclBodyJsonString = km.issueBCL(bclBodyJsonString, userPukPemString, userPrkPemString, isIssuer);
    • サブジェクトは、公開鍵と秘密鍵を使用してBCLを発行し、許可されたデータにアクセスします。

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

(オプション) 手順3: 承認の取り消し

発行者が承認を取り消す場合は、次の操作を実行します。

  1. 取り消される権限を定義します。

    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"
        }
      ]
    }
     """;  

    パラメーターの説明

    パラメーター

    値の例

    説明

    version

    1

    バージョン番号。 値を1に設定します。

    pukid

    "dYJ3Wfj/n0eZbuqgQjv8bnBdPXGyWGOlxE/uMy16NXo="

    発行者の公開鍵ダイジェスト。 BCL認可アプリケーションの発行者の公開鍵ダイジェストに値を設定します。 このパラメーターは、「権限付与コンテンツの定義」で説明したパラメーターと同じ方法で設定できます。

    this_update

    "20220819111128 + 0800"

    失効リストが更新される時点。 値はGeneralizedTime時間形式である必要があります。

    next_update

    "20220919111128 + 0800"

    失効リストの次の更新時刻。 値はGeneralizedTime時間形式である必要があります。

    取り消された

    revocation_date

    "20220819111128 + 0800"

    承認が取り消された時点。 値はGeneralizedTime時間形式である必要があります。

    serial_num

    「a121bac0-5cb3-4463-a2b2-1155ff29f4c8」

    UUID形式のシリアル番号。 このパラメーターは、BCL認証アプリケーションで指定されたシリアル番号に設定する必要があります。

  2. 発行者は失効を承認し発行する。

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

シナリオ

シナリオ1: リスクの高い操作: 平文と暗号文の間のデータ変換

データを移行せずに既存の平文データベースに対して常に機密性のデータベース機能を有効にする場合は、データベース上の平文データを暗号文データに直接変換できます。 詳細については、「平文と暗号文の間のデータ変換」をご参照ください。 デフォルトでは、DBAを含むユーザーは、常に機密のデータベースで平文と暗号文の間のデータ変換を実行できません。これは、この操作がリスクの高い操作と見なされるためです。

データのセキュリティリスクを理解して認識した場合は、BCLを発行して、必要なユーザーに、常に機密性の高いデータベースの平文と暗号文の間でデータを直接変換することを許可できます。 この場合、サブジェクトと発行者は同じアカウントを使用します。

  • BCLのコンテンツを定義する場合、issuer_pukidパラメーターをsubject_pukidパラメーターと同じ値に設定し、issuer_dek_groupパラメーターをsubject_dek_groupパラメーターと同じ値に設定する必要があります。 詳細については、「権限付与コンテンツの定義」をご参照ください。

    権限付与コンテンツの定義

    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"
      }
    }
     """;
  • 公開鍵と秘密鍵を初期化すると、サブジェクトと発行者は同じ公開鍵と秘密鍵を使用します。 公開鍵はuserPukPemStringパラメーターで指定され、秘密鍵はuserPrkPemStringパラメーターで指定されます。 発行者のuserPukPemStringおよびuserPrkPemStringパラメーターの値は、サブジェクトの値と同じです。 詳細については、「公開鍵と秘密鍵の初期化」をご参照ください。

    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);

BCLを発行した後、許可されたアカウントを使用して必要な操作を実行できます。 サンプルコード:

高リスク操作: 平文と暗号文の間のデータ変換

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.
        }
    }
}

シナリオ2: マルチユーザーデータの共同クエリ

たとえば、データプラットフォーム会社は、データプラットフォームを使用してユーザーデータを収集し、ユーザープロファイルを生成するために必要な許可を取得します。 次いで、データプラットフォーム会社は、保険会社などの第三者が、マルチパーティデータ統合およびコンピューティングのためにデータを使用することを許可する。 これは共同マーケティングの実装に役立ちます。 詳細については、「マルチパーティデータ統合とコンピューティング」をご参照ください。

次のコードは、共同クエリの実行方法の例を示しています。

マルチユーザデータのジョイントクエリ

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");
        }
    }
}

よくある質問

  • ERROR: permission denied for table <table name> エラーメッセージが表示された場合はどうすればよいですか。

    エラーメッセージは、テーブルにアクセスする権限がないことを示しています。 このエラーを解決するには、テーブルの所有者または管理者がGRANT SELECT ON <table> To <user>; 文を実行してアクセス許可を付与する必要があります。

  • 承認が成功したことを確認するにはどうすればよいですか。

    承認が完了したら、サブジェクトのアカウントを使用して発行者のデータを照会できます。 クエリが成功した場合、承認は成功です。 クエリが失敗した場合、fa030000またはfa020000エラーコードが返されます。 fa030000エラーコードは、承認レコードが見つからないことを示します。 fa020000エラーコードは、アクセスが拒否されたことを示します。 ハイリスク操作の承認が成功したかどうかを確認する場合は、ハイリスク操作を実行できます。 ハイリスク操作が成功した場合、承認は成功です。

  • 複数の列のDEKに対するアクセス許可を付与するにはどうすればよいですか。

    サブジェクトが発行者の複数の列にアクセスする必要がある場合は、承認のために単一のBCLに列のDEKを追加することを推奨します。 DEKは、issuer_dek_groupおよびsubject_dek_groupパラメーターに追加できます。

    個々のDEKにアクセス権限を付与する場合は、異なるBCLにDEKを追加し、BCLに異なるシリアル番号を割り当てる必要があります。 異なるBCLに同じシリアル番号を使用すると、BCLの内容が上書きされます。 この場合、特定のデータの計算中にエラーが発生する可能性があります。