如何在.NET中实现与PostgreSQL pgp_sym_encrypt兼容的加密功能?
如何在.NET中实现与PostgreSQL pgp_sym_encrypt兼容的加密功能?
我之前刚好踩过这个坑,折腾了好一阵才摸透门道——其实pgp_sym_encrypt本质是遵循OpenPGP标准的对称加密实现,不是PostgreSQL自定义的奇怪格式,所以不用自己硬写加密逻辑,找个支持OpenPGP的.NET库就能搞定,最靠谱的就是BouncyCastle。下面一步步给你讲怎么实现:
1. 先搞定依赖
首先在你的.NET项目里安装BouncyCastle的NuGet包,用CLI或者NuGet包管理器都可以:
# .NET CLI 安装命令 dotnet add package BouncyCastle.Cryptography
或者在Visual Studio里搜索BouncyCastle.Cryptography,装最新稳定版就行。
2. 核心加密实现(完全对齐pgp_sym_encrypt默认行为)
pgp_sym_encrypt的默认参数是:cipher-algo=CAST5、compress-algo=zlib、integrity=true,下面的代码完全匹配这些默认设置,你可以直接用:
using System.IO; using Org.BouncyCastle.Bcpg.OpenPgp; using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities.IO; public static class PgpEncryptionHelper { /// <summary> /// 生成与PostgreSQL pgp_sym_encrypt兼容的加密数据 /// </summary> /// <param name="inputData">要加密的原始字节数据</param> /// <param name="passphrase">加密密码,和PostgreSQL侧保持一致</param> /// <param name="compress">是否启用zlib压缩(对应pgp_sym_encrypt的compress-algo=zlib)</param> /// <param name="cipherAlgorithm">加密算法(默认CAST5,对应pgp_sym_encrypt默认值)</param> /// <returns>加密后的字节数组,转Base64后可直接在PostgreSQL中解密</returns> public static byte[] EncryptPgpSymmetric(byte[] inputData, string passphrase, bool compress = true, string cipherAlgorithm = "CAST5") { // 1. 从密码生成OpenPGP兼容的对称密钥(用默认的S2K算法) var cipher = PgpUtilities.GetSymmetricKeyCipher(cipherAlgorithm); var s2kGenerator = new PgpS2kGenerator(PgpS2k.Sha1, 0x60, new SecureRandom()); var keyBytes = PgpUtilities.MakeKeyFromPassphrase(cipher, s2kGenerator, passphrase.ToCharArray()); // 2. 处理压缩(默认开启zlib,和pgp_sym_encrypt默认行为对齐) using var memoryStream = new MemoryStream(); Stream outputStream = compress ? new PgpZlibCompressedGenerator().Open(memoryStream) : memoryStream; // 3. 初始化加密器,启用完整性保护(对应pgp_sym_encrypt的integrity=true) var encryptedDataGenerator = new PgpEncryptedDataGenerator(cipher, true, new SecureRandom()); encryptedDataGenerator.AddMethod(keyBytes); // 4. 执行加密操作 using var encryptedStream = encryptedDataGenerator.Open(outputStream, new byte[4096]); encryptedStream.Write(inputData, 0, inputData.Length); encryptedStream.Close(); if (compress) { outputStream.Close(); } return memoryStream.ToArray(); } // 可选:如果需要解密PostgreSQL生成的加密数据,可添加这个方法 public static byte[] DecryptPgpSymmetric(byte[] encryptedData, string passphrase) { using var inputStream = new MemoryStream(encryptedData); var pgpObjectFactory = new PgpObjectFactory(PgpUtilities.GetDecoderStream(inputStream)); var encryptedDataList = (PgpEncryptedDataList)pgpObjectFactory.NextPgpObject(); // 找到对应的对称加密包 PgpSymmetricEncryptedData encryptedEntry = null; foreach (var obj in encryptedDataList.GetEncryptedDataObjects()) { encryptedEntry = (PgpSymmetricEncryptedData)obj; break; } // 用密码解密密钥,再解密原始数据 var keyBytes = PgpUtilities.MakeKeyFromPassphrase(encryptedEntry.KeyAlgorithm, passphrase.ToCharArray()); var clearStream = encryptedEntry.GetDataStream(keyBytes); // 处理压缩(如果PostgreSQL加密时启用了压缩) var compressedStream = PgpUtilities.GetDecoderStream(clearStream); var literalFactory = new PgpObjectFactory(compressedStream); var literalData = (PgpLiteralData)literalFactory.NextPgpObject(); using var outputStream = new MemoryStream(); Streams.PipeAll(literalData.GetInputStream(), outputStream); return outputStream.ToArray(); } }
3. 兼容性验证
你可以按下面的方式确认是否和PostgreSQL兼容:
- .NET加密后验证:把
EncryptPgpSymmetric返回的字节数组转成Base64字符串,在PostgreSQL中执行SELECT pgp_sym_decrypt(decode('你的Base64字符串', 'base64'), '你的密码');,应该能得到原始数据。 - PostgreSQL加密后在.NET解密:用PostgreSQL执行
SELECT encode(pgp_sym_encrypt('测试内容', '你的密码'), 'base64');得到Base64,转成字节数组后传入DecryptPgpSymmetric方法,应该能解密出原始字符串。
4. 关键注意事项(避免踩坑)
- 算法对齐:如果你在PostgreSQL中调用
pgp_sym_encrypt时指定了其他算法(比如cipher-algo=aes256),一定要在.NET代码中把cipherAlgorithm参数改成对应的值(比如"AES-256")。 - 压缩设置:如果PostgreSQL用了
compress-algo=none,那么.NET代码中要把compress参数设为false。 - 字符编码:pgp_sym_encrypt处理字符串时用的是数据库的默认编码,所以在.NET中要确保把字符串转成字节数组时用的编码和PostgreSQL一致(推荐统一用UTF-8)。
- 完整性保护:pgp_sym_encrypt默认开启
integrity=true,代码中对应的是PgpEncryptedDataGenerator的第二个参数设为true,如果关掉的话,PostgreSQL解密时会提示完整性检查失败。
之前你说查文档没找到明确指引,其实是因为pgcrypto没有自定义加密格式,完全遵循OpenPGP的对称加密规范,所以不用自己模拟底层细节,直接用支持OpenPGP的库就行,BouncyCastle已经把所有复杂逻辑都封装好了。
备注:内容来源于stack exchange,提问作者Tom
相关产品推荐
相关产品推荐

