集成EncJDBC

重要

本文中含有需要您注意的重要提示信息,忽略该信息可能对您的业务造成影响,请务必仔细阅读。

为了支持您的Java业务系统实现零改造与一键式接入,全密态PolarMySQL功能推出了相应的全密态客户端驱动JDBC(即EncJDBC)。EncJDBC能够自动完成密文数据的解密,并返回明文数据,整个过程对应用程序而言是透明的,业务应用仅需几行配置即可接入全密态PolarMySQL,现有业务代码无需进行任何修改。

方案架构

全密态PolarMySQL是一套基于数据库代理(Proxy)的动态加解密方案。其核心逻辑是在查询结果返回给客户端的过程中,由代理层对预先配置的敏感字段进行加密。客户端应用程序需配合专用的 EncJDBC 驱动,在接收到数据后进行透明解密。

工作流程与角色权限

  1. 查询请求:客户端应用通过EncJDBC驱动向PolarDB集群地址发起标准SQL查询。

  2. 代理处理:数据库代理(Proxy)接收请求,转发至后端数据库内核执行。

  3. 动态加密:在查询结果返回途中,代理层检查是否命中预设的加密策略。如果命中,则使用用户指定的密钥(通过KMS或自持)对结果集中的敏感字段进行加密。

  4. 数据返回:加密后的结果集返回给客户端。

  5. 透明解密:客户端的EncJDBC驱动自动对密文数据进行解密,应用程序获取到明文数据,整个过程对业务代码透明。

根据数据库账号的角色,查询结果会有不同表现:

  • 超级管理员:查询结果始终为明文,不受加密策略影响,便于数据库管理与审计。

  • 普通用户:查询结果为密文,需配合使用EncJDBC驱动和正确的密钥才能在客户端解密数据。

  • 其他用户:查询结果为密文,且无法解密。

使用限制

实施前,需了解以下关键限制,以评估其是否符合业务与技术架构要求。

  • 连接地址要求:加密规则仅在通过集群地址自定义地址连接时生效。直接连接主地址将绕过代理,导致加密功能失效。

  • 密钥管理:使用自持密钥时,暂不支持更新密钥且存在丢失和泄露风险。密钥一旦丢失,对应的加密数据将无法解密,需建立严格的安全流程来管理密钥。

  • JDK版本:需使用1.8或以上版本。

使用KMS密钥

步骤一:配置KMS访问权限

  1. 获取访问密钥:获取RAM用户的AccessKeyIdAccessKeySecret,以便应用程序能够获取KMS托管的MEK。

    1. 已有合适的RAM用户:若您已有适用的RAM用户以访问KMS,则可直接使用该用户。

    2. 无合适的RAM用户:若无合适的RAM用户,请前往RAM 控制台,进入身份管理 > 用户页面,单击创建用户,并按照页面指引完成用户创建。

  2. RAM用户授权

    1. 前往RAM 控制台,进入权限管理 > 权限策略页面,单击创建权限策略。切换至脚本编辑,复制下述内容并单击确定。填写策略名称,完成创建。

      {
          "Version": "1",
          "Statement": [
              {
                  "Effect": "Allow",
                  "Action": "KMS:Decrypt",
                  "Resource": "*"
              }
          ]
      }
    2. 返回RAM 控制台,进入身份管理 > 用户页面,找到目标RAM用户,单击操作列的添加权限,并授予其刚创建的权限策略。以便于PolarDB可动态解密数据。

  3. 获取KMS实例访问地址:

    • VPC地址(默认):KMS实例中的密钥默认仅允许VPC网络访问,您可以在KMS的实例管理页面,选择实例所在地域,找到目标KMS实例,单击操作列的详情,然后在基本信息页签,查看实例VPC地址。

    • 公网地址:若您需要通过公网访问密钥,则需要在RD多账号页签下开启公网访问权限。开启后,可在基本信息页签,查看公网地址

步骤二:安装依赖

您需要在自己的Maven项目配置文件pom.xml中加入以下依赖项。

<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-cls-jdbc</artifactId>
    <version>1.0.10-1</version>
</dependency>
说明

在添加Maven依赖时请根据实际使用场景替换version的值。您可以在官网上查看aliyun-cls-jdbc的最新版本。

步骤三:配置MEK连接数据库

