SEAL全同态加密CKKS方案入门详解

CKKS(Cheon--Kim--Kim--Song)是2017年论文《Homomorphic Encryption for Arithmetic of Approximate Numbers》中提出的近似计算同态加密算法,论文的作者是Cheon等四位韩国研究者,如今该方案已经是面向浮点/复数近似同态计算的主流方案,广泛应用于密文机器学习、数值计算场景。不同于以往同态加密算法中所追求的解密结果和明文完全一致,CKKS算法的目标是做近似计算。这并不偏离需求,因为现实生活中大部分运算,面对的是实数(复数),而实数(复数)的运算,往往只需要保留一部分有效数字即可。此外,允许误差,放宽准确性的限制,使得CKKS对比于其他基于LWE/RLWE难题的同态方案,细节有了较大的简化,计算效率也有了很大提升。

1 核心原理

1.1 编码与嵌入原理

CKKS的核心创新是规范嵌入(Canonical Embedding),将环R上的多项式与复数空间CN/2双向映射:

(1)多项式f(x)∈R可通过分圆多项式的根映射为N/2维复数向量

(2)复数向量可反向映射为环上多项式,实现向量数据与多项式的无损转换

(3)结合缩放因子Δ=2k,将浮点数放大为整数,适配有限域运算

槽位数量=N/2,例如N=8192时,单密文可打包4096个浮点数并行运算。R是分圆多项式环:

环中的元素是系数是整数(严格来说是实数)的N次多项式。

CN/2是N/2维复数向量构成的环,它里面的元素是N/2维复数向量。规范嵌入是环同构(保持环加法与乘法的双向一一映射):

设ζj = ei(2j+1)π/N,j = 0,1,...,N-1是xN + 1 = 0,即xN = -1 = e的N个元根,则σ从左到右表示为:

σ有以下关键性质:

(1)加法映射为向量加法,即σ(f+g) = σ(f) + σ(g)

(2)乘法映射为向量逐元素乘法(Hadamard积)σ(f•g) = σ(f) • σ(g)

(3)双射(可逆)

可以从向量还原多项式(逆映射),接下来以N=8为例,从正反两个方面进行分析。

