
1.前言
因为等不到TDE功能在PostgreSQL中的及时的代码入库,最终它以plugin的形式出现,如下是它的解释:
Percona已经收到用户反馈,认为这将是一个有用的特性,所以我们正在努力将其作为Postgres的开源扩展,任何人都可以部署。Percona的联合创始人Peter Zaitsev在他的博客《PostgreSQL为什么需要TDE》中强调了一些你可能需要TDE的技术和商业原因。由于PostgreSQL还没有TDE特性,Percona希望提供TDE特性作为PostgreSQL的扩展。
详情可以参考中译文:为什么PostgreSQL需要透明数据库加密(TDE) - 墨天轮 (modb.pro) :https://www.modb.pro/db/611703
Percona 公司是成立于 2006年,总部在美国北卡罗来纳的Raleigh。由 Peter Zaitsev 和 Vadim Tkachenko, 可以通过他们的名字看出这两个人,它是由俄罗斯人创建的一家公司。业界良心!
关于CLUSTER WIDE TDE (Ttransparent Data Encryption), https://momjian.us/main/writings/pgsql/cfe.pdf Cluster File Encryption in Postgres, BRUCE MOMJIA他也做了比较详细的介绍。
完整的wiki介绍 ,可以参考: https://wiki.postgresql.org/wiki/Transparent_Data_Encryption
从根本上说,TDE必须满足三个标准——显然,它必须是安全的,但它也必须以一种对其余Postgres代码影响最小的方式完成。这有两个原因—首先,只有少数用户会使用TDE,因此添加的代码越少,所需的测试就越少。其次,添加的代码越少,TDE因未来的Postgres更改而中断的可能性就越小。最后,TDE应该满足监管要求。此外,TDE不需要防止所有潜在的攻击向量,但它应该完全防止所有预期的攻击向量。
1.1 历史
第一个补丁于2016年[1]:https://www.postgresql.org/message-id/CA%2BCSw_tb3bk5i7if6inZFc3yyf%2B9HEVNTy51QFBoeUk7UE_V%3Dw%40mail.gmail.com提出,并使用单个密钥实现实例范围的加密。2018年,提出了表级透明数据加密[2]:https://www.postgresql.org/message-id/031401d3f41d%245c70ed90%241552c8b0%24%40lab.ntt.co.jp,以及与密钥管理系统集成的方法;第一个补丁于2019年提交:https://www.postgresql.org/message-id/CAD21AoBjrbxvaMpTApX1cEsO%3D8N%3Dnc2xVZPB0d9e-VjJ%3DYaRnw%40mail.gmail.com。该补丁使用两层密钥体系结构和通用密钥管理API实现了表空间级加密,以便与外部密钥管理系统通信。
1.2 TDE的范围
内部密钥管理系统(KMS),将密钥存储在数据库中
实例范围的TDE
加密所有持久的内容
不加密内存中的共享缓冲区或数据
实例范围加密的优点:
体系结构简单
适合于对所有数据加密的需求
就TDE而言,实例范围的加密满足合规性要求,并且符合要求。它还满足对静态数据(即持久数据)加密的标准。
虽然这种方法提供了对具有读访问权限的攻击者的保护,但它不能保证数据完整性,也就是说,不能保证对具有写访问权限的攻击者的保护。有一天可能会尝试进行完整性检查,但是需要加密的文件数量会更大(不仅仅是包含用户数据的文件),并且有些文件在没有显著复杂性的情况下很难加密和完整性检查,例如pg_xact。一种解决方案可能是,如果可以保证默认数据目录不受写攻击,则仅对表空间提供完整性检查保证。表空间目录中的vm和fsm文件会使这种方法变得复杂。
1.3 什么时候需要加解密
Buffer
它在文件系统I/O期间加密缓冲区数据:
进程在写入文件系统时对数据进行加密
从文件系统读取时解密
共享缓冲区中的数据不加密
WAL
实例加密背景下:
进程以非加密状态将WAL数据插入到WAL缓冲区
当写入文件系统时,WAL缓冲区是加密的
WAL将使用专用的加密密钥
临时文件
临时文件将使用在postmaster启动时随机生成的临时密钥,该密钥仅在邮政管理员的生命周期内有效。对于并行查询,特别是并行哈希连接,由于多个并行工作人员可能使用相同的临时文件,因此应该与并行工作人员共享临时键。需要为临时文件编写统一的I/O API,以便集中加密更改。当前的API是流式的,因此可能需要使用流式密码模式。
备份文件
在pg_basebackup中将有一个选项用于更改堆/索引键以实现键轮换。在故障转移到备用服务器之后,也可以更改WAL密钥。
如何加密
我们将使用高级加密标准(AES)[4]:https://en.wikipedia.org/wiki/Advanced_Encryption_Standard。我们将提供在initdb时使用——file-encryption-method选择的三个密钥长度选项(128、192和256位)。
2. 实验体会
因为主代码实现并没有入库,我们可以试用一下TDE插件来了解一下TDE的功能。它需要系统安装配备好PG16。
而刚好前天2023.11.9,16.1版本也Release了。
PostgreSQL 16.1, 15.5, 14.10, 13.13, 12.17, and 11.22 Released!
Ref: https://www.postgresql.org/about/news/postgresql-161-155-1410-1313-1217-and-1122-released-2749/
我们可以在CentOS7.9下边安装PG16.1, 然后安装对应的插件。
2.1 简单安装
sudo yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm
sudo yum makecache
sudo yum install -y postgresql16 postgresql16-contrib postgresql16-libs postgresql16-server
可能网络原因,报错:
No package postgresql16 available.
No package postgresql16-contrib available.
No package postgresql16-libs available.
No package postgresql16-server available.
Error: Nothing to do
改用源码安装:
假定数据库用户postgres已经提前准备好。
wget https://ftp.postgresql.org/pub/source/v16.1/postgresql-16.1.tar.gz
[06:16:08-postgres@centos3:/iihero/source/pgsrc]$ tar xf postgresql-16.1.tar.gz
./configure --prefix=/usr/pgsql-16 -with-extra-version=" [Sean]" -with-openssl
[06:18:57-postgres@centos3:/iihero/source/pgsrc/postgresql-16.1]$ make -j4 world-bin && sudo make install-world-bin
-- 补充环境变量 ~/.bash_profile
export PGROOT=/usr/pgsql-16
export PGHOME=/var/lib/pgsql/16
export PGPORT=5432
export PGDATA=$PGHOME/data
export PATH=$PGROOT/bin:$PATH
export LD_LIBRARY_PATH=$PGROOT/lib:$LD_LIBRARY_PATH
TDE插件安装:
cd /iihero/source
git clone https://github.com/Percona-Lab/postgres-tde-ext.git
cd /iihero/source/postgres-tde-ext
source ~/.bash_profile
make USE_PGXS=1
sudo PATH=$PATH make USE_PGXS=1 install
在make时可能会报错:
/iihero/source/postgres-tde-ext/src/include/keyring/keyring_config.h:7:18: fatal error: json.h: No such file or directory
#include <json.h>
^
compilation terminated.
补充一下对应的包:
sudo yum install json-c-devel
这下就可以通过编译了。
sudo PATH=$PATH make USE_PGXS=1 install
......
/usr/bin/mkdir -p '/usr/pgsql-16/lib'
/usr/bin/mkdir -p '/usr/pgsql-16/share/extension'
/usr/bin/mkdir -p '/usr/pgsql-16/share/extension'
/usr/bin/install -c -m 755 pg_tde.so '/usr/pgsql-16/lib/pg_tde.so'
/usr/bin/install -c -m 644 .//pg_tde.control '/usr/pgsql-16/share/extension/'
/usr/bin/install -c -m 644 .//pg_tde--1.0.sql '/usr/pgsql-16/share/extension/'
至此二进制安装完成。
2.2 使用TDE
初始化实例
mkdir /usr/lib/pgsql/16
cd /usr/lib/pgsql/16
initdb -E UTF8 --locale=C -D data/
调整修改配置文件:
vi postgresql.conf
log_filename = 'postgresql-%Y-%m-%d.log'
logging_collector = on
log_directory = 'log'
# 添加插件预加载
shared_preload_libraries = 'pg_tde'
# keyring配置文件项
pg_tde.keyringConfigFile = '/iihero/tde/keyring.json'
创建keyring配置文件:
vi /iihero/tde/keyring.json
{
'provider': 'file',
'datafile': '/tmp/pgkeyring'
}
启动数据库
pg_ctl start
[06:47:32-postgres@centos3:/var/lib/pgsql/16/data]$ psql
psql (16.1 [Sean])
Type "help" for help.
postgres=# \dx
List of installed extensions
Name | Version | Schema | Description
---------+---------+------------+------------------------------
plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language
(1 row)
postgres=# create extension pg_tde;
CREATE EXTENSION
postgres=# \dx
List of installed extensions
Name | Version | Schema | Description
---------+---------+------------+------------------------------
pg_tde | 1.0 | public | pg_tde access method
plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language
(2 rows)
经过简单验证,pg_tde确实安装上了。
2.3 建表对比测试
1、简单的表测试
两张表:t1,sbtest1,前者普通表,后者使用TDE. 使用的都是普通的整型字段。
postgres=# create table t1(id SERIAL, k INTEGER DEFAULT '0' NOT NULL, PRIMARY KEY (id));
CREATE TABLE
postgres=# CREATE TABLE sbtest1 ( id SERIAL, k INTEGER DEFAULT '0' NOT NULL, PRIMARY KEY (id)) USING pg_tde;
CREATE TABLE
postgres=# \timing on
Timing is on.
postgres=# insert into t1 (k) select generate_series(1, 1000000);
INSERT 0 1000000
Time: 2372.397 ms (00:02.372)
postgres=# insert into sbtest1 select generate_series(1, 1000000);
INSERT 0 1000000
Time: 7292.093 ms (00:07.292)
插入时间性能简单对比,差不多慢了3倍。空间大小比较:
postgres=# select pg_total_relation_size('t1');
pg_total_relation_size
------------------------
58777600
(1 row)
Time: 9.352 ms
postgres=# select pg_total_relation_size('sbtest1');
pg_total_relation_size
------------------------
58777600
(1 row)
Time: 0.151 ms
这个有点神奇,大小居然是一样的。
postgres=# select pg_relation_filepath('t1'), pg_relation_filepath('sbtest1');
pg_relation_filepath | pg_relation_filepath
----------------------+----------------------
base/5/16450 | base/5/16442
(1 row)
Time: 0.596 ms
postgres=# \! stat /var/lib/pgsql/16/data/base/5/16450
File: ‘/var/lib/pgsql/16/data/base/5/16450’
Size: 36249600 Blocks: 70800 IO Block: 4096 regular file
Device: 803h/2051d Inode: 6623009 Links: 1
Access: (0600/-rw-------) Uid: ( 26/postgres) Gid: ( 26/postgres)
Access: 2023-11-11 07:29:32.516799960 +0800
Modify: 2023-11-11 07:36:53.453002230 +0800
Change: 2023-11-11 07:36:53.453002230 +0800
Birth: -
postgres=# \! stat /var/lib/pgsql/16/data/base/5/16442
File: ‘/var/lib/pgsql/16/data/base/5/16442’
Size: 36249600 Blocks: 70800 IO Block: 4096 regular file
Device: 803h/2051d Inode: 6623008 Links: 1
Access: (0600/-rw-------) Uid: ( 26/postgres) Gid: ( 26/postgres)
Access: 2023-11-11 07:24:53.844581957 +0800
Modify: 2023-11-11 07:34:38.873064530 +0800
Change: 2023-11-11 07:34:38.873064530 +0800
Birth: -
2、使用text型字段对比
再看两张表:t是普通表,t_tde是使用TDE的表
CREATE TABLEpostgres=# create table t(id int primary key, col2 text);
CREATE TABLE
postgres=# create table t_tde(id int primary key, col2 text) using pg_tde;
CREATE TABLE
postgres=# \timing on
Timing is on.
postgres=# insert into t select n, md5(random()::varchar) || n from generate_series(1, 1000000) as n;
INSERT 0 1000000
Time: 3345.107 ms (00:03.345)
postgres=# insert into t_tde select n, md5(random()::varchar) || n from generate_series(1, 1000000) as n;
2023-11-11 06:53:56.092 CST [12609] ERROR: EVP_CipherFinal_ex failed. OpenSSL error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
2023-11-11 06:53:56.092 CST [12609] STATEMENT: insert into t_tde select n, md5(random()::varchar) || n from generate_series(1, 1000000) as n;
ERROR: EVP_CipherFinal_ex failed. OpenSSL error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
Time: 80.529 ms
postgres=# insert into t_tde values(0, 'abcdefg');
ERROR: EVP_CipherFinal_ex failed. OpenSSL error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
Time: 1.043 ms
似乎对TEXT类型加解密有点问题。。。。。。怀疑是BUG。我想我可以向他们提一个BUG了。
3、使用VARCHAR型字段对比
建表:
postgres=# create table t3(id int primary key, col2 varchar(128));
CREATE TABLE
postgres=# create table t3_tde(id int primary key, col2 varchar(128)) using pg_tde;
CREATE TABLE
插入数据:
postgres=# \timing on
Timing is on.
postgres=# insert into t3 select n, md5(random()::varchar) || n from generate_series(1, 1000000) as n;
INSERT 0 1000000
Time: 3963.480 ms (00:03.963)
postgres=# insert into t3_tde select n, md5(random()::varchar) || n from generate_series(1, 1000000) as n;
INSERT 0 1000000
Time: 13146.733 ms (00:13.147)
能看到,时间上差不多3倍的差距。
表的空间大小:
postgres=# select pg_total_relation_size('t3');
pg_total_relation_size
------------------------
99090432
(1 row)
Time: 0.656 ms
postgres=# select pg_total_relation_size('t3_tde');
pg_total_relation_size
------------------------
99090432
(1 row)
Time: 0.227 ms
表的空间大小上看不出什么差别。简单看看内容:
postgres=# SELECT cmin, cmax, xmin, xmax, ctid, * FROM t3_tde limit 2;
cmin | cmax | xmin | xmax | ctid | id | col2
------+------+------+------+-------+----+-----------------------------------
0 | 0 | 766 | 0 | (0,1) | 1 | 10468dac2373043fac7515ea079c9b3e1
0 | 0 | 766 | 0 | (0,2) | 2 | eb5042dc2e9f054493174c24f48634f42
(2 rows)
Time: 0.505 ms
postgres=# explain select * from t3_tde where id = 99997;
QUERY PLAN
----------------------------------------------------------------------------
Index Scan using t3_tde_pkey on t3_tde (cost=0.42..8.44 rows=1 width=278)
Index Cond: (id = 99997)
(2 rows)
Time: 2.917 ms
检查server log, 会发现若干解密的调用:
2023-11-11 11:30:47.568 CST [26143] LOG: DECRYPT-TUPLE-ExecStoreBuffer: table Oid: 16472 block no: 933 data size: 42, tuple offset in file: 7647152
2023-11-11 11:30:47.568 CST [26143] STATEMENT: select * from t3_tde where id = 99997;
2023-11-11 11:30:47.568 CST [26143] LOG: DECRYPT-TUPLE-ExecStoreBuffer: Batch-No:0 Start offset: 7647152 Data_Len: 42, batch_start_block: 477947, batch_end_block: 477950
2023-11-11 11:30:47.568 CST [26143] STATEMENT: select * from t3_tde where id = 99997;
2023-11-11 11:31:17.404 CST [26143] ERROR: syntax error at or near "alalyze" at character 9
2023-11-11 11:31:17.404 CST [26143] STATEMENT: explain alalyze select * from t3_tde where id = 99997;
2023-11-11 11:31:19.514 CST [26488] ERROR: EVP_CipherFinal_ex failed. OpenSSL error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
2023-11-11 11:31:19.514 CST [26488] CONTEXT: while scanning block 0 of relation "public.t3_tde"
automatic vacuum of table "postgres.public.t3_tde"
2023-11-11 11:31:19.514 CST [26488] ERROR: EVP_CipherFinal_ex failed. OpenSSL error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
2023-11-11 11:31:19.514 CST [26488] CONTEXT: while scanning block 0 of relation "public.sbtest1"
automatic vacuum of table "postgres.public.sbtest1"
4、再多一点字段类型的试验
前边已经看到text字段出问题了,如果换上另的字段类型,不知道结果如何?
create table t4_tde(id int primary key, col2 varchar(36), col3 timestamp, col4 char(36), col5 bytea, col6 varbit(256)) using pg_tde;
postgres=# postgres=# insert into t4_tde values(1, 'wang', '2023-11-11 11:40:00', 'aaaabbbb', 'aaaabbbbccdd'::bytea, B'101010101');
ERROR: EVP_CipherFinal_ex failed. OpenSSL error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
嗯,同样报错。
postgres=# create table t4_tde(id int primary key, col2 varchar(36), col3 timestamp, col4 char(36), col6 varbit(256)) using pg_tde;
CREATE TABLE
postgres=# insert into t4_tde values(1, 'wang', '2023-11-11 11:40:00', 'aaaabbbb', B'101010101');
INSERT 0 1
如此看来,如果字段类型是TEXT或者bytea, 就会出现相关的加解密的错了,似乎是跟PADDING有关。
3. 总结:
pg_tde的推出,不管怎么说,是一件好事。目前可能还有一些BUG, 可以观察一段时间,用的人多了,估计就可以大规模推出了。
目前只是有一个latest BUILD,应该还没有正式的release版本。等出了Release版本,我们可以重新再考查一下看看。





