本文介绍在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)形式部署,本步骤介绍如何在服务器端安装扩展以及创建加密表。
- 安装EncDB扩展。
- 创建加密表。
创建加密表时,您可以根据自身需要,将敏感字段的数据类型替换为对应的加密数据类型。以下表为例,其原始定义如下:
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 |
自动识别加密数据类型并对数据进行加密解密。 |
(推荐使用)基本不需要改动业务代码。 |
EncDB SDK
- 安装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路径下。
- 添加依赖。
您需要在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>
- 全加密功能使用示例。
- 连接数据库。
说明 此示例中MEK仅为测试用,实际应用中请使用自己的MEK。参数Mek和EncAlgo只作为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在链接断开后失效。
|
- 插入加密数据。
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();
- 查询解密数据。
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
- 安装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即可。
- 添加依赖。
您需要在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>
- 全加密功能使用示例。
- 连接数据库。
说明 此示例中MEK仅为测试用,实际应用中请使用自己的MEK。参数mek和enc_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在链接断开后失效。
|
- 插入加密数据。
说明 相比于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();
- 查询解密数据。
说明 加密列存储的密文数据会被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,更换主密钥会导致原密钥加密的数据无法访问,请使用原密钥连接数据库。