您可以通过JDBC properties属性、文件以及URL三种方式来配置MEK。若您的JDBC同时配置了两种或更多方法,则优先级顺序为:JDBC properties配置 > 文件配置 > URL配置。

说明
  • 以下配置和连接方式,MEK均在客户端本地进行处理,并以安全方式(信封加密)发送到服务端,保证MEK不泄露。

  • 不推荐直接将访问密钥(AccessKeyIdAccessKeySecret)硬编码在业务代码中,本示例使用配置系统环境变量的方式管理访问密钥。具体内容,请参见Linux、macOSWindows系统配置环境变量

  • 如果需要使用STS临时访问凭证获取KMS托管的MEK,您可以使用STS SDK获取临时凭据STS Token。STS SDK示例,请参见STS SDK概览

JDBC properties配置

标准的JDBC在建立连接时,可以通过Properties对象设置用户自定义属性。

示例

// 准备好连接地址(hostname)、端口(port)、数据库实例名(dbname)、用户名(username)、密码(password)等连接信息。
String hostname = "your-hostname";
String port = "your-port";
String dbname = "your-database-name";
String username = "your-username";
String password = "your-password";

// 从环境变量中获取访问密钥(AccessKey ID和AccessKey Secret)。
String accessKeyId = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
String accessKeySecret = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
// 如果使用STS临时访问凭证读取KMS密钥,还需要填写获取的STS安全令牌(SecurityToken)。
// String stsToken= "yourSecurityToken";

// KMS实例访问地址,开启公网访问使用公网地址。在VPC网络访问,使用实例VPC地址。
String kmsEndpoint = "your-kms-endpoint";

Properties props = new Properties();
props.setProperty("user", username);
props.setProperty("password", password);
props.setProperty("ALIBABA_CLOUD_ACCESS_KEY_ID", accessKeyId);
props.setProperty("ALIBABA_CLOUD_ACCESS_KEY_SECRET", accessKeySecret);
props.setProperty("ALIBABA_CLOUD_KMS_ENDPOINT", kmsEndpoint);
// props.setProperty("ALIBABA_CLOUD_STS_TOKEN","stsToken");

// 下面是MySQL版数据库的连接URL格式"jdbc:mysql:encdb://%s:%s/%s"。
String dbUrl = String.format("jdbc:mysql:encdb://%s:%s/%s", hostname, port, dbname);

// 下面是MySQL版数据库的加载 EncJDBC 驱动。
Class.forName("com.aliyun.encdb.mysql.jdbc.EncDriver");

// 获取数据库连接。
Connection connection = DriverManager.getConnection(dbUrl, props);

// ... 发起查询 ...
URL配置

支持通过URL链接中嵌入获取KMS密钥的参数。

示例

// 准备好连接地址(hostname)、端口(port)、数据库实例名(dbname)、用户名(username)、密码(password)等连接信息。
String hostname = "your-hostname";
String port = "your-port";
String dbname = "your-database-name";
String username = "your-username";
String password = "your-password";

// 从环境变量中获取访问密钥(AccessKey ID和AccessKey Secret)。
String accessKeyId = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
String accessKeySecret = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
// 如果使用STS临时访问凭证读取KMS密钥,还需要填写获取的STS安全令牌(SecurityToken)。
// String stsToken= "yourSecurityToken";

// KMS实例访问地址,开启公网访问使用公网地址。在VPC网络访问,使用实例VPC地址。
String kmsEndpoint = "your-kms-endpoint";

// 下面是MySQL版数据库的连接URL格式。
String dbUrl = String.format("jdbc:mysql:encdb://%s:%s/%s?ALIBABA_CLOUD_ACCESS_KEY_ID=%s&ALIBABA_CLOUD_ACCESS_KEY_SECRET=%s&ALIBABA_CLOUD_KMS_ENDPOINT=%s", hostname, port, dbname, accessKeyId, accessKeySecret, kmsEndpoint);
// 使用STS Token。
// String dbUrl = String.format("jdbc:mysql:encdb://%s:%s/%s?ALIBABA_CLOUD_ACCESS_KEY_ID=%s&ALIBABA_CLOUD_ACCESS_KEY_SECRET=%s&ALIBABA_CLOUD_KMS_ENDPOINT=%s&ALIBABA_CLOUD_STS_TOKEN=%s", hostname, port, dbname, accessKeyId, accessKeySecret, kmsEndpoint, stsToken);

