Microsoft SEAL(Simple Encrypted Arithmetic Library)是微软开源的轻量级、高性能全同态加密(FHE)库,专为整数/浮点数的密文运算设计,支持BFV、CKKS、BGV等主流FHE方案,广泛应用于隐私计算、联邦学习、数据加密等场景,源码:https://github.com/microsoft/SEAL。
1 数学基础
1.1 多项式环
SEAL使用的基础环是:

BFV的所有运算都在这个多项式环中进行,符号含义如下:
Zq:系数域,即多项式的所有系数都取自"模q的整数集合",q称为系数模数(一个大素数或多个素数的乘积),决定密文的噪声容忍度和运算深度。
xN+1:多项式模,要求N是2的整数幂(如4096、8192),满足xN≡-1 (mod xN+1),这意味着所有多项式的次数都不会超过N-1(超过的项可通过xN = -1降次)。
Rq元素形式:任意元素是一个次数≤ N - 1的多项式,形如:

1.2 明文空间

明文(待加密的整数)会被编码为Rt中的多项式,t满足t≡1 (mod 2N),这是批量加密的硬性要求,明文在进行加密前要进行编码,有两种编码方式:
单整数编码:将单个整数m编码为常数多项式f(x)=m,即所有高次项系数为0。
批量编码 :将N个小整数[m0,m1,...,mN-1]直接做为多项式的系数,编码为:

1.3 RLWE问题
BFV的安全性基于RLWE问题(Ring Learning With Errors)的计算困难性,简单描述为:
给定多项式Rq,选择一个秘密多项式s(x)∈Rq(系数为0/1的短多项式),以及大量的"噪声多项式对"(ai(x), bi(x)),其中ai(x)是随机生成的,bi(x)=-ai(x)s(x)+ei(x) (mod q)是通过秘密多项式s(x)计算得到的"响应多项式",加入噪声ei(x)是为了让bi(x)看起来像一个完全随机的多项式,从而隐藏s(x)的存在,而在计算上无法从这些多项式对中恢复出秘密多项式s(x)。对于bi(x)其生成时每一部分的作用如下:
ai(x):从多项式环Rq中均匀随机生成的多项式,相当于"公共输入",可以公开。
s(x):秘密多项式(系数仅为0或1),是整个RLWE问题的核心,必须严格保密。
ei(x):小系数噪声多项式(系数仅为-1、0、1),是"隐藏秘密"的关键。
bi(x):由ai(x)s(x)加上噪声得到的结果,与ai(x)一起构成公开的"多项式对"。
如果没有噪声ei(x),即ei(x)=0,那么bi(x)=-ai(x)s(x) (mod q),此时攻击者可以通过多组(ai(x), bi(x))构建线性方程组,直接解密出秘密多项式s(x),这就完全失去了安全性。而加入小噪声ei(x)后:
bi(x)不再是ai(x)s(x)的精确结果,而是一个"近似值";
这个近似值的误差被控制在很小的范围内(由ei(x)的系数大小决定);
从计算角度,目前没有任何算法(包括量子算法)能高效地从这些带噪声的近似结果中恢复出s(x),这正是RLWE问题的"计算困难性"来源,也是BFV适合后量子秘密场景的原因。
1.4 缩放因子
在BFV同态加密方案中,缩放因子(Scaling Factor)是连接明文空间(Zt)和密文空间(Zq)的核心系数,本质是为了让明文多项式能"适配"系数模数q的范围,同时保证解密时可以精确还原明文。BFV的明文模数t远小于系数模数q(t<<q),比如t=65537,q=260量级。
明文多项式m(x)∈Rt的系数范围是[0, t-1],而密文多项式c(x)∈Rq的系数范围是[0, q-1],如果直接将明文m(x)放入密文公式,由于t太小,明文信息会被噪声和掩码完全淹没,无法解密。因此需要一个缩放因子,将明文系数放大到q的量级,再参与密文计算。缩放因子贯穿加密和解密两个核心步骤,是明文和密文的"桥梁"。
(1)加密时:明文放大
在加密步骤中,明文多项式m(x)不会直接代入密文公式,而是先乘于缩放因子Δ,再放入到公式:

