本文介绍在RDS PostgreSQL安全增强型实例中,如何配置和使用全加密云数据库功能。

前提条件

  • RDS PostgreSQL服务端
  • 本地客户端
    • 已具备Java 1.8+的开发环境。
    • 已根据服务端版本下载对应EncDB依赖包。
      RDS PostgreSQL小版本 服务端encdb插件版本号 客户端EncDB依赖包
      20210930 1.1.6
      20211031 ~ 20220130 1.1.7
      20220228或以上版本 1.1.9
    • 已生成用户主密钥。
      说明
      • 用户主密钥是您访问加密数据的根凭据,全加密功能不提供用户主密钥的生成和备份服务,您需要自行生成用户主密钥。一旦您丢失密钥,将无法再访问已有的数据。因此我们建议您妥善备份用户主密钥。
      • 用户主密钥由长度为16字节的16进制字符串组成,本示例以0x00112233445566778899aabbccddeeff为例。常见的生成方法有:密码生成工具(如openssl, openssl rand -hex 16)、编程语言中的random函数。

背景信息

RDS PostgreSQL 10或其以上版本的所有实例规格都支持全加密功能,但推荐您选择Intel SGX 安全增强型规格,以获得最完整的全加密功能支持。Intel SGX 安全增强型规格与其他规格的支持差异,请参见功能支持

本文以Intel SGX 安全增强型规格的PostgreSQL实例为例,介绍如何通过客户端连接并使用全加密云数据库。

服务端配置步骤

全加密云数据库在RDS PostgreSQL服务端以扩展(Extension)形式部署,本步骤介绍如何在服务器端安装扩展以及创建加密表。

说明 本步骤需要连接RDS PostgreSQL实例进行操作,连接方法请参见连接PostgreSQL实例
  1. 安装EncDB扩展。
    CREATE EXTENSION encdb;
  2. 创建加密表。
    创建加密表时,您可以根据自身需要,将敏感字段的数据类型替换为对应的加密数据类型。以下表为例,其原始定义如下:
    CREATE TABLE example (
      id      INTEGER,
      name    VARCHAR,
      price   INTEGER,
      miles   REAL,
      secret  TEXT,
      PRIMARY KEY (id)
    );
    如果选择price, miles, secret为敏感数据,创建的加密表定义如下:
    CREATE TABLE example (
        id      INTEGER,
        name    VARCHAR,
        price   enc_int4,
        miles   enc_float4,
        secret  enc_text,
        PRIMARY KEY (id)
    );
    说明
    • Intel SGX 安全增强型规格实例中,全加密功能定义并新增了7 种数据类型,这些数据类型及其相应算子的范围内,支持SQL查询与事务,兼容标准SQL语法。具体请参见功能支持
    • 您不能直接修改原有表(即Alter table),而是需要新建一个加密表,然后再将原表数据通过SDK加密后导入。

客户端配置步骤

本步骤分别介绍通过EncDB SDK和EncJDBC两种方式连接数据库并进行数据加密解密操作。

EncDB SDK和EncJDBC两种方式的差异如下。
交互方式 说明 是否需要改动业务代码
EncDB SDK 在客户端调用EncDB SDK提供的加解密函数,对明文数据进行加密或者对密文数据进行解密。 需要。
EncJDBC 自动识别加密数据类型并对数据进行加密解密。 (推荐使用)基本不需要改动业务代码。
说明 本示例以Maven构建Java项目为例。