// 下面是MySQL版数据库的加载 EncJDBC 驱动。
Class.forName("com.aliyun.encdb.mysql.jdbc.EncDriver");

// 获取数据库连接。
Connection connection = DriverManager.getConnection(dbUrl, username, password);

// ... 发起查询 ...

步骤四:查询加密列的明文数据

连接数据库成功后,可以像普通JDBC查询一样操作数据库。EncJDBC会自动对加密列进行解密并返回明文数据。

示例

// 创建查询语句。
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("SELECT * FROM your_table_name");

// 遍历结果集。
while (resultSet.next()) {
    for (int i = 0; i < rs.getMetaData().getColumnCount(); i++) {
        System.out.print(rs.getString(i + 1));
        System.out.print("\t");
    }
    System.out.print("\n");
}

附录:完整代码示例

说明

本示例使用的Maven版本为3.9.9,使用的开发工具为IntelliJ IDEA Community Edition 2024.1.2

import java.sql.*;
import java.util.Properties;

public class EncryptedColumnAccess {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        // 以下连接地址(hostname)、端口(port)、数据库实例名(dbname)、用户名(username)、密码(password)等连接信息需要更新为您的实例信息。
        String hostname = "your-hostname";
        String port = "your-port";
        String dbname = "your-database-name";
        String username = "your-username";
        String password = "your-password";

        // 从环境变量中获取访问密钥(AccessKey ID和AccessKey Secret)。
        String accessKeyId = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
        String accessKeySecret = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
        // 如果使用STS临时访问凭证读取KMS密钥,还需要填写获取的STS安全令牌(SecurityToken)。
        // String stsToken= "yourSecurityToken";

        // KMS实例访问地址,开启公网访问使用公网地址。在VPC网络访问,使用实例VPC地址。
        String kmsEndpoint = "your-kms-endpoint";

        Properties props = new Properties();
        props.setProperty("user", username);
        props.setProperty("password", password);
        props.setProperty("ALIBABA_CLOUD_ACCESS_KEY_ID", accessKeyId);
        props.setProperty("ALIBABA_CLOUD_ACCESS_KEY_SECRET", accessKeySecret);
        props.setProperty("ALIBABA_CLOUD_KMS_ENDPOINT", kmsEndpoint);
        // props.setProperty("ALIBABA_CLOUD_STS_TOKEN","stsToken");

        // MySQL版数据库的连接URL格式"jdbc:mysql:encdb://%s:%s/%s"。
        String dbUrl = String.format("jdbc:mysql:encdb://%s:%s/%s", hostname, port, dbname);

        // 加载 EncJDBC 驱动。
        Class.forName("com.aliyun.encdb.mysql.jdbc.EncDriver");

        // 获取数据库连接。
        Connection connection = DriverManager.getConnection(dbUrl, props);

        // 发起查询。
        try {
            // 创建查询语句。
            Statement statement = connection.createStatement();
            ResultSet resultSet = statement.executeQuery("SELECT * FROM users");

            // 遍历结果集。
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String name = resultSet.getString("username");
                String phone = resultSet.getString("phone");

                // 根据你的表结构处理其他字段。
                System.out.println("ID: " + id + ", Name: " + name + ", Phone: " + phone);
            }

            // 关闭资源。
            resultSet.close();
            statement.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

使用本地密钥

步骤一:生成MEK

警告

MEK是您授权客户端访问加密数据的根凭据。出于安全考虑,加密数据库不持有并管理您的MEK,也不提供MEK的生成和备份服务,您需要自行生成MEK。MEK的保存和管理对数据库的安全性非常重要。因此建议您妥善备份MEK。

MEK:由客户端通过安全的非对称加密协议传输给数据库服务端,使服务端与客户端具有相同的密钥,从而通过对称加密安全传输数据。

取值范围:长度为16字节的16进制字符串,且长度为32个字符。例如,00112233445566778899aabbccddeeff

常见的生成方法:密码生成工具或编程语言中的random函数。

示例

  • Linux系统自带OpenSSL工具,执行openssl rand -hex 16,生成密钥。