作用:将明文系数从[0, t-1]放大到[0, Δ*(t-1)],这个范围在q的量级内,能避免明文被噪声覆盖。
(2)解密时:明文缩小
解密的核心步骤是先计算聚合多项式D(ct),代入加密公式后可得:

此时需要反向缩放来还原明文:

概括来说,缩放因子不会直接参与运算,但会间接影响噪声的增长速度:
-
加法运算:密文加法是系数直接相加,噪声线性叠加,缩放因子不影响噪声增长;
-
乘法运算:密文乘法是多项式乘法,噪声会平方增长,而缩放因子Δ越大,噪声的规模也会越大,导致运算深度降低。
因此,在参数配置时,需要在"明文范围(t大小)"和"运算深度(q大小)"之间做权衡:
若t增大→Δ减小→噪声容忍度提升→运算深度增加;
若t减小→Δ增大→噪声容忍度降低→运算深度减小。
所以t与Δ成反比,需根据业务需求平衡明文范围和运算深度。
2 BFV核心流程
2.1 参数配置
参数配置决定方案的性能与安全性,BFV核心参数有4个,需严格满足数学约束:

参数约束:需满足q>t*(2N)d*B(d是目标运算深度,B是噪声上限),否则运算过程中噪声会"爆炸"导致解密失败。
2.2 密钥生成
基于RLWE问题生成私钥、公钥、重线性化密钥3中密钥,核心是构造含噪声的多项式对。
(1)私钥(sk)
随机生成一个短多项式s(x)∈Rq,系数仅为0或1(如s(x) = 1 + x2 + x5),私钥就是s(x)。
(2)公钥(pk)
随机生成多项式a(x)∈Rq,生成小希数噪声多项式e(x)∈Rq,计算b(x) = -a(x)s(x) + e(x) (mod q),公钥是多项式对pk = (b(x), a(x)),可公开传播。这里的噪声e(x)让攻击者无法从公钥对中恢复私钥s(x),目的是解决"公钥本身的安全性"。
(3)重线性化密钥(rlk)
密文乘法会导致密文从"2项多项式"膨胀为"3项多项式",后续运算效率骤降。重线性化密钥用于将膨胀后的密文压缩回2项,生成逻辑与公钥类似,本质是一组扩展的RLWE多项式对。
2.3 加密
将明文多项式转为密文多项式,BFV的密文是Rq中的2项多项式对ct = (c0(x), c1(x)),加密过程分两步:
(1)明文编码:将整数明文m编码为明文多项式m(x)∈Rt;
(2)添加噪声与混淆:
随机生成两个小噪声多项式e0(x),e1(x)∈Rq,随机生成一个"掩码多项式"u(x)∈Rq(系数为0/1),计算密文:

核心设计:密文中包含明文信息m(x),但被噪声e0/e1和掩码u(x)混淆,只有私钥能去除混淆和噪声。该步骤中的掩码多项式u(x)和噪声多项式e0(x),e1(x)是两套独立的安全机制,它们解决的是完全不同的问题,不能互相替代,如下图:

u(x)通过随机缩放实现公钥和明文间的非固定线性关系,噪声通过"近似"进一步打破它们之间精确的代数关系,使得攻击者无法从近似值中还原精确明文。
2.4 同态运算
这步的核心是:密文运算=多项式环运算,BFV支持秘密&密文(Ct&Ct)和密文&明文(Ct&Pt)的加减乘运算,所有运算都在多项式环Rq中进行,且无需密钥。

运算后的密文仍然是合法的RLWE密文,可继续参与后续运算------这就是「同态性」的体现。
2.5 解密
解密是加密的逆运算,核心是去除噪声、还原明文多项式,步骤如下:
(1)密文聚合
用私钥s(x)计算聚合多项式:

代入加密公式可推导:

