1. 项目概述为什么要在数据库里搞国密加密如果你负责过金融、政务或者一些对数据安全有强监管要求的项目肯定对“国密”这个词不陌生。简单说国密就是咱们国家自主研发的一套密码算法标准核心是SM2、SM3、SM4。以前我们做系统安全尤其是网络传输加密基本都绕不开国外的TLS协议和RSA、AES这些算法。但现在越来越多的场景要求必须支持国密算法这不仅是合规要求更是技术自主可控的体现。那么当国密遇上数据库特别是像PostgreSQL这样的开源数据库巨头会擦出什么火花这就是我们今天要聊的核心在PostgreSQL中实现基于国密TLCP协议的双证书加密通信。你可能用过PostgreSQL的SSL/TLS加密连接但那个默认用的是国际算法。我们的目标是把它“改造”成使用国密SM2算法的TLCP协议。这不仅仅是换个算法那么简单。TLCP协议和TLS 1.2/1.3在握手流程、证书体系上都有显著区别。最核心的一点就是双证书机制一个签名证书用于身份认证一个加密证书用于密钥交换。这带来了更高的安全性但也给集成带来了新的挑战。我们将基于铜锁Tongsuo这个开源密码库来实现它铜锁是OpenSSL的一个重要分支对国密算法和TLCP协议提供了原生且成熟的支持。所以这篇文章适合谁正在为项目做国密改造的架构师、需要对PostgreSQL进行深度安全定制的运维或开发工程师或者任何对国密算法实际落地感兴趣的技术爱好者。我会带你从原理到实操一步步走通整个流程过程中踩过的坑、需要注意的细节都会毫无保留地分享出来。2. 核心原理拆解TLS vs. TLCP单证书 vs. 双证书在动手之前我们必须把地基打牢。理解TLS和TLCP的根本性差异是后续一切操作不出错的前提。2.1 TLS协议的工作机制与局限我们熟悉的HTTPS底层用的就是TLS协议。在TLS 1.2中一个典型的RSA密钥交换流程是这样的客户端Hello客户端告诉服务器它支持的密码套件比如TLS_RSA_WITH_AES_128_GCM_SHA256。服务器响应与证书服务器选一个套件并把它的RSA证书发给客户端。这个证书既包含了服务器的公钥也由CA签名证明了身份。密钥交换客户端验证证书后生成一个预主密钥用证书里的RSA公钥加密发送给服务器。密钥派生双方用这个预主密钥派生出会话所需的对称加密密钥。看出问题了吗身份认证签名和密钥交换加密的功能都压在了同一对RSA密钥和同一张证书上。这被称为“静态RSA”密钥交换。虽然后续的TLS 1.3和ECDHE模式做了改进但核心证书体系仍是一张证书承载多个用途。2.2 TLCP协议的双证书机制精讲国密TLCP协议的设计哲学不同它明确地将两种功能分离签名证书用于身份认证。证明“你是你”。它的私钥用于生成签名如SM2-with-SM3签名公钥放在证书里供对方验签。签名私钥通常不出卡存储在更安全的硬件介质中。加密证书用于密钥交换。它的公钥用来加密客户端生成的临时密钥材料在TLCP中通常是客户端生成的临时SM2公钥或预主密钥私钥用于解密。加密私钥可以放在服务器软件能访问的地方。在TLCP握手比如基于SM2的密钥交换中客户端发送ClientHello表明支持TLCP和SM2系列密码套件。服务器发送它的双证书先是签名证书然后是加密证书。客户端验证签名证书链确认服务器身份。客户端生成一个临时的SM2密钥对将其中的临时公钥用服务器的加密证书中的SM2公钥进行加密发送给服务器。服务器用加密私钥解密得到客户端的临时公钥。随后双方利用彼此的SM2密钥对和交换的临时公钥通过SM2密钥交换算法协商出共享的主密钥。这种分离的好处显而易见安全性提升即使加密私钥因为长期使用或存储风险而泄露攻击者也无法冒充服务器因为签名私钥是独立的且保护得更好。合规性完全遵循国密标准GM/T 0024-2014《SSL VPN技术规范》的要求。灵活性两套密钥可以独立更新、管理。比如加密证书可以频繁更换以应对密钥泄露风险而签名证书相对稳定。2.3 铜锁Tongsuo的角色与优势为什么选铜锁因为它让这件事变得可行。OpenSSL虽然强大但对国密算法和TLCP的原生支持一直不够“丝滑”。铜锁作为源于OpenSSL并深度融合国密生态的分支提供了关键支持完整的国密算法引擎原生实现了SM2、SM3、SM4算法性能经过优化。TLCP协议栈实现了完整的TLCP 1.0/1.1协议提供了对应的API和命令行工具。与OpenSSL兼容的API这对于集成到PostgreSQL这类使用OpenSSL接口的软件至关重要。在很多情况下你只需要将链接的库从libssl换成libtongsuo并启用相应的编译选项。成熟的生态工具提供了tongsuo命令行工具可以方便地生成国密证书、进行TLCP握手测试等替代了openssl命令的国密功能。3. 环境准备与铜锁国密库部署理论清楚了我们开始动手。第一步是把环境搭建好核心是编译安装支持国密和TLCP的铜锁库。3.1 系统环境与依赖检查我是在一台CentOS 7.9的机器上操作的理论上Ubuntu、Debian等主流Linux发行版步骤类似。首先安装基础编译工具yum groupinstall -y Development Tools yum install -y wget perl perl-IPC-Cmd注意编译铜锁和PostgreSQL都需要比较新的perl版本系统自带的通常够用但如果遇到奇怪错误可以检查一下。3.2 编译安装铜锁Tongsuo我们不使用包管理器安装因为需要确保启用特定的国密和TLCP编译选项。下载源码访问铜锁的GitHub仓库下载最新稳定版。这里以8.3.0版本为例。wget https://github.com/Tongsuo-Project/Tongsuo/archive/refs/tags/8.3.0.tar.gz -O tongsuo-8.3.0.tar.gz tar -zxvf tongsuo-8.3.0.tar.gz cd Tongsuo-8.3.0配置编译选项这是关键步骤。enable-sm2、enable-sm3、enable-sm4是启用国密算法支持。enable-ntls是启用国密传输层安全协议即TLCP。--prefix指定安装目录。./config --prefix/usr/local/tongsuo enable-sm2 enable-sm3 enable-sm4 enable-ntls -Wl,-rpath,/usr/local/tongsuo/lib-Wl,-rpath,...参数是为了让编译出的库自动记住运行时查找路径避免后续运行程序时找不到libtongsuo.so的问题。这是个实用技巧。编译与安装make -j$(nproc) # 使用多核编译加速 make install验证安装/usr/local/tongsuo/bin/tongsuo version输出应该显示Tongsuo以及版本信息。再测试一下国密算法和TLCP支持/usr/local/tongsuo/bin/tongsuo ciphers -v | grep -i sm2 /usr/local/tongsuo/bin/tongsuo ciphers -v | grep -i ECC-SM2如果能看到ECC-SM2-SM4-CBC-SM3、ECC-SM2-SM4-GCM-SM3等TLCP密码套件说明编译成功。3.3 为系统链接铜锁库为了让PostgreSQL在编译时能找到铜锁我们需要将铜锁库添加到系统链接路径。echo /usr/local/tongsuo/lib /etc/ld.so.conf.d/tongsuo.conf ldconfig现在你可以用ldconfig -p | grep tongsuo检查是否成功。4. 生成国密双证书体系TLCP依赖双证书所以我们需要创建一套完整的证书链根CA、签名证书、加密证书。这里我们用铜锁的命令行工具来生成。4.1 生成SM2算法的根CA证书首先创建一个工作目录并进入。mkdir -p ~/tlcp_certs cd ~/tlcp_certs生成根CA的私钥和自签名证书。注意国密算法使用-sm2指定椭圆曲线参数。/usr/local/tongsuo/bin/tongsuo ecparam -genkey -name sm2 -out ca.key /usr/local/tongsuo/bin/tongsuo req -new -x509 -days 3650 -key ca.key -out ca.crt -subj /CCN/STBeijing/LBeijing/OMyOrg/CNMy TLCP Root CA -sm3ecparam -genkey -name sm2: 生成SM2算法的私钥。req -new -x509 ... -sm3: 生成一个自签名的X509证书使用SM3作为哈希算法。-subj参数设置了证书主题信息请根据实际情况修改。4.2 生成服务器签名证书签名证书用于身份认证。生成签名私钥和证书请求/usr/local/tongsuo/bin/tongsuo ecparam -genkey -name sm2 -out sign.key /usr/local/tongsuo/bin/tongsuo req -new -sm3 -key sign.key -out sign.csr -subj /CCN/STBeijing/LBeijing/OMyOrg/CNpostgresql-server.local这里的CN必须设置为PostgreSQL服务器的主机名或IP地址客户端会用它来验证。用根CA签发签名证书 需要创建一个扩展配置文件sign.ext定义证书用途。cat sign.ext EOF basicConstraints CA:FALSE keyUsage digitalSignature, nonRepudiation extendedKeyUsage serverAuth subjectAltName DNS:postgresql-server.local, IP:192.168.1.100 EOFkeyUsage digitalSignature, nonRepudiation: 明确此证书仅用于数字签名。subjectAltName: 主题备用名称非常重要必须包含客户端连接时使用的服务器地址DNS名或IP。如果这里不匹配证书验证会失败。签发证书/usr/local/tongsuo/bin/tongsuo x509 -req -days 365 -in sign.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out sign.crt -extfile sign.ext -sm34.3 生成服务器加密证书加密证书用于密钥交换。生成加密私钥和证书请求/usr/local/tongsuo/bin/tongsuo ecparam -genkey -name sm2 -out enc.key /usr/local/tongsuo/bin/tongsuo req -new -sm3 -key enc.key -out enc.csr -subj /CCN/STBeijing/LBeijing/OMyOrg/CNpostgresql-server.localCN可以和签名证书一样也可以不同但通常设为一致方便管理。用根CA签发加密证书 创建加密证书的扩展配置文件enc.ext。cat enc.ext EOF basicConstraints CA:FALSE keyUsage keyEncipherment, dataEncipherment, keyAgreement EOFkeyUsage keyEncipherment, dataEncipherment, keyAgreement: 明确此证书用于密钥加密、数据加密和密钥协商。签发证书/usr/local/tongsuo/bin/tongsuo x509 -req -days 365 -in enc.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out enc.crt -extfile enc.ext -sm34.4 证书文件整理与权限设置现在~/tlcp_certs目录下应该有ca.crt- 根证书sign.key,sign.crt- 服务器签名证书及私钥enc.key,enc.crt- 服务器加密证书及私钥关键的安全步骤将私钥文件的权限设置为仅所有者可读。chmod 600 sign.key enc.key ca.key将ca.key移走或妥善保管生产环境中不应放在服务器上。服务器只需要ca.crt、sign.key、sign.crt、enc.key、enc.crt。5. 编译支持国密TLCP的PostgreSQLPostgreSQL默认使用OpenSSL进行SSL/TLS通信。我们需要重新编译它使其链接到我们刚安装的铜锁库。5.1 下载与配置PostgreSQL这里以PostgreSQL 15为例。wget https://ftp.postgresql.org/pub/source/v15.0/postgresql-15.0.tar.gz tar -zxvf postgresql-15.0.tar.gz cd postgresql-15.0配置是关键。我们需要指定使用铜锁的头文件和库文件路径并确保启用SSL支持。./configure --prefix/usr/local/pgsql_tlcp \ --with-openssl \ --with-includes/usr/local/tongsuo/include \ --with-libraries/usr/local/tongsuo/lib \ --with-ldflags-Wl,-rpath,/usr/local/tongsuo/lib--with-openssl: 启用SSL支持。PostgreSQL的配置脚本仍然叫openssl但实际上它会去查找libssl和libcrypto。因为我们用铜锁兼容了这些库所以这里仍然启用。--with-includes和--with-libraries: 指向铜锁的安装目录确保编译时能找到正确的头文件和库。--with-ldflags: 再次传递rpath确保PostgreSQL二进制文件运行时能链接到铜锁库。检查配置结果运行configure后仔细查看输出确认是否有错误。你应该能看到类似checking for SSL_library_init in -lssl... yes和checking for CRYPTO_new_ex_data in -lcrypto... yes的行并且路径指向/usr/local/tongsuo。5.2 编译与安装make -j$(nproc) make install5.3 初始化数据库并配置TLCP创建数据目录和初始化/usr/local/pgsql_tlcp/bin/initdb -D /var/lib/pgsql_tlcp_data准备证书文件将之前生成的服务器证书和私钥复制到数据库数据目录下比如一个专门的certs子目录。mkdir -p /var/lib/pgsql_tlcp_data/certs cp ~/tlcp_certs/ca.crt ~/tlcp_certs/sign.crt ~/tlcp_certs/sign.key ~/tlcp_certs/enc.crt ~/tlcp_certs/enc.key /var/lib/pgsql_tlcp_data/certs/ chown -R postgres:postgres /var/lib/pgsql_tlcp_data/certs chmod 600 /var/lib/pgsql_tlcp_data/certs/*.key实操心得证书和私钥的路径和权限是SSL/TLS配置中最常见的错误来源。一定要确保运行PostgreSQL的系统用户如postgres有权限读取证书文件.crt并且只有它能读取私钥文件.key。配置postgresql.conf编辑数据目录下的postgresql.conf文件。vi /var/lib/pgsql_tlcp_data/postgresql.conf修改或添加以下关键参数listen_addresses * # 监听所有IP或指定服务器IP port 5432 # 默认端口 ssl on # 启用SSL/TLS ssl_cert_file certs/sign.crt # 签名证书PostgreSQL目前主要用这个 ssl_key_file certs/sign.key # 签名私钥 # 注意PostgreSQL原生配置项不支持直接指定双证书。 # 我们需要通过铜锁的特定配置或环境变量来启用TLCP和指定加密证书。这里遇到了第一个核心挑战PostgreSQL的postgresql.conf配置项是面向标准OpenSSL TLS设计的没有直接设置第二张加密证书的参数。6. 核心挑战与解决方案让PostgreSQL识别双证书PostgreSQL的SSL模块在初始化时会调用OpenSSL的SSL_CTX_use_certificate_file和SSL_CTX_use_PrivateKey_file加载证书和私钥。对于TLCP我们需要在SSL上下文SSL_CTX中加载两张证书。6.1 方案一使用铜锁的SSL_CTX接口需修改PostgreSQL源码这是最彻底但最复杂的方式。需要修改PostgreSQL后端源码src/backend/libpq/be-secure-openssl.c在初始化SSL上下文的地方添加加载加密证书的逻辑。大致步骤在加载完签名证书后获取SSL_CTX指针。调用铜锁特有的API例如SSL_CTX_use_enc_certificate_file(ctx, certs/enc.crt)和SSL_CTX_use_enc_PrivateKey_file(ctx, certs/enc.key)。重新编译PostgreSQL。这种方式需要对PostgreSQL源码和铜锁API有较深理解且升级PostgreSQL版本时需要重新打补丁维护成本高。6.2 方案二利用铜锁的配置文件与环境变量推荐铜锁库在运行时可以通过环境变量OPENSSL_CONF或TONGSUO_CONF指定一个配置文件。在这个配置文件里我们可以详细定义TLCP相关的行为包括指定双证书。这是侵入性最小、最推荐的方法。创建铜锁配置文件在数据库数据目录下创建tongsuo.cnf。vi /var/lib/pgsql_tlcp_data/tongsuo.cnf内容如下openssl_conf openssl_init [openssl_init] ssl_conf ssl_section [ssl_section] system_default system_default_section [system_default_section] # 启用国密算法和TLCP协议 CipherString ECC-SM2-SM4-CBC-SM3 # 指定签名证书和私钥与postgresql.conf中一致 SignatureCert certs/sign.crt SignatureKey certs/sign.key # 指定加密证书和私钥 EncryptCert certs/enc.crt EncryptKey certs/enc.key # 协议版本偏好 MinProtocol NTLS MaxProtocol NTLSCipherString这里指定了TLCP的密码套件。ECC-SM2-SM4-CBC-SM3是一个典型的TLCP 1.1套件。SignatureCert/Key和EncryptCert/Key正是通过这两个参数铜锁在创建SSL上下文时会自动加载双证书。MinProtocol和MaxProtocol都设为NTLS强制使用国密TLCP协议。通过环境变量启动PostgreSQL修改PostgreSQL的服务启动脚本如systemd的service文件或者在命令行启动时添加环境变量。对于systemd服务/etc/systemd/system/postgresql_tlcp.service[Service] ... EnvironmentTONGSUO_CONF/var/lib/pgsql_tlcp_data/tongsuo.cnf # 或者使用 OPENSSL_CONF铜锁会识别 # EnvironmentOPENSSL_CONF/var/lib/pgsql_tlcp_data/tongsuo.cnf ...手动启动export TONGSUO_CONF/var/lib/pgsql_tlcp_data/tongsuo.cnf /usr/local/pgsql_tlcp/bin/postgres -D /var/lib/pgsql_tlcp_data这个方案的原理是PostgreSQL在初始化OpenSSL实为铜锁库时会读取这个配置文件。铜锁库在解析到SignatureCert和EncryptCert等配置时会自动为SSL_CTX加载双证书无需修改PostgreSQL一行代码。6.3 启动数据库并验证TLCP监听使用配置了环境变量的方式启动PostgreSQL服务。systemctl daemon-reload systemctl start postgresql_tlcp systemctl status postgresql_tlcp使用netstat或ss命令检查端口监听情况并确认SSL已启用/usr/local/pgsql_tlcp/bin/psql -h localhost -p 5432 -U postgres -c SELECT version(); SHOW ssl; postgres如果SHOW ssl;返回on说明基础SSL已开启。但要验证是否是TLCP需要用专门的客户端工具。7. 客户端连接测试与深度验证服务器端配置好了客户端也需要相应的支持才能建立TLCP连接。7.1 使用铜锁s_client工具测试这是最直接的验证方法。铜锁的tongsuo s_client命令可以模拟一个TLCP客户端。/usr/local/tongsuo/bin/tongsuo s_client -connect localhost:5432 -cipher ECC-SM2-SM4-CBC-SM3 -quiet -no_ign_eof-cipher ECC-SM2-SM4-CBC-SM3指定TLCP密码套件。-quiet减少输出。-no_ign_eof连接后不忽略EOF方便测试数据库协议。如果连接成功你会看到铜锁输出的握手信息其中包含“Protocol : NTLS”和“Cipher : ECC-SM2-SM4-CBC-SM3”等字样。这证明TLCP握手成功。之后你可以手动输入一些PostgreSQL的启动报文如SSL请求进行更深度的协议测试但通常看到NTLS和国密套件就说明服务器端TLCP服务已经正常提供了。7.2 编译支持国密的PostgreSQL客户端libpq要让真正的PostgreSQL客户端如psql、JDBC、libpq支持TLCP客户端也必须链接铜锁库并支持国密套件。环境变量法临时如果你只是临时测试可以在运行psql前设置环境变量强制其使用铜锁的配置和密码套件。export TONGSUO_CONF/path/to/client_tongsuo.cnf export PGSSLCIPHERECC-SM2-SM4-CBC-SM3 /usr/local/pgsql_tlcp/bin/psql -h server_ip -p 5432 sslmodeverify-full sslrootcert/path/to/ca.crt -U postgres postgres你需要创建一个客户端的tongsuo.cnf至少指定CipherString。PGSSLCIPHER环境变量可以指定客户端优先使用的密码套件。编译法永久和编译服务器一样重新编译libpq客户端库使其链接铜锁。这样任何基于该libpq的客户端如psql、pg_dump都将具备TLCP能力。cd postgresql-15.0 ./configure --prefix/usr/local/pgsql_tlcp_client --with-openssl --with-includes/usr/local/tongsuo/include --with-libraries/usr/local/tongsuo/lib --with-ldflags-Wl,-rpath,/usr/local/tongsuo/lib cd src/interfaces/libpq make make install然后使用新编译的psql进行连接。7.3 连接字符串参数详解在客户端连接时SSL相关参数至关重要sslmodeverify-full最严格的模式。客户端验证服务器证书并检查主机名是否匹配。生产环境推荐使用此模式。sslrootcert/path/to/ca.crt指定受信任的根CA证书路径。客户端用它来验证服务器发送的签名证书。sslcert/sslkey如果服务器要求客户端证书认证双向TLS/TLCP则需要指定客户端的证书和私钥。国密场景下同样需要双证书配置更为复杂本文暂不展开。一个完整的psql连接示例/usr/local/pgsql_tlcp_client/bin/psql host192.168.1.100 port5432 userpostgres dbnamepostgres sslmodeverify-full sslrootcert/home/user/ca.crt8. 常见问题、性能调优与监控在实际部署中你肯定会遇到各种问题。这里记录一些典型场景和解决思路。8.1 连接失败问题排查清单“SSL SYSCALL error: EOF detected” 或 “SSL connection has been closed unexpectedly”最常见原因密码套件不匹配。服务器配置了TLCP套件但客户端尝试使用普通的TLS套件如TLS_AES_256_GCM_SHA384连接。排查在服务器tongsuo.cnf中确认CipherString。在客户端连接时通过PGSSLCIPHER环境变量或libpq参数强制指定国密套件。使用tongsuo s_client测试是否能握手。“certificate verify failed”原因证书验证失败。排查步骤检查客户端sslrootcert指定的CA证书是否正确是否与签发服务器证书的CA一致。检查服务器证书是否过期tongsuo x509 -in sign.crt -noout -dates。检查服务器证书的subjectAltName是否包含了客户端连接时使用的主机名或IP地址。这是最容易忽略的一点。检查证书链是否完整。我们的例子是自签名根CA直接签服务器证书所以只需要根CA证书。如果是多级CA可能需要提供完整的证书链文件。“private key does not match certificate”原因配置文件tongsuo.cnf中SignatureKey指定的私钥与SignatureCert证书不匹配。同样检查EncryptKey和EncryptCert。排查使用命令验证tongsuo pkey -in sign.key -pubout -outform PEM | openssl x509 -in sign.crt -pubkey -noout | diff。两者输出应该相同。“Permission denied” 读取证书或私钥文件原因PostgreSQL进程用户没有读取文件的权限。解决确保证书文件.crt对运行用户可读私钥文件.key仅对运行用户可读chmod 600。8.2 性能考量与简易压测国密SM2算法是非对称加密其计算开销比RSA 2048略高但比RSA 4096低。在TLCP握手阶段由于涉及双证书验证和SM2密钥交换首次连接建立时间会比普通TLS略长。但连接建立后的对称加密SM4性能与AES相当。简易性能观察方法使用普通TLS国际算法连接通过pgbench进行测试记录TPS。使用TLCP连接同样进行pgbench测试。对比两者在连接建立密集型场景短连接和长连接场景下的差异。通常在长连接为主的数据库应用如连接池中握手开销被分摊性能差异微乎其微。在短连接频繁的场景下可能需要关注连接池的配置或考虑会话复用TLCP也支持会话票证。8.3 监控与日志PostgreSQL日志在postgresql.conf中设置log_connections on和log_disconnections on可以查看连接建立和断开的情况。如果SSL握手失败这里通常会有相关错误记录。铜锁/OpenSSL日志可以通过环境变量OPENSSL_DEBUG或TONGSUO_DEBUG开启铜锁的调试日志但这会输出大量信息仅建议在排查极端问题时使用。系统级监控使用ss -tlnp查看5432端口的监听状态。使用netstat -s | grep -i tcp查看TCP层面的错误统计。8.4 关于双向认证mTLS本文主要配置了服务器端认证。更高级的安全场景可能需要双向认证客户端也向服务器出示证书。在TLCP中这意味着客户端也需要准备签名和加密双证书并在连接时提供。服务器端需要在tongsuo.cnf中配置VerifyCAFile或VerifyMode等参数并在pg_hba.conf中设置clientcertverify-full。由于配置复杂度成倍增加建议在单向认证稳定后再尝试。整个流程走下来从原理理解、环境搭建、证书生成、服务配置到问题排查每一个环节都需要仔细。国密改造不是简单的“替换算法”而是涉及协议、证书体系、客户端兼容性等一系列变化的系统工程。基于铜锁和配置文件的方案最大程度地降低了对PostgreSQL本身的侵入性为在生产环境落地提供了可行的路径。最关键的是一定要在测试环境充分验证准备好回滚方案毕竟数据库的稳定和安全永远是第一位的。