1 正向映射(canomical embedding R->C4

当N=8时,环表示为:

环中的元素为多项式:f(x)=a0+a1x+a2x2+...+a7x7,x8+1=0的元根为:x=ei(2k+1)π/8,k=0,1,...,7,由元根的共轭可知:

则正向映射将多项式映射为复数向量,表示为:

以上只取前4个根,因为由于共轭有:

以多项式f(x) = 1 + 2x + 3x2为例,计算第一个槽:

同理可得4维向量的其他分量,从而完成多项式向复向量的映射,σ(f) = (z0,z1,z2,z3)∈C4。这一步本质上就是FFT的前半部分,𝑓(𝑥)有8个实自由度,而复向量看起来只有4个自由度,但是因为𝑧i是复数,它本身有2个实自由度,所以总体上来说复向量有4x2=8个实自由度。

2 逆向映射(C4->R)

逆向映射将复数向量映射为多项式,由(z0,z1,z2,z3)∈C4恢复一个多项式,构造长度为8的"频域向量":

唯一多项式f(x)满足:f(ζk) = zk,k = 0,1,2,...,7,利用Lagrange插值(工程上对应inverse FFT)即可得到:

其中所有ai∈R。这里的双向映射正是CKKS Encode/Decode在做的事:

1.2 缩放因子(Scale)及近似计算

CKKS不追求绝对精确,解密结果与原始明文存在极小精度误差,误差可控且远低于机器学习/数值分析的容忍阈值,这是效率与实用性的平衡设计,为了实现该目标引入了缩放因子,缩放因子在"把实/复数嵌入整数多项式环"时,由"缩放+舍入"不可避免引入了精度误差:

仍以N=8为例说明相应流程,原本映射

是一对零误差的严格双射,但是CKKS要做一件"非法的事",逆向映射由实/复数转换成整系数多项式,就是:

操作本身就不可能精确完成,原因很简单,左边是连续域,右边是离散格,CKKS的解决方案是进行"缩放+舍入(误差源头)",首先引入一个scale Δ,在映射前对复向量进行缩放:

这一步只是放大并没有引入误差,接下来进行逆embedding(IFFT)得到实系数多项式:

理论上这一步仍然是精确的,但是在进行随后一步舍入到整系数时会产生误差:

令:

其中e(x)的每个系数都满足|ei|≤1/2,当映射回slot空间时,会对m(x)再做canonical embedding:

而:

所以:

其中εk = e(ζk),最后会除于scale Δ:

这就是CKKS的"近似",对比BFV中的"舍入",他们的含义完全不同:

BFV的舍入是:

在模t意义下,向整数添加噪声,再把"整数+小噪声"映射回同一个整数,只要噪声受控该过程就是之前操作的精确逆操作。

CKKS的舍入是:

把"连续实数"映射到最近的整数,再把整数映射回实数,该过程并不是之前操作的精确逆操作,所以产生"近似"。

一句话:BFV里round用于"纠错",CKKS里round用于"近似"。在SEAL中常用2^40,精度与噪声达到平衡。

1.3 噪声管理

在CKKS中,噪声管理主要依靠Rescaling(重缩放)机制,它配合RNS链,实现了对噪声的"阶梯式"精准控制。

1 CKKS噪声的组成部分

CKKS的噪声e并不是单一来源,它是由三部分叠加而成的:

**初始噪声(erlwe):**加密时引入的离散高斯噪声,用于保障安全性。

**编码误差(eenc):**将浮点数缩放并舍入到整数多项式时参数的损失。

**计算误差(ecomp):**同态乘法和重线性化引入的噪声。

在CKKS中,我们不区分"噪声"和"误差"。缩放因子Δ决定了明文的精度,而噪声只要不侵蚀到Δ定义的有效位,计算就是可靠的。

2 核心机制:Rescaling(重缩放)

当我们做一次同态乘法:(m1•Δ)x(m2•Δ) = (m1•m2)•Δ2,此时,缩放因子从Δ变成了Δ2,如果不处理,连续乘法会导致数值迅速爆炸,超出模数q的范围。此时将密文除于一个RNS素数ql(通常设ql∈Δ),并进行舍入:

结果:

(1)缩放因子回归:Δ2/ql ≈ Δ,有效位回到了正确的位置。

(2)噪声同步缩减:原本的噪声e也被除于了ql,新噪声为:e/ql+舍入误差

可见Rescaling不仅把膨胀的数值拉回来,同时顺手把增长的噪声也按比例缩小了。

3 RNS链与层(Levels)的噪声控制

在实际实现中,模数Q被分解为Q=q0•q1•...•qL,每一层计算都对应链中的一个素数。

以上每一层Rescaling都会吃掉总模数Q的约p比特(假设Δ=2p),当RNS链只剩下最后一个素数q0时,无法再进行Rescaling,此时再做乘法,噪声将无法被缩小,会迅速覆盖掉有效明文位。假设Q为600bit,Δ为40bit,Q能支撑约600/40=15层乘法,算到第15层时,模数只剩40bit左右,此时噪声和明文混在一起,无法再分。与BFV的区别:BFV的噪声管理是为了保住"整数的绝对准确";CKKS的噪声管理是为了保住"浮点数的有效精度"。

2 CKKS标准流程划分

以SEAL库为例,完整流程分为5个阶段。

2.1 系统初始化与参数生成

输入参数:

多项式模次数N(2的整数幂)

模数链系数{q1,q2,...,qL}(递减序列,决定运算深度)

缩放因子Δ=2k(控制计算精度)

高斯噪声分布χ(RLWE基础噪声)

输出参数:

合法的SEAL上下文

2.2 密钥生成

基于RLWE问题生成密钥组,私钥绝对保密,公钥/辅助密钥可公开分发。

(1)私钥sk

随机采样小范数多项式:sk∈R2(系数仅为-1/0/1)

(2)公钥pk=(b,a)

a:环上随机多项式

b:b=-(a·sk + e),其中e是高斯噪声多项式

(3)重线性化密钥rlk

用于密文乘法后的维度压缩,由私钥加密派生,服务器执行重线性化时使用。

(4)旋转密钥rot_k(可选)

用于密文槽位旋转,实现向量数据重排、求和等高阶运算。

2.3 编码与加密

步骤1 明文编码(浮点数/复数->多项式)

该步会将实数/复数向量z∈CN/2转换为明文多项式。

(1)构造对称向量(Symmetry Construction)

在进行变换前,首先构造一个长度为N的向量Z:

向量的后部部分是前半部分的共轭,这种对称性保证了下一步IFFT出来的多项式系数全部是实浮点数。

(2)正则嵌入逆变换(IFFT)

将长度为N的对称向量Z进行逆变换,得到一个多项式p(x),其系数ai理论上应该是实数(由于浮点数计算精度问题,可能会有极小的虚部,通常直接舍弃)。

(3)缩放与舍入(Scaling & Rounding)

对实浮点数系数进行操作:

由此得到了系数为实整数的多项式pt。

以N=4为例,假设z=(3,4),则首先对z进行扩展Z=(3,4,4,3),因为3和4可以看作是虚部为零的复数,所以Z向量后半部分的共轭和前半部分完全相同。接下来目标是将Z映射为多项式p(x)=a0+a1x+a2x2+a3x3,即需要找到多项式的实系数a0,a1,a2,a3。在CKKS中,使用x4+1=0的单位根,它们是:ζ0 = eiπ/4,ζ1 = ei3π/4,ζ2 = ei5π/4,ζ3 = ei7π/4,ζ2和ζ3是ζ1和ζ0的共轭,则对于多项式p(x)满足:p(ζ0) = y0 = 3,p(ζ1) = y1 = 4,p(ζ2) = y2 = 4,p(ζ3) = y3 = 3,可以利用IFFT逆变换系数公式进行求解:

依次计算各个系数:

计算a0

计算a1

由:

可知:

计算a2

由于ζ0-2 = -i,ζ1-2 = i,ζ2-2 = -i,ζ3-2 = i,可知:

计算a3

由:

可知:

以上多项式系数的计算过程可以看到,系数中的虚部会因为共轭对称完全抵消,只剩实部,得到多项式是p(x)=3.5-0.3535x+0.3535x3,后续将在此多项式的基础上对齐系数进行放大及舍入完成编码流程。

步骤2 密文加密(明文->密文)

编码后的明文多项式pt被转换为一个包含两个多项式的密文对(c0,c1):

图中a,b是公钥对儿的两部分,u是小范数随机多项式,e0,e1是高斯噪声多项式,Q是密文大模数。

2.4 密文同态运算

支持加法、标量乘、密文乘、旋转、求和等运算,是CKKS的核心能力。

1 密文加法(同态加)

输入:两个同维度密文ct=(ct0,ct1),ct'=(ct0',ct1')

输出:ctadd=(ct0+ct0',ct1+ct1')

特性:无维度膨胀,无需重线性化,噪声线性叠加,运算后缩放因子保持不变。

2 密文乘法(同态乘)

原始乘法:两个二元组密文相乘,输出三元组密文ctmult=(c0,c1,c2),其中c0=ct0•ct0',c1=ct0•ct1'+ct1•ct0',c2=ct1•ct1'。

重线性化:使用重线性化密钥rlk,将三元组压缩回标准二元组,恢复密文结构

重缩放:乘法后,缩放因子变成Δ2,为了防止数值爆炸,会将密文除于Δ并舍入

模切换:降低模数,同步缩放数据,压缩噪声,保证后续运算可行性

特性:噪声指数级增长,必须配合重线性化+模切换使用。

3 高阶运算

标量乘法:密文与公开常数相乘,无需密钥

槽位旋转:移动单个槽位数据,配合旋转密钥使用

槽位求和:批量数据聚合,适用于统计计算

2.5 解密与解码

1 密文解密

输入:密文ct=(ct0,ct1)、私钥sk

输出:解码前明文多项式

解密公式:ptdec = ct0 + ct1•sk = u•(-(a•sk + epk))+e0+pt + (u•a+e1)•sk = pt + e0 + e1•sk - u•epk,最后部分可以看作是总噪声,则有:ptdec = pt + etotal

2 明文解码

(1)去除模数映射,将多项式转换为复数向量

(2)除于缩放因子Δ,还原为原始尺度的浮点数

(3)舍弃虚部(实数场景),得到最终计算结果

3 代码示例

以下是SEAL库CKKS同态加密方案示例代码:

复制代码
#include "seal/seal.h"
#include <iostream>
#include <vector>
#include <iomanip>

using namespace seal;
using namespace std;

// 工具函数:打印浮点向量,格式化输出结果
void print_vector(const vector<double>& vec, const string& title, size_t print_size = 5) {
    cout << title << ": ";
    size_t limit = min(print_size, vec.size());
    for (size_t i = 0; i < limit; ++i) {
        cout << fixed << setprecision(3) << vec[i] << " ";
    }
    if (vec.size() > limit) cout << "...";
    cout << endl;
}

int main() {
    // ===================== 1. CKKS 方案参数配置 =====================
    // 多项式模次数:决定安全等级、槽位数量、计算性能,必须是2的幂
    size_t poly_modulus_degree = 8192;
    EncryptionParameters params(scheme_type::ckks);
    params.set_poly_modulus_degree(poly_modulus_degree);

    // 配置系数模数链:CKKS核心,通过模切换控制噪声和计算深度
    // 60, 40, 40, 60 表示模数链分段,支持2层乘法运算
    params.set_coeff_modulus(CoeffModulus::Create(
        poly_modulus_degree, { 60, 40, 40, 60 }
    ));

    // 初始化SEAL上下文,验证参数合法性
    SEALContext context(params);
    cout << "SEAL CKKS 参数初始化完成,多项式模次数: " << poly_modulus_degree << endl;
    cout << "可用槽位数量(单密文可打包数据量): "
        << context.get_context_data(context.first_parms_id())->parms().poly_modulus_degree() / 2 << endl;

    // ===================== 2. 密钥生成 =====================
    // 密钥生成器:基于上下文生成公私钥、重线性化密钥
    KeyGenerator keygen(context);

    // 私钥(核心机密,仅客户端持有)
    SecretKey secret_key = keygen.secret_key();
    // 公钥(公开分发,用于加密)
    PublicKey public_key;
    keygen.create_public_key(public_key);
    // 重线性化密钥(密文乘法后压缩密文维度,必需)
    RelinKeys relin_keys;
    keygen.create_relin_keys(relin_keys);

    // ===================== 3. 核心组件初始化 =====================
    // 加密器:使用公钥加密明文
    Encryptor encryptor(context, public_key, secret_key);
    // 解密器:使用私钥解密密文
    Decryptor decryptor(context, secret_key);
    // CKKS编码器:实现浮点数 ↔ 明文多项式 转换
    CKKSEncoder encoder(context);
    // 同态运算器:执行密文加减乘、重线性化等操作
    Evaluator evaluator(context);

    // 缩放因子:CKKS用于控制浮点计算精度的核心参数
    double scale = pow(2.0, 40);
    cout << "CKKS 缩放因子: 2^40" << endl << endl;

    // ===================== 4. 构造明文浮点向量 =====================
    vector<double> x = { 1.1, 2.2, 3.3, 4.4 };
    vector<double> y = { 5.5, 6.6, 7.7, 8.8 };
    print_vector(x, "原始向量 x");
    print_vector(y, "原始向量 y");

    // ===================== 5. 明文编码 + 加密 =====================
    Plaintext plain_x, plain_y;
    // 将浮点向量编码为CKKS明文格式,指定缩放因子
    encoder.encode(x, scale, plain_x);
    encoder.encode(y, scale, plain_y);

    Ciphertext cipher_x, cipher_y;
    // 加密明文为密文
    encryptor.encrypt(plain_x, cipher_x);
    encryptor.encrypt(plain_y, cipher_y);
    cout << "\n明文编码、加密完成,密文x数据大小: " << cipher_x.size() << endl;

    // ===================== 6. 密文同态运算 =====================
    // 6.1 密文加法:cipher_add = x + y
    Ciphertext cipher_add;
    evaluator.add(cipher_x, cipher_y, cipher_add);
    cout << "密文加法运算完成" << endl;

    // 6.2 密文乘法:cipher_mult = x * y
    Ciphertext cipher_mult;
    evaluator.multiply(cipher_x, cipher_y, cipher_mult);
    // 乘法后密文维度膨胀,执行重线性化压缩回标准格式
    evaluator.relinearize_inplace(cipher_mult, relin_keys);
    cout << "密文乘法 + 重线性化完成" << endl;

    // ===================== 7. 密文解密 + 解码 =====================
    Plaintext plain_add_result, plain_mult_result;
    // 解密运算结果密文
    decryptor.decrypt(cipher_add, plain_add_result);
    decryptor.decrypt(cipher_mult, plain_mult_result);

    vector<double> result_add, result_mult;
    // 解码为浮点向量
    encoder.decode(plain_add_result, result_add);
    encoder.decode(plain_mult_result, result_mult);

    // ===================== 8. 结果输出 =====================
    cout << "\n==================== 运算结果对比 ====================" << endl;
    print_vector(result_add, "密文加法解密结果");
    print_vector(result_mult, "密文乘法解密结果");

    // 计算明文基准值用于对比
    vector<double> true_add, true_mult;
    for (size_t i = 0; i < x.size(); ++i) {
        true_add.push_back(x[i] + y[i]);
        true_mult.push_back(x[i] * y[i]);
    }
    print_vector(true_add, "明文加法基准值");
    print_vector(true_mult, "明文乘法基准值");

    return 0;
}

CKKS

代码运行效果如下:

4 参考

1 https://zhuanlan.zhihu.com/p/366965077

2 https://blog.csdn.net/WaitMrAnt/article/details/140950613

3 https://zhuanlan.zhihu.com/p/619856459

相关推荐
weiwei2284422 天前
SEAL全同态加密BFV方案入门详解
bfv·全同态·多项式环·seal
帅逼码农2 个月前
openfhe同态库安装
加密·全同态·openfhe·库安装
胡乱编胡乱赢8 个月前
同态加密类型详解:部分同态加密,全同态加密
算法·区块链·同态加密·全同态·部分同态
木亦汐丫1 年前
全同态加密基于多项式环计算的图解
图解·全同态加密·bgv·ckks·bfv·rlwe·多项式环
木亦汐丫1 年前
BFV/BGV全同态加密方案浅析
密码学·全同态加密·bgv·ckks·bfv·重线性化·模数切换
Nicolas8931 年前
【隐私计算篇】全同态加密应用场景案例(隐私云计算中的大模型推理、生物识别等)
同态加密·隐私计算·全同态·全流程密算·全流程加密·全匿踪
木亦汐丫1 年前
【密码学】全同态加密张量运算库解读 —— TenSEAL
神经网络·tensor·1024程序员节·全同态加密·ckks·bfv·tenseal·卷积运算
木亦汐丫1 年前
全同态加密算法概览
隐私计算·全同态加密·bgv·fhe·lwe·ckks·bfv