    # 使用 OpenSSL 生成一个安全的随机密钥
    openssl rand -hex 16
    # 输出示例: 11ce9a9489fab7355cf710837cea5d5b
  • Windows系统,请安装OpenSSL软件包

步骤二:安装依赖

您需要在自己的Maven项目配置文件pom.xml中加入以下依赖项。

<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-cls-jdbc</artifactId>
    <version>1.0.10-1</version>
</dependency>
说明

在添加Maven依赖时请根据实际使用场景替换version的值。您可以在官网上查看aliyun-cls-jdbc的最新版本。

步骤三:配置MEK连接数据库

您可以通过JDBC properties属性、文件以及URL三种方式来配置MEK。若您的JDBC同时配置了两种或更多方法,则优先级顺序为:JDBC properties配置 > 文件配置 > URL配置。

说明

以下配置和连接方式,MEK均在客户端本地进行处理,并以安全方式(信封加密)发送到服务端,保证MEK不泄露。

JDBC properties配置

标准的JDBC在建立连接时,可以通过Properties对象设置用户自定义属性。

示例

// 准备好连接地址(hostname)、端口(port)、数据库实例名(dbname)、用户名(username)、密码(password)等连接信息。
String hostname = "your-hostname";
String port = "your-port";
String dbname = "your-database-name";
String username = "your-username";
String password = "your-password";

// 用户主密钥。
String mek = "00112233445566778899aabbccddeeff"; 

Properties props = new Properties();
props.setProperty("user", username);
props.setProperty("password", password);
props.setProperty("MEK", mek);

// MySQL版数据库的连接URL格式"jdbc:mysql:encdb://%s:%s/%s"。
String dbUrl = String.format("jdbc:mysql:encdb://%s:%s/%s", hostname, port, dbname);

// MySQL版数据库的加载 EncJDBC 驱动。
Class.forName("com.aliyun.encdb.mysql.jdbc.EncDriver");

// 获取数据库连接。
Connection connection = DriverManager.getConnection(dbUrl, props);

// ... 发起查询 ...
文件配置
说明

文件配置方式仅适用配置本地密钥MEK。

支持通过配置文件导入参数,以配置所需的MEK等参数。您可以在项目中设置一个名为encJdbcConfigFile的属性,将其值设置为配置文件的路径(缺省时,系统将默认使用encjdbc.conf文件)。配置文件的内容如下:

MEK=00112233445566778899aabbccddeeff

示例

  1. 您可以通过以下两种方式指定配置文件的位置:

    • 将文件放入项目中的resources目录下,如下图所示:

      image

    • 将文件放入项目根目录,即程序的运行时目录。

  2. 做完文件配置后,您可以不用在程序中做额外的配置,如下面所示:

    // 准备好连接地址(hostname)、端口(port)、数据库实例名(dbname)、用户名(username)、密码(password)等连接信息。
    String hostname = "your-hostname";
    String port = "your-port";
    String dbname = "your-database-name";
    String username = "your-username";
    String password = "your-password";
    
    // MySQL版数据库的连接URL格式"jdbc:mysql:encdb://%s:%s/%s"
    String dbUrl = String.format("jdbc:mysql:encdb://%s:%s/%s", hostname, port, dbname);
    
    // MySQL版数据库的加载 EncJDBC 驱动。
    Class.forName("com.aliyun.encdb.mysql.jdbc.EncDriver");
    
    // 获取数据库连接。
    Connection connection = DriverManager.getConnection(dbUrl, username, password);
    
    // ... 发起查询 ...
URL配置

支持通过URL链接中嵌入MEK等参数。

示例

// 准备好连接地址(hostname)、端口(port)、数据库实例名(dbname)、用户名(username)、密码(password)等连接信息。
String hostname = "your-hostname";
String port = "your-port";
String dbname = "your-database-name";
String username = "your-username";
String password = "your-password";

 // 用户主密钥。
String mek = "00112233445566778899aabbccddeeff";

// MySQL版数据库的连接URL格式"jdbc:mysql:encdb://%s:%s/%s?MEK=%s"。
String dbUrl = String.format("jdbc:mysql:encdb://%s:%s/%s?MEK=%s", hostname, port, dbname, mek);

// MySQL版数据库的加载 EncJDBC 驱动。
Class.forName("com.aliyun.encdb.mysql.jdbc.EncDriver");

// 获取数据库连接。
Connection connection = DriverManager.getConnection(dbUrl, username, password);

// ... 发起查询 ...

步骤四:查询加密列的明文数据

连接数据库成功后,可以像普通JDBC查询一样操作数据库。EncJDBC会自动对加密列进行解密并返回明文数据。

示例

// 创建查询语句。
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("SELECT * FROM your_table_name");

// 遍历结果集。
while (resultSet.next()) {
    for (int i = 0; i < rs.getMetaData().getColumnCount(); i++) {
        System.out.print(rs.getString(i + 1));
        System.out.print("\t");
    }
    System.out.print("\n");
}

附录:完整代码示例

说明

本示例使用的Maven版本为3.9.9,使用的开发工具为IntelliJ IDEA Community Edition 2024.1.2

import java.sql.*;
import java.util.Properties;

public class EncryptedColumnAccess {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        // 以下连接地址(hostname)、端口(port)、数据库实例名(dbname)、用户名(username)、密码(password)等连接信息需要更新为您的实例信息。
        String hostname = "your-hostname";
        String port = "your-port";
        String dbname = "your-database-name";
        String username = "your-username";
        String password = "your-password";
        
