把文件加密之后还能直接做加法、乘法甚至更复杂的统计运算,算完的结果解密出来和先解密再计算完全一致——这就是同态加密(Homomorphic Encryption)最吸引人的地方。对于需要在不可信环境(比如公有云服务器)里处理敏感文件的C#开发者来说,这项技术可以在不暴露原始数据的前提下完成服务端的计算任务。本文将围绕C#环境下的具体实现方式展开,重点介绍微软官方开源的Microsoft SEAL库,并用完整代码演示如何对加密后的文件内容进行求和与统计。

同态加密的基本原理与三种类型
理解同态加密的关键在于一个数学性质:在明文空间定义的运算,经过加密后依然成立。举个最简单的例子,RSA算法天然支持乘法同态——对两个密文相乘再解密,结果等于两个明文相乘。这种性质使得服务器可以在看不到明文的情况下替用户完成计算。
按照支持运算的程度,同态加密通常分为三类。第一种是部分同态加密(PHE),只支持一种运算,比如Paillier支持加法、RSA支持乘法。第二种是些许同态加密(SWHE),支持有限次数的加法和乘法组合,运算次数一多噪声就会超出上限导致解密失败。第三种是全同态加密(FHE),理论上支持任意深度的电路运算,代表方案有BFV、BGV、CKKS等,其核心思路是同态乘法会累积噪声,当噪声逼近阈值时通过“重线性化”和“自举”技术刷新密文,让计算可以无限进行下去。
对C#开发者来说,直接从零实现这些方案门槛很高,好消息是微软开源的SEAL(Simple Encrypted Arithmetic Library)提供了成熟的.NET封装,支持BFV和CKKS两种方案,性能经过大量优化,是目前.NET生态下做同态加密计算的首选。
使用Microsoft SEAL在C#中对文件内容加密并计算
SEAL库可以通过NuGet直接安装,包名为Microsoft.Research.SEALNet。BFV方案适合精确整数运算,比如统计文件中的数值总和、计数等场景。下面演示一个完整流程:读取文件中的整数数据,逐个加密成密文,然后在密文状态下求和,最后解密得到结果。整个过程服务器端接触到的只有密文。
安装命令与初始化代码如下:
Install-Package Microsoft.Research.SEALNet using Microsoft.Research.SEALNet; // 初始化加密参数 var parms = new EncryptionParameters(SchemeType.BFV); // 多项式模度,决定安全性与性能 parms.PolyModulusDegree = 4096; // 明文系数模数 parms.PlainModulus = PlainModulus.Batching(4096, 20); var context = new SEALContext(parms); // 生成密钥 var keygen = new KeyGenerator(context); keygen.CreatePublicKey(out PublicKey publicKey); var secretKey = keygen.SecretKey; var encryptor = new Encryptor(context, publicKey); var evaluator = new Evaluator(context); var decryptor = new Decryptor(context, secretKey);
假设文件中每行是一个整数,读取后加密并求和的核心逻辑如下:
// 读取文件内容
string[] lines = File.ReadAllLines(@"C:\data\numbers.txt");
long[] numbers = lines.Select(long.Parse).ToArray();
// 将明文打包进一个多项式(batching技术,一次加密多个值)
var plain = new Plaintext();
var encoder = new BatchEncoder(context);
encoder.Encode(numbers, plain);
// 加密
var encrypted = new Ciphertext();
encryptor.Encrypt(plain, encrypted);
// 在密文上执行同态求和
// negate后再用rotate_sum类思路,这里演示简单的同态加法叠加
var encryptedCopy = new Ciphertext();
evaluator.Negate(encrypted, encryptedCopy);
evaluator.Add(encrypted, encryptedCopy, encryptedCopy); // 结果为全零,验证同态加法生效
// 实际求和:利用旋转向量累加(需要Galois密钥)
keygen.CreateGaloisKeys(out GaloisKeys galoisKeys);
var sumCipher = new Ciphertext(encrypted);
for (int i = 1; i < 4096; i *= 2)
{
var rotated = new Ciphertext();
evaluator.RotateVector(sumCipher, i, galoisKeys, rotated);
evaluator.AddInplace(sumCipher, rotated);
}
// 解密查看结果
var resultPlain = new Plaintext();
decryptor.Decrypt(sumCipher, resultPlain);
var result = new List<ulong>();
encoder.Decode(resultPlain, result);
Console.WriteLine($"文件数值总和(第一个槽位):{result[0]}");这段代码有几个值得注意的点。BatchEncoder把数组打包进一个密文的多个“槽位”,一次加密可以携带上千个数值,大幅摊薄加密开销。RotateVector配合Galois密钥实现了槽位之间的旋转叠加,循环结束后所有槽位的值都会集中到第一个槽位,等价于对整个数组求和。如果只是对两个密文相加,直接调用evaluator.Add即可,运算结果解密后就是两个明文数组的逐元素和。
CKKS方案处理浮点数据与文件统计分析
文件里存放的往往不是整数,而是温度、金额这类浮点数。BFV只能处理精确整数,此时应换用CKKS方案。CKKS采用近似计算,把浮点数编码到多项式系数中,适合做均值、方差等统计分析,结果会有极小的精度损失,通常在1e-6量级,对统计场景完全够用。
CKKS的使用方式与BFV类似,区别在于编码器换成CKKSEncoder,并且需要在加密前设定缩放系数(scale)。示例如下:
var ckksParms = new EncryptionParameters(SchemeType.CKKS);
ckksParms.PolyModulusDegree = 8192;
ckksParms.CoeffModulus = CoeffModulus.Create(8192,
new int[] { 40, 40, 40, 40, 40 });
var ckksContext = new SEALContext(ckksParms);
var ckksKeygen = new KeyGenerator(ckksContext);
ckksKeygen.CreatePublicKey(out PublicKey pk);
var ckksEncryptor = new Encryptor(ckksContext, pk);
var ckksEvaluator = new Evaluator(ckksContext);
var ckksDecryptor = new Decryptor(ckksContext, ckksKeygen.SecretKey);
var ckksEncoder = new CKKSEncoder(ckksContext);
double scale = Math.Pow(2.0, 40);
// 假设文件里是浮点数,计算均值
string[] lines = File.ReadAllLines(@"C:\data\scores.txt");
double[] scores = lines.Select(double.Parse).ToArray();
var plainVec = new Plaintext();
ckksEncoder.Encode(scores, scale, plainVec);
var cipherVec = new Ciphertext();
ckksEncryptor.Encrypt(plainVec, cipherVec);
// 密文乘以 1/n 求均值(先加密常数再同态乘)
var plainInv = new Plaintext();
ckksEncoder.Encode(1.0 / scores.Length, scale, plainInv);
var meanCipher = new Ciphertext();
ckksEvaluator.Multiply(cipherVec, plainInv, meanCipher);
ckksEvaluator.RelinearizeInplace(meanCipher, new RelinKeys());
// 解密第一个槽位即均值
var meanPlain = new Plaintext();
ckksDecryptor.Decrypt(meanCipher, meanPlain);
var output = new List<double>();
ckksEncoder.Decode(meanPlain, output);
Console.WriteLine($"密文域计算得到的均值:{output[0]}");注意同态乘法之后必须调用RelinearizeInplace做重线性化,否则密文尺寸会膨胀,后续运算会越来越慢甚至报错。另外CKKS中多次乘法会累积缩放误差,深度较大的计算链需要手动调用RescaleToNext控制scale,这是CKKS实践中最容易踩的坑。
性能开销、安全性与适用场景分析
同态加密的代价十分明显。以PolyModulusDegree等于4096为例,单次加密大约耗时几毫秒,一次同态乘法可能达到十几毫秒,相比明文运算慢了四到五个数量级。密文体积也会膨胀数百倍,一个几MB的数据文件加密后可能占用数百MB内存。因此实际项目中要充分利用batching技术批量处理,并只把真正敏感的计算环节放到密文域,其余逻辑留在明文侧。
参数选择直接决定安全强度。PolyModulusDegree越大安全性越高但越慢,一般4096是入门下限,处理金融级别数据建议8192起步。SEAL会自动校验参数组合是否达到128位安全级别,SEALContext创建后可以通过KeyGenInfo相关属性检查。切记不要为了速度随意缩小参数,否则可能被已知攻击方法破解。
最后说清适用边界。如果你只是想在本地加密文件、用到时再解密,那么AES这类传统对称加密效率高得多,没必要上同态加密。它的真正价值在于:数据拥有方不信任计算环境,比如把敏感文件交给云服务器统计、让第三方对加密病历做分析、多方联合计算互不泄露各自数据。只要场景中存在“必须计算但对方不可信”这个矛盾,同态加密就值得考虑,而在C#生态里,SEAL库已经把实现门槛降到了普通业务开发者可以接受的水平。