EncDB SDK

  1. 安装EncDB依赖包。
    mvn install:install-file -DgroupId=com.alibaba.encdb -DartifactId=<安装的Jar包名> -Dversion=<安装的Jar版本> -Dpackaging=jar -Dfile=<下载的Jar包所在路径>/<安装的Jar包名.jar>
    示例:
    mvn install:install-file -DgroupId=com.alibaba.encdb -DartifactId=libencdb -Dversion=1.2.4 -Dpackaging=jar -Dfile=D:\encdb\libs\libencdb-1.2.4.jar
    说明 此示例中EncDB依赖包在D:\encdb\libs路径下。
  2. 添加依赖。
    您需要在pom.xml中添加如下依赖:
       <dependency>
          <groupId>com.alibaba.encdb</groupId>
          <artifactId>libencdb</artifactId>
          <version>1.2.4</version>
       </dependency>
       <!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
       <dependency>
         <groupId>org.bouncycastle</groupId>
         <artifactId>bcprov-jdk15on</artifactId>
         <version>1.62</version>
       </dependency>
       <!-- https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15on -->
       <dependency>
         <groupId>org.bouncycastle</groupId>
         <artifactId>bcpkix-jdk15on</artifactId>
         <version>1.62</version>
       </dependency>
       <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
       <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>fastjson</artifactId>
         <version>1.2.62</version>
       </dependency>
       <!-- https://mvnrepository.com/artifact/org.postgresql/postgresql -->
       <dependency>
         <groupId>org.postgresql</groupId>
         <artifactId>postgresql</artifactId>
         <version>42.2.23</version>
       </dependency>
       <dependency>
         <groupId>com.google.guava</groupId>
         <artifactId>guava</artifactId>
         <version>24.1.1-jre</version>
       </dependency>
  3. 全加密功能使用示例。
    1. 连接数据库。
      说明 此示例中MEK仅为测试用,实际应用中请使用自己的MEK。参数MekEncAlgo只作为EncDB SDK的加解密模块使用,不会直接传送给服务器端。
      try {
          Class.forName("org.postgresql.Driver");
      } catch (ClassNotFoundException e) {
          e.printStackTrace();
      }
      String hostname = "pgm-uf6atm45967068****.pg.rds.aliyuncs.com";
      String port = "5432";
      String dbname = "encdbtest";
      String username = "username";
      String password = "password";
      String tableName = "example";
      String mek = "0x00112233445566778899aabbccddeeff";
      
      String dbUrl = "jdbc:postgresql://" + hostname + ":" + port + "/" + dbname + "?binaryTransfer=true";
      Connection dbConnection = null;
      try {
          dbConnection = DriverManager.getConnection(dbUrl, username, password);
      } catch (SQLException throwables) {
          throwables.printStackTrace();
      }
      
      /*创建EncdbSDK实例*/
      EncdbSDK sdk = EncdbSDKBuilder.newInstance()
              .setMek(mek)
              .setDbConnection(dbConnection)
              .setEncAlgo(Constants.EncAlgo.SM4_128_CBC) 
              .setEncScheme(Constants.EncScheme.RND)
              .setDekGenMode(Constants.DekGenMode.ENCLAVE)
              .setSdkMode(Constants.SDKMode.Default)
              .setStateless(true) 
              .build();
      Cryptor cryptor = sdk.getCryptor();
      上述示例中关键参数解释及取值示例如下:
      参数 取值示例(字符串类型) 说明
      Mek 0x00112233445566778899aabbccddeeff

      用户主密钥。

      取值范围:长度为16字节的16进制字符串。

      EncAlgo SM4_128_CBC

      加密算法。

      取值范围:
      • AES_128_GCM
      • AES_128_CBC
      • SM4_128_CBC(默认值)
      • AES_128_ECB
      • SM4_128_ECB
      说明
      • CTR加密算法暂不支持。
      • AES_128_ECB和SM4_128_ECB加密算法安全性较弱,请谨慎使用,推荐适用其他安全性更高的加密算法。
      EncScheme RND

      加密方案。

      取值范围:
      • RND(默认值):随机加密。
      • DET:确定性加密。
      说明EncAlgo取值为AES_128_ECB或SM4_128_ECB时,该参数取值无效。
      DekGenMode ENCLAVE

      生成数据密钥的方式。

      取值范围:
      • ENCLAVE(默认值):由EncDB服务器端的Enclave内生成数据密钥DEK
      • LOCAL:由EncDB SDK在客户端生成数据密钥DEK。
      SdkMode Default

      SDK模式。

      取值范围:
      • Default默认值:TEE模式,安全增强实例下使用,可使用的加密数据类型包含了TEE特有数据类型(如enc_int4)和密码学所有的数据类型(如det_type)。
      • Crypto: Crypto模式,只可使用密码学数据类型,如det_type, rnd_type。
      Stateless true

      加密数据库Stateless模式。

      取值范围:
      • true(默认值):MEK在链接断开后不会失效。
      • false:MEK在链接断开后失效。
    2. 插入加密数据。
      PreparedStatement stmt = dbConnection.prepareStatement("insert into " + tableName + " (id, name, price, miles, secret) values(?,?,?,?,?)");
      int price = 1234;
      float miles = 12.34f;
      String secret = "aliyun";
      stmt.setInt(1, 1);
      stmt.setString(2, "name");
      stmt.setBytes(3, cryptor.encrypt(tableName, "price", price));
      stmt.setBytes(4, cryptor.encrypt(tableName, "miles", miles));
      stmt.setBytes(5, cryptor.encrypt(tableName, "secret", secret));
      stmt.execute();   
    3. 查询解密数据。
      String sqlCmd = "SELECT * from " + tableName + " WHERE  price > ?";
      PreparedStatement stmt = dbConnection.prepareStatement(sqlCmd);
      stmt.setBytes(1, cryptor.encrypt(tableName,"price", 100));
      ResultSet rs = stmt.executeQuery();
      while (rs.next()) {
          int id = rs.getInt(1);
          String name = rs.getString(2);
          int price = cryptor.decryptInt(rs.getBytes(3));
          float miles = cryptor.decryptFloat(rs.getBytes(4));
          String text = cryptor.decryptString(rs.getBytes(5));
          System.out.println(id +", " + name + ", " + price + ", " + miles + ", " + text);
      }
    查询结果:
    • 已被加密的数据在客户端查询时被解密,您可以查询到明文。
      1, name, 1234, 12.34, aliyun
    • 已被加密的数据在服务器端无法查看,能够有效防御来自云平台外部和内部的安全威胁,时刻保护加密数据。
      select * from example;
       id | name |                                  price                                   |                                  miles                                   |                                  secret
      ----+------+--------------------------------------------------------------------------+--------------------------------------------------------------------------+--------------------------------------------------------------------------
        1 | name | \xdf4901df087c6a3e0325175bb76942c684191a8dda2a8d0c35f295dc1e30eaeaa0c0e3 | \x315102ea5ab8a659066ab672e6dfbfd89a3a2b360bf6efe3787931e00f61af05f7408c | \xed4903dfd1bda1a89ad6aa7e8905c0e6305e15db4bc9ce2d2cfac9e25094d2a3ed367d
      (1 行记录)