(2)噪声去除
由于总噪声etotal<q/(2t),可通过"舍入+模运算"还原明文:
(3)明文解码
将解密后的多项式m(x)转换回整数(单整数取常数项,批量加密取所有系数)。
解密成功条件:总噪声etotal<q/(2t),若运算次数过多导致噪声爆炸,舍入后无法还原明文,则会解密失败------这是BFV"层次性"的本质,运算深度有限。
2.6 python示例
以下是一个完整的python示例程序:


import numpy as np
import random
class PolynomialRing:
def __init__(self, n, modulus):
self.n = n
self.modulus = modulus
self.phi = np.zeros(n + 1, dtype=int)
self.phi[0] = 1
self.phi[n] = 1
def poly_add(self, a, b):
result = np.zeros(self.n, dtype=int)
for i in range(self.n):
result[i] = (a[i] + b[i]) % self.modulus
return result
def poly_mul(self, a, b):
result = np.zeros(2 * self.n - 1, dtype=int)
for i in range(self.n):
for j in range(self.n):
result[i + j] = (result[i + j] + a[i] * b[j]) % self.modulus
return self.poly_mod(result)
def poly_mod(self, poly):
result = poly.copy()
for i in range(len(result) - 1, self.n - 1, -1):
if result[i] != 0:
coeff = result[i]
result[i] = 0
idx = i - self.n
if idx < len(result):
result[idx] = (result[idx] - coeff) % self.modulus
result = result[:self.n]
return result
def poly_sub(self, a, b):
result = np.zeros(self.n, dtype=int)
for i in range(self.n):
result[i] = (a[i] - b[i]) % self.modulus
return result
def poly_scale(self, a, scalar):
result = np.zeros(self.n, dtype=int)
for i in range(self.n):
result[i] = (a[i] * scalar) % self.modulus
return result
def random_poly(self):
return np.random.randint(0, self.modulus, self.n)
def binary_poly(self):
return np.random.randint(0, 2, self.n)
def small_poly(self, bound=3):
return np.random.randint(-bound, bound + 1, self.n)
class BFV:
def __init__(self, n=256, q=1048576, t=256):
self.n = n
self.q = q
self.t = t
self.ring = PolynomialRing(n, q)
self.plaintext_ring = PolynomialRing(n, t)
self.delta = q // t
def keygen(self):
# 产生密钥
s = self.ring.binary_poly()
# 挑战多项式
a = self.ring.random_poly()
# 随机多项式
e = self.ring.small_poly()
a_s = self.ring.poly_mul(a, s)
# 响应多项式
pk0 = self.ring.poly_sub(e, a_s)
# 公钥
pk = [pk0, a]
sk = s
return pk, sk
# 编码明文
def encode(self, message):
if isinstance(message, int):
# 单整数编码
m = np.zeros(self.n, dtype=int)
m[0] = message % self.t
else:
# 批量编码
m = np.array(message, dtype=int) % self.t
return m
# 使用公钥加密
def encrypt(self, pk, message):
# 对明文进行编码
m = self.encode(message)
# 对明文编码结果进行放大
m_scaled = self.ring.poly_scale(m, self.delta)
#print("m_scaled: {}".format(m_scaled))
# 生成掩码多项式
u = self.ring.binary_poly()
# 产生两个小噪声多项式
e1 = self.ring.small_poly()
e2 = self.ring.small_poly()
pk0_u = self.ring.poly_mul(pk[0], u)
pk1_u = self.ring.poly_mul(pk[1], u)
# 生成密文c0
c0 = self.ring.poly_add(pk0_u, e1)
c0 = self.ring.poly_add(c0, m_scaled)
# 生成密文c1
c1 = self.ring.poly_add(pk1_u, e2)
# 返回密文
return [c0, c1]
def decrypt(self, sk, ciphertext):
c0, c1 = ciphertext
s_c1 = self.ring.poly_mul(sk, c1)
decrypted = self.ring.poly_add(c0, s_c1)
result = np.zeros(self.n, dtype=int)
for i in range(self.n):
result[i] = round(decrypted[i] * self.t / self.q) % self.t
return result
def add(self, c1, c2):
c0 = self.ring.poly_add(c1[0], c2[0])
c1 = self.ring.poly_add(c1[1], c2[1])
return [c0, c1]
def main():
print("=== BFV 同态加密方案演示 ===\n")
bfv = BFV(n=256, q=1048576, t=256)
print(f"参数设置:")
print(f" 多项式次数 n = {bfv.n}")
print(f" 密文模数 q = {bfv.q}")
print(f" 明文模数 t = {bfv.t}")
print(f" 缩放因子 Δ = {bfv.delta}\n")
pk, sk = bfv.keygen()
print("密钥生成完成")
print(f"私钥 s 前5个系数: {sk[:5]}...\n")
print(sk)
m1 = 42
m2 = 17
print(f"明文 m1 = {m1}")
print(f"明文 m2 = {m2}\n")
c1 = bfv.encrypt(pk, m1)
c2 = bfv.encrypt(pk, m2)
print("加密完成")
print(f"密文 c1[0] 前5个系数: {c1[0][:5]}...")
print(f"密文 c1[1] 前5个系数: {c1[1][:5]}...\n")
print("密文c1 {}".format(c1))
d1 = bfv.decrypt(sk, c1)
d2 = bfv.decrypt(sk, c2)
print("解密完成")
print(f"解密结果 d1 = {d1[0]}")
print(f"解密结果 d2 = {d2[0]}\n")
print("=== 同态加法演示 ===")
c_sum = bfv.add(c1, c2)
d_sum = bfv.decrypt(sk, c_sum)
expected_sum = (m1 + m2) % bfv.t
print(f"密文同态加法: c1 + c2")
print(f"解密结果: {d_sum[0]}")
print(f"期望结果: {expected_sum}")
print(f"验证: {'成功' if d_sum[0] == expected_sum else '失败'}\n")
print("=== 多项式明文演示 ===")
m_poly = np.array([1, 2, 3, 4, 5] + [0] * 251, dtype=int)
c_poly = bfv.encrypt(pk, m_poly)
d_poly = bfv.decrypt(sk, c_poly)
print(f"多项式明文前5个系数: {m_poly[:5]}")
print(f"解密结果前5个系数: {d_poly[:5]}")
print(f"验证: {'成功' if np.array_equal(d_poly[:5], m_poly[:5]) else '失败'}")
if __name__ == "__main__":
main()
View Code
3 SEAL使用
3.1 源码编译
这里仅简单介绍下Windows下使用VS2022环境进行编译,下载源码并安装cmake,运行VS2022安装菜单下的"Developer Command Prompt for VS 2022"命令行,执行命令进行配置:
cmake -S . -B build -G "Visual Studio 17 2022" -A x64 -DCMAKE_INSTALL_PREFIX=./out
这里编译的是64位版本,并将安装目录设置为当前目录下的out文件夹,配置完成后再执行以下命令进行编译
cmake --build build --config Release #编译Release版本
cmake --build build --config Debug #编译Debug版本
编译完成后会生成seal-4.1.lib库文件:

