赞
踩
WHU W.F.
2020.12.30
由于博主本人不再从事相关工作,请大家注意时效性。
部分资源在下载区,我已取消下载限制(1积分)
2023.4.30
本文的目的并非深入探究CKKS原理,而是力求帮助开发者在完全不懂原理的情况下,快速构建CKKS工程,并掌握基本的使用技巧。
阅读本文要求读者:
如无特殊说明,本文用m代指明文,c代指密文
看代码之前我们先回顾一些概念:
首先,CKKS是一个公钥加密体系,具有公钥加密体系的一切特点,例如公钥加密、私钥解密等。因此,我们的代码中需要以下组件:
密钥生成器 keygenerator
加密模块 encryptor
解密模块 decryptor
其次,CKKS是一个(level)全同态加密算法(level表示其运算深度仍然存在限制),可以实现数据的“可算不可见”,因此我们还需要引入:
密文计算模块 evaluator
最后,加密体系都是基于某一数学困难问题构造的,CKKS所基于的数学困难问题在一个“多项式环”上(没有数论知识也没有关系,只需要明白环上的元素与实数并不相同),因此我们需要引入:
编码器 encoder
来实现数字和环上元素的相互转换。
总结下来,整个构建过程为:
① 选择CKKS参数 parms
② 生成CKKS框架 context
③ 构建CKKS模块 keygenerator,encoder,encryptor,evaluator和decryptor
④ 使用encoder 将 数据n 编码为 明文m
⑤ 使用encryptor 将 明文m 加密为 密文c
⑥ 使用evaluator 对 密文c 运算为 密文c’
⑦ 使用decryptor 将 密文c’ 解密为 明文m’
⑧ 使用encoder 将 明文m’ 解码为 数据n’
同态加密算法最直观的应用是云计算,其基本流程为:
①发送方利用公钥pk 加密 明文m 为 密文c
②发送方把密文c发送到服务器
③服务器执行密文运算,生成结果密文 c’
④服务器将结果密文c’发送给接收方
⑤接收方利用私钥sk 解密密文c’为明文结果m’
当发送方与接收方相同时,则该客户利用全同态加密算法完成了一次安全计算,即既利用了云计算的算力,又保障了数据的安全性,这对云计算的安全应用有重要意义。
下面的例子实现了上述情景:
#include "examples.h" /* This file can be found in SEAL/native/example/ 该文件可以在SEAL/native/example目录下找到 */ #include <vector> using namespace std; using namespace seal; #define N 3 //本例的目的是计算x,y,z的乘积 int main(){ //客户端的视角: 要进行计算的数据 vector<double> x, y, z; x = { 1.0, 2.0, 3.0 }; y = { 2.0, 3.0, 4.0 }; z = { 3.0, 4.0, 5.0 }; //构建参数容器 parms EncryptionParameters parms(scheme_type::CKKS); /*CKKS有三个重要参数: 1.poly_module_degree(多项式模数) 2.coeff_modulus(参数模数) 3.scale(规模) 下一小节会详细解释*/ size_t poly_modulus_degree = 8192; parms.set_poly_modulus_degree(poly_modulus_degree); parms.set_coeff_modulus(CoeffModulus::Create(poly_modulus_degree, { 60, 40, 40, 60 })); //选用2^40进行编码 double scale = pow(2.0, 40); //用参数生成CKKS框架context auto context = SEALContext::Create(parms); //构建各模块 //首先构建keygenerator,生成公钥、私钥和重线性化密钥 KeyGenerator keygen(context); auto public_key = keygen.public_key(); auto secret_key = keygen.secret_key(); auto relin_keys = keygen.relin_keys(); //构建编码器,加密模块、运算器和解密模块 //注意加密需要公钥pk;解密需要私钥sk;编码器需要scale Encryptor encryptor(context, public_key); Evaluator evaluator(context); Decryptor decryptor(context, secret_key); CKKSEncoder encoder(context); //对向量x、y、z进行编码 Plaintext xp, yp, zp; encoder.encode(x, scale, xp); encoder.encode(y, scale, yp); encoder.encode(z, scale, zp); //对明文xp、yp、zp进行加密 Ciphertext xc, yc, zc; encryptor.encrypt(xp, xc); encryptor.encrypt(yp, yc); encryptor.encrypt(zp, zc); /*对密文进行计算,要说明的原则是: 1.加法可以连续运算,但乘法不能连续运算 2.密文乘法后要进行relinearize操作 3.执行乘法后要进行rescaling操作 4.进行运算的密文必需执行过相同次数的rescaling(位于相同level) */ //基于上述原则进行运算 //至此,客户端将pk、CKKS参数发送给服务器,服务器开始运算 //服务器的视角:先设中间变量 Ciphertext temp; Ciphertext result_c; //计算x*y,密文相乘,要进行relinearize和rescaling操作 evaluator.multiply(xc,yc,temp); evaluator.relinearize_inplace(temp, relin_keys); evaluator.rescale_to_next_inplace(temp); //在计算x*y * z之前,z没有进行过rescaling操作,所以需要对z进行一次乘法和rescaling操作,目的是 make x*y and z at the same level Plaintext wt; encoder.encode(1.0, scale, wt); //我们可以查看框架中不同数据的层级: cout << " + Modulus chain index for zc: " << context->get_context_data(zc.parms_id())->chain_index() << endl; cout << " + Modulus chain index for temp(x*y): " << context->get_context_data(temp.parms_id())->chain_index() << endl; cout << " + Modulus chain index for wt: " << context->get_context_data(wt.parms_id())->chain_index() << endl; //执行乘法和rescaling操作: evaluator.multiply_plain_inplace(zc, wt); evaluator.rescale_to_next_inplace(zc); //再次查看zc的层级,可以发现zc与temp层级变得相同 cout << " + Modulus chain index for zc after zc*wt and rescaling: " << context->get_context_data(zc.parms_id())->chain_index() << endl; //最后执行temp(x*y)* zc(z*1.0) evaluator.multiply_inplace(temp, zc); evaluator.relinearize_inplace(temp,relin_keys); evaluator.rescale_to_next(temp, result_c); //计算完毕,服务器把结果发回客户端 //客户端进行解密和解码 Plaintext result_p; decryptor.decrypt(result_c, result_p); //注意要解码到一个向量上 vector<double> result; encoder.decode(result_p, result); //得到结果 //正确的话将输出:{6.000,24.000,60.000,...,0.000,0.000,0.000} cout << "结果是:" << endl; print_vector(result,3,3); return 0; }
读完样例代码,对CKKS同态加密计算流程应该有了基本的了解。
本小节对三个参数进行简单的解释
该参数必须是2的幂,如1024, 2048, 4096, 8192, 16384, 32768,当然再大点也没问题。
更大的 poly_modulus_degree 会增加密文的尺寸,这会让计算变慢, 但也能让你执行更复杂的计算,关于这点稍后会有更直观的理解。
每个密文和明文(即Plaintext和Cipertext)本质上是一个长为poly_modules/2的向量,如果要利用例子中的“包加密技术”将一个向量直接加密到密文上,就需要保证向量的长度不超过poly_modules/2,在本例中为4096。否则就要先进行拆分再加密(即用for循环遍历数据,逐一加密),或选取更大的poly_modules参数。
这是一组重要参数,因为rescaling操作依赖于coeff_modules。
简单来说,coeff_modules的个数决定了你能进行rescaling的次数,进而决定了你能执行的乘法操作的次数。
coeff_modules的最大位数与poly_modules有直接关系,列表如下:
poly_modulus_degree | max coeff_modulus bit-length |
---|---|
1024 | 27 |
2048 | 54 |
4096 | 109 |
8192 | 218 |
16384 | 438 |
32768 | 881 |
本文例子中的{60,40,40,60}有以下含义:
① coeff_modules总位长200(60+40+40+60)位
② 最多进行两次(两层)乘法操作
该系列数字的选择不是随意的,有以下要求:
① 总位长不能超过上表限制
② 最后一个参数为特殊模数,其值应该与中间模数的最大值相等
③ 中间模数与scale尽量相近
本例中,每次Rescaling的示意图如下:
\ special prime:60
coeff_modulus: { 60, 40, 40, 60 } ±–+ Level 3 (key level)
----------------------rescaling↓--------------------------------------
coeff_modulus: { 60, 40, 40 } ±–+ Level 2 (data level)
-----------------rescaling↓---------------------------------------------
coeff_modulus: { 60, 40 } ±–+ Level 1
------------rescaling↓--------------------------------------------
coeff_modulus: { 60 } ±–+ Level 0 (lowest level)
Encoder利用该参数对浮点数进行缩放,每次相乘后密文的scale都会翻倍,因此需要执行rescaling操作约减一部分,约模的大素数位长由coeff_modules中的参数决定。
Scale不应太小,虽然大的scale会导致运算时间增加
但能确保噪声在约模的过程中被正确地舍去,同时不影响正确解密。
因此,两组推荐的参数为:
Poly_module_degree = 8196; coeff_modulus={60,40,40,60};scale = 2^40
Poly_module_degree = 8196; coeff_modulus={50,30,30,30.50};scale = 2^30
如示例代码中所述,每次进行运算前,要保证参与运算的数据位于同一“level”上。
加法不需要进行rescaling操作,因此不会改变数据的level
数据的level只能降低无法升高,所以要小心设计计算的先后顺序
可以通过输出p.scale()、p.parms_id()以及
context->get_context_data(p.parms_id())->chain_index()
来确认即将进行操作的数据满足:
1)用同一组参数进行加密
2)位于(chain)上的同一level
3)scale相同
的计算条件。
要想把不同level的数据拉到同一level,可以利用乘法单位元1把层数较高的操作数拉到较低的level(如本例),也可以通过内置函数进行直接转换,但笔者推荐前者,有利于提高代码的可读性,便于维护。
目前,SEAL提供了reverse、square等有限的计算操作,大部分复杂运算需要自己编写代码实现,在实现过程中要根据数据量把握好精度和性能的取舍。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。