EncJDBC

  1. 安装EncDB依赖包。
    mvn install:install-file -DgroupId=com.alibaba.encdb -DartifactId=<安装的Jar包名> -Dversion=<安装的Jar版本> -Dpackaging=jar -Dfile=<下载的Jar包所在路径>/<安装的Jar包名.jar>
    示例:
    mvn install:install-file -DgroupId=com.alibaba.encdb -DartifactId=libencdb -Dversion=1.2.4 -Dpackaging=jar -Dfile=libs/libencdb-1.2.4.jar
    mvn install:install-file -DgroupId=com.alibaba.encdb -DartifactId=encjdbc -Dversion=1.0.2 -Dpackaging=jar -Dfile=libs/encjdbc-1.0.2.jar
    mvn install:install-file -DgroupId=com.alibaba.encdb -DartifactId=crypto-rewrite -Dversion=1.2.2 -Dpackaging=jar -Dfile=libs/crypto-rewrite-1.2.2.jar
    说明
    • 此示例中EncDB依赖包在libs路径下。
    • encjdbc自1.0.4版本后,内部已包含libencdb与crypto-rewrite。用户只需依赖encjdbc即可。
  2. 添加依赖。
    您需要在pom.xml中添加如下依赖:
    <dependency>
      <groupId>com.alibaba.encdb</groupId>
      <artifactId>libencdb</artifactId>
      <version>1.2.4</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba.encdb</groupId>
      <artifactId>crypto-rewrite</artifactId>
      <version>1.2.2</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba.encdb</groupId>
      <artifactId>encjdbc</artifactId>
      <version>1.0.2</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
    <dependency>
      <groupId>org.bouncycastle</groupId>
      <artifactId>bcprov-jdk15on</artifactId>
      <version>1.62</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15on -->
    <dependency>
      <groupId>org.bouncycastle</groupId>
      <artifactId>bcpkix-jdk15on</artifactId>
      <version>1.62</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.62</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.postgresql/postgresql -->
    <dependency>
      <groupId>org.postgresql</groupId>
      <artifactId>postgresql</artifactId>
      <version>42.2.23</version>
    </dependency>
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>24.1.1-jre</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.2.4</version>
    </dependency>
    <dependency>
      <groupId>org.jgrapht</groupId>
      <artifactId>jgrapht-core</artifactId>
      <!-- jgrapht does not support java 1.8 since 1.5.0 -->
      <version>1.4.0</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.5</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
      <version>1.6.4</version>
    </dependency>
    <dependency>
      <groupId>org.javatuples</groupId>
      <artifactId>javatuples</artifactId>
      <version>1.2</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>commons-cli</groupId>
      <artifactId>commons-cli</artifactId>
      <version>1.3.1</version>
    </dependency>
  3. 全加密功能使用示例。
    1. 连接数据库。
      说明 此示例中MEK仅为测试用,实际应用中请使用自己的MEK。参数mekenc_algo只作为EncJDBC的加解密模块使用,不会直接传送给服务器端。
      Class.forName("com.alibaba.encdb.encjdbc.EncDriver");
      String hostname = "pgm-uf6atm45967068****.pg.rds.aliyuncs.com";
      int port = 5432;
      String dbname = "encdbtest";
      String username = "username";
      String password = "password";
      String tableName = "example";
      String mek = "0x00112233445566778899aabbccddeeff";
      
      String dbUrl
              = String.format("encjdbc:postgresql://%s:%d/%s?mek=%s&dek_gen_mode=%s&enc_algo=%s&enc_scheme=%s&stateless=%s",
              hostname, port, dbname
              , mek
              , Constants.DekGenMode.ENCLAVE.name()
              , Constants.EncAlgo.SM4_128_CBC.name()
              , Constants.EncScheme.RND.name()
              , "true"
      );
      Connection dbConnection = DriverManager.getConnection(dbUrl, username, password);
      上述示例中关键参数解释及取值示例如下:
      参数 取值示例(字符串类型) 说明
      mek 0x00112233445566778899aabbccddeeff

      用户主密钥。

      取值范围:长度为16字节的16进制字符串。

      enc_algo SM4_128_CBC

      加密算法。

      取值范围:
      • AES_128_GCM
      • AES_128_CBC
      • SM4_128_CBC(默认值)
      • AES_128_ECB
      • SM4_128_ECB
      说明
      • CTR加密算法暂不支持。
      • AES_128_ECB和SM4_128_ECB加密算法安全性较弱,请谨慎使用,推荐适用其他安全性更高的加密算法。
      enc_scheme RND

      加密方案。

      取值范围:
      • RND(默认值):随机加密。
      • DET:确定性加密。
      说明enc_algo取值为AES_128_ECB或SM4_128_ECB时,该参数取值无效。
      dek_gen_mode ENCLAVE

      生成数据密钥的方式。

      取值范围:
      • ENCLAVE(默认值):由EncDB服务器端的Enclave内生成数据密钥DEK。
      • LOCAL:由EncDB SDK在客户端生成数据密钥DEK。
      stateless true

      加密数据库Stateless模式。

      取值范围:
      • true(默认值):MEK在链接断开后不会失效。
      • false:MEK在链接断开后失效。
    2. 插入加密数据。
      说明 相比于EncDB SDK方式,使用EncJDBC时会自动识别并加密数据,然后插入到数据库,您无需修改任何代码
      PreparedStatement stmt = dbConnection.prepareStatement("insert into " + tableName + " (id, name, price, miles, secret) values(?,?,?,?,?)");
      int price = 1234;
      float miles = 12.34f;
      String secret = "aliyun";
      stmt.setInt(1, 1);
      stmt.setString(2, "name");
      stmt.setInt(3, price);
      stmt.setFloat(4, miles);
      stmt.setString(5, secret);
      stmt.execute();
    3. 查询解密数据。
      说明 加密列存储的密文数据会被EncJDBC自动识别并解密,您无需修改任何代码
      String sqlCmd = "SELECT * from " + tableName + " WHERE  price > ?";
      PreparedStatement stmt = dbConnection.prepareStatement(sqlCmd);
      stmt.setInt(1, 100);
      
      ResultSet rs = stmt.executeQuery();
      while ( rs.next() ){
          int id = rs.getInt(1);
          String name = rs.getString(2);
          int price = rs.getInt(3);
          float miles = rs.getFloat(4);
          String secret = rs.getString(5);
          System.out.println(id + ", " + name + ", " + price + ", " + miles + ", " + secret);  
      }
    查询结果:
    • 已加密的数据在客户端查询时被解密,您可以查询到明文。
      1, name, 1234, 12.34, aliyun
    • 已被加密的数据在服务器端无法查看,能够有效防御来自云平台外部和内部的安全威胁,时刻保护用户数据。
      select * from example;
       id | name |                                  price                                   |                                  miles                                   |                                  secret
      ----+------+--------------------------------------------------------------------------+--------------------------------------------------------------------------+--------------------------------------------------------------------------
        1 | name | \xdf4901df087c6a3e0325175bb76942c684191a8dda2a8d0c35f295dc1e30eaeaa0c0e3 | \x315102ea5ab8a659066ab672e6dfbfd89a3a2b360bf6efe3787931e00f61af05f7408c | \xed4903dfd1bda1a89ad6aa7e8905c0e6305e15db4bc9ce2d2cfac9e25094d2a3ed367d
      (1 行记录)

常见问题

Q:连接数据库提示报错org.postgresql.util.PSQLException: ERROR: db_process_msg_api: process message failure - returned 0xf7070000

A:错误码0xf7070000表示MEK密钥导入失败。可能原因为同一个账号在连接同一个加密数据库时,使用了不同的MEK,更换主密钥会导致原密钥加密的数据无法访问,请使用原密钥连接数据库。

说明 如提示其他错误码,请提交工单