然后执行以下命令进行安装:
cmake --install build
out下include中是头文件,lib中是静态库文件:

3.2 示例程序
使用VS2022创建空项目,并添加demo.cpp文件,内容如下:


1 #include <iostream>
2 #include <SEAL/SEAL.h>
3
4 using namespace std;
5 using namespace seal;
6
7 int main() {
8 // 步骤 1:配置加密参数(BFV 方案)
9 EncryptionParameters parms(scheme_type::bfv);
10 // 多项式模数:4096(2的幂次)
11 size_t poly_modulus_degree = 4096;
12 parms.set_poly_modulus_degree(poly_modulus_degree);
13 // 明文模数:支持批量运算,取值范围 2^20
14 parms.set_plain_modulus(PlainModulus::Batching(poly_modulus_degree, 20));
15 // 系数模数:使用 BFV 默认参数
16 parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
17
18 cout << "明文模数 t = " << parms.plain_modulus().value() << endl;
19 cout << "明文模数 t 比特数 = " << parms.plain_modulus().bit_count() << endl;
20 cout << "t mod 2N = " << (parms.plain_modulus().value() % (2 * poly_modulus_degree)) << endl;
21
22 vector<Modulus> coeff_mods = CoeffModulus::BFVDefault(poly_modulus_degree);
23
24 // 3. 系数模数 q(多素数乘积)
25 cout << "\n系数模数 q 的构成(素数列表):" << endl;
26 int total_bits = 0;
27 for (size_t i = 0; i < coeff_mods.size(); i++) {
28 cout << "第" << i + 1 << "个素数:" << coeff_mods[i].value()
29 << "(比特数:" << coeff_mods[i].bit_count() << ")" << endl;
30 total_bits += coeff_mods[i].bit_count();
31 }
32 cout << "系数模数总比特数 = " << total_bits << endl;
33
34 // 步骤 2:创建加密上下文,验证参数合法性
35 SEALContext context(parms);
36 // 打印上下文信息(可选,查看参数配置)
37 cout << "Context created successfully, scheme type: BFV" << endl;
38
39 // 步骤 3:生成密钥(适配 SEAL 4.1 API,核心修改部分)
40 KeyGenerator keygen(context);
41 // 4.1 版本:通过 create_public_key() 生成公钥(替代原 public_key())
42 PublicKey public_key;
43 keygen.create_public_key(public_key);
44 // 4.1 版本:直接通过成员函数获取私钥(该接口未变更)
45 SecretKey secret_key = keygen.secret_key();
46 // 4.1 版本:通过 create_relin_keys() 生成评估密钥(替代原 relin_keys())
47 RelinKeys relin_keys;
48 keygen.create_relin_keys(relin_keys);
49
50 // 步骤 4:初始化加密器、解密器、评估器
51 Encryptor encryptor(context, public_key);
52 Decryptor decryptor(context, secret_key);
53 Evaluator evaluator(context);
54
55 // 步骤 5:明文准备(两个整数)
56 Plaintext plain1("123");
57 Plaintext plain2("456");
58 cout << "Original plaintext 1: " << plain1.to_string() << endl;
59 cout << "Original plaintext 2: " << plain2.to_string() << endl;
60
61 // 步骤 6:加密明文为密文
62 Ciphertext cipher1, cipher2;
63 encryptor.encrypt(plain1, cipher1);
64 encryptor.encrypt(plain2, cipher2);
65 cout << "Plaintext encrypted to ciphertext successfully" << endl;
66
67 // 步骤 7:密文同态运算(加法 + 乘法)
68 // 密文加法
69 Ciphertext cipher_add;
70 evaluator.add(cipher1, cipher2, cipher_add);
71 // 密文乘法 + 重线性化(减少密文大小)
72 Ciphertext cipher_mult;
73 evaluator.multiply(cipher1, cipher2, cipher_mult);
74 evaluator.relinearize_inplace(cipher_mult, relin_keys);
75
76 // 步骤 8:解密密文,验证结果
77 Plaintext plain_add, plain_mult;
78 decryptor.decrypt(cipher_add, plain_add);
79 decryptor.decrypt(cipher_mult, plain_mult);
80 cout << "Ciphertext add result: " << plain_add.to_string() << endl;
81 cout << "Ciphertext multiply result: " << plain_mult.to_string() << endl;
82
83 return 0;
84 }
View Code
将之间产生的out文件夹下的include和lib拷贝到项目文件夹下,配置项目的C/C++编译包含头文件路径,库文件路径以及输入库,即可进行编译。

编译完成后运行程序,输出如下:

在该示例程序中,多项式模数N是4096,明文模数t是1032193,位宽为20bits,系数模数q是3个素数的乘积68719403009*68719230977*137438822401=0x1ffff4400622fecd904df7f92001,位宽是109bits,示例中演示了0x123和0x456的加法和乘法运行,可见加密运算后的解密结果和未加密运算的结果完全一致。