        // 只是示例,建议用更复杂的密钥。
        String mek="00112233445566778899aabbccddeeff";

        Properties props = new Properties();
        props.setProperty("user", username);
        props.setProperty("password", password);
        props.setProperty("MEK", mek);
        
        // MySQL版数据库的连接URL格式"jdbc:mysql:encdb://%s:%s/%s"。
        String dbUrl = String.format("jdbc:mysql:encdb://%s:%s/%s", hostname, port, dbname);

        // 加载 EncJDBC 驱动。
        Class.forName("com.aliyun.encdb.mysql.jdbc.EncDriver");

        // 获取数据库连接。
        Connection connection = DriverManager.getConnection(dbUrl, props);

        // 发起查询。
        try {
            // 创建查询语句。
            Statement statement = connection.createStatement();
            ResultSet resultSet = statement.executeQuery("SELECT * FROM users");

            // 遍历结果集。
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String name = resultSet.getString("username");
                String phone = resultSet.getString("phone");

                // 根据你的表结构处理其他字段。
                System.out.println("ID: " + id + ", Name: " + name + ", Phone: " + phone);
            }

            // 关闭资源。
            resultSet.close();
            statement.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

常见问题

集成EncJDBC时,运行报错Code: Forbidden.KeyNotFound;Message: code: 404, The specified Key is not found.应如何处理?

请检查您应用程序中,KMS实例访问地址是否设置正确。

  • 检查应用程序环境:KMS实例访问地址区分VPC地址与公网地址。

    • VPC地址(默认):KMS实例中的密钥默认仅允许VPC网络访问,您可以在KMS的实例管理页面,选择实例所在地域,找到目标KMS实例,单击操作列的详情,然后在基本信息页签,查看实例VPC地址。

    • 公网地址:若您需要通过公网访问密钥,则需要在RD多账号页签下开启公网访问权限。开启后,可在基本信息页签,查看公网地址

  • 检查KMS实例地域

    • 请根据您KMS实例所在地域调整访问地址。例如,华东1(杭州)公网地址应为kms.cn-hangzhou.aliyuncs.com,华北2(北京)公网地址应为kms.cn-beijing.aliyuncs.com

集成EncJDBC时,运行报错Code:UnsupportedOperation;Message: code: 400, This action is not supported.应如何处理?

可能的原因如下,请逐一进行排查:

  • 您可能使用的并非KMS用户主密钥,而是默认密钥中的主密钥。

    说明

    您的默认密钥中的主密钥只能用于云产品服务端加密,不能用于您的客户端数据加密。如果您有客户端数据加密的诉求(全密态PolarMySQL),请通过创建实例购买主密钥(软件)或者主密钥(硬件)

  • KMS实例没有开通公网访问权限。

集成EncJDBC时,运行报错Code:Forbidden.NoPermission;Message: code: 403, This operation for XXX is forbidden by permission system.应如何处理?

您的访问密钥(AccessKeyIdAccessKeySecret)可能没有KMS的访问权限,请授予其KMS:Decrypt权限,以便于PolarDB可动态解密数据。

  1. 前往RAM 控制台,进入权限管理 > 权限策略页面,单击创建权限策略。切换至脚本编辑,复制下述内容并单击确定。填写策略名称,完成创建。

    {
        "Version": "1",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": "KMS:Decrypt",
                "Resource": "*"
            }
        ]
    }
  2. 返回RAM 控制台,进入身份管理 > 用户页面,找到目标RAM用户,单击操作列的添加权限,并授予其刚创建的权限策略。以便于PolarDB可动态解密数据。