HElib是IBM用C++写成的一个开源的同态加密库。至于同态加密是什么,网上有一堆教程,无须赘述。
对于同态加密库HElib而言,所有的输入输出都表示成为vector<long>的形式,vector<long>的大小和HElib的初始化参数有关。需要将原始数据全部全部转换成vector<long>的形式,至于如何转换,与具体的业务逻辑有关系,HElib并不关心具体的转换方式,HElib所做的只是对vector<long>进行加解密,以及进行加减乘等运算。
首先先介绍一下HElib的几个重要的类:
FHEcontext类:初始化所必须的类,程序根据初始化的参数生成FHEcontext对象,进而通过FHEcontext对象生成同态加密的公私钥。
ZZX类:生成EncryptedArray所必须的。
FHESecKey类:同态加密私钥类
FHEPubKey类:同态加密公钥类
EncryptedArray类:贯穿整个同态加密的代码,用于将vector<long>编码成同态加密的明文对象,对于数据进行同态加密和解密,用于将明文对象解码成vector<long>。
NewPlaintextArray类:明文对象,输入vector<long>转换成明文对象进而进一步被HElib处理,如果利用cout将NewPlaintextArray对象输出的话也是以向量形式输出,维度与对应位置的元素与vector<long>相同。提供了加、减、乘、移位、异或等运算。加减乘运算即向量对应位置的元素进行加减乘。
Ctxt对象:同态加密的密文对象,NewPlaintextArray类进行同态加密以后得到Ctxt对象。提供了和NewPlaintextArray一样的运算。
示例代码:
#include <NTL/ZZ.h>
#include <NTL/BasicThreadPool.h>
#include "FHE.h"
#include "timing.h"
#include "EncryptedArray.h"
#include <NTL/lzz_pXFactoring.h>
#include <cassert>
#include <cstdio>
int main(){
long R=1;
long p=619;//p决定了进行同态加密的数值可以有多大
long r=1;
long d=1;
long c=2;
long k=80;
long w=64;
long L=6;
long m=7781;
Vec<long> gens;
Vec<long> ords;//以上都是进行同态加密初始化的参数,其中p参数比较重要,它是Ctxt类中Plaintextspace成员的值,决定了我们进行同态加密运算的数据范围并且p必须为素数。例如本例中p=619,则进行同态加密运算的数据不得超过619,明文运算的结果也不能超过619,否则没有办法解密出正确的结果。
vector<long> gens1, ords1;
convert(gens1, gens);
convert(ords1, ords);
FHEcontext context(m, p, r, gens1, ords1);
buildModChain(context, L, c);
ZZX G;
if (d == 0)
G = context.alMod.getFactorsOverZZ()[0];
else
G = makeIrredPoly(p, d);//进行HElib的初始化
//生成同态加密的公私钥,进行同态加密运算的密文对象必须是经过同一公钥加密的数据,否则会报错
FHESecKey secretKey(context);
const FHEPubKey& publicKey = secretKey;
secretKey.GenSecKey(w); // A Hamming-weight-w secret key
addSome1DMatrices(secretKey); // compute key-switching matrices that we need
EncryptedArray ea(context, G);
long nslots = ea.size();
NewPlaintextArray plain_text1(ea);
vector<long> data1;
data1.resize(nslots);
data1[0]=7;
data1[1]=8;//向量数据
encode(ea,plain_text1,data1);//将向量编码成明文对象
cout<<"plain_text1="<<plain_text1<<endl;
Ctxt cipher_text1(publicKey);
cout<<"encrypt plain_text1"<<endl;
ea.encrypt(cipher_text1, publicKey, plain_text1);
NewPlaintextArray plain_text2(ea);
data1[0]=5;
data1[1]=4;
encode(ea,plain_text2,data1);
cout<<"plain_text2="<<plain_text2<<endl;
Ctxt cipher_text2(publicKey);
ea.encrypt(cipher_text2,publicKey,plain_text2);
NewPlaintextArray plain_text3(ea);
data1[0]=1;
data1[1]=2;
encode(ea,plain_text3,data1);
cout<<"plain_text3="<<plain_text3<<endl;
Ctxt cipher_text3(publicKey);
ea.encrypt(cipher_text3,publicKey,plain_text3);
cout<<"密文相加"<<endl;
cipher_text1+=cipher_text2;
cipher_text1+=cipher_text3;
cout<<"结束"<<endl;
NewPlaintextArray de(ea);
cout<<"解密:"<<endl;
ea.decrypt(cipher_text1, secretKey, de);
vector<long> array2;
array2.resize(ea.size());
decode(ea,array2,de);//把解密的结果还原成向量
cout<<"decrypt:"<<array2<<endl;
}
最终打印出解密结果,即三个向量对应位置的元素相加。
此外,在进行乘法运算的过程中,存在一个我也不知道是不是BUG的问题:
有两个NewPlaintextArray对象plain1和plain2,加密成Ctxt对象cipher1,cipher2,接着进行运算cipher1×=cipher2,此时对cipher1进行解密可以得到正确的结果。但是调用cipher1的write函数将cipher1写入文件以后,再读文件读入cipher3,此时再对cipher3进行解密无法解密出正确的结果。
我比较了一下cipher1和cipher3,打印出他们所有的成员,发现了cipher1的PrimeSet的成员=[1,2]但是cipher3的PrimeSet成员=[0,1,2],我调用了cipher3的getPrimeSet方法得到了PrimeSet成员,调用该成员的remove(0)方法删去0,接着进行解密可以解密出正确的结果,但目前不确定该方法是否具有通用性。