2021SC@SDUSC
2021-10-17
第三周完成事项
工作分工
在本周二,我跟队友与孔凡玉老师进行了上周工作汇报以及成果分析,然后与孔凡玉老师探讨了接下来的代码分析方向。经过了孔凡玉老师的初步分析,我们组重新制定了代码分析方向,采取首尾并进的方法进行分析,由我和李东晓进行seal全同态加密算法底层方法的分析,由高跃进行顶层调用和整体结构的分析。
根据这周二的讨论成果,作为队长我安排了本周的代码分析工作:高跃负责keygenerator,也就是加密算法的第一步——密钥的生成;李东晓负责ntt算法的分析,ntt是以数论为基础的快速数论变换算法,可以大大减少算法的耗费时间;我负责rlwe——带错误的环运算。接下来,将由我来介绍一下本次代码分析的内容以及其在整个算法中的重要作用。
RLWE
rlwe的全称可以通过查阅维基百科得知是Ring learning with errors,中文翻译也就是带有错误的环运算。
在离散数学中,我们学习过有关群环域的相关知识,环的定义是对于任意运算(Z,+, ⋅),满足,(R, +)是交换群、(R, ·)是幺半群以及乘法对加法满足分配律,这里所指的加法和乘法是广义的两种运算,并不只是我们熟知的加法和乘法。在SEAl全同态加密算法中,我们定义加法和乘法为复数范围的运算。
RLWE是一个计算问题,其可以作为新的加密算法的基础算法。目的是防止量子计算机进行密码分析,并为同态加密提供基础。公钥密码学依赖于数学问题的构建,如果没有进一步的信息可用,这些数学问题被认为是很难解决的,但是如果已知问题构建中使用了某些信息,则很容易被解决。如果可以建造足够大的量子计算机,那么目前在密码学中使用的某些此类问题将面临被攻击的风险,因此余姚寻找具有抵抗力的问题,同态加密是一种加密形式,它允许对密文进行计算,例如对存储在加密数据库中的数值进行算术运算。
RLWE更恰当地称之为环上的误差学习,并且只是专门针对有限域上的多项式环的较大误差学习(LWE)问题。由于假设即使在量子计算机上也难以解决RLWe问题,因此基于RLWE的密码学可能会成为未来公钥密码学的基础,就像是整数分解和离散对数问题已经成为公钥密码学的基础一样。自1980年代初以来,基于环的学习错误问题的密码学的一个重要特征是,RLWE问题的解决方案可用于解决格中的NP难的最短向量问题(SVP)(SVP的多项式时间减少)
RLWE与SEAL全同态加密算法的联系
基于RLWE的密码学相较于基于错误学习(LWE)的原始密码学的主要优势在于公钥和私钥的大小。RLWE的密钥大小大致是LWE中密钥大小的平方根。对于128位的安全性,RLWE加密算法将使用大约7000位长度的公钥,相应的LWE方案则需要4900万位的公钥才能达到相同的安全级别。RLWE密钥大小大于当前使用的公钥算法(如RSA和椭圆曲线)的密钥大小,分别需要3072位和256位的公钥大小,以实现128位的安全级别,然而,从计算的角度来看,RLWE算法已被证明等于或优于现有的公钥系统。
与代码相关的知识补充——RNS
RNS的中文意思是余数系统。余数系统是一种无权的运算,各个模运算之间具有天然的独立性和并行性,相互之间不存在进位。因此,采用余数系统可以加快模乘和模逆的运算速度,挖掘模乘和模逆的并行性,在当今密码运算的大运算量时代,对提高公钥密码算法速度,具有重要的研究价值。(知识来源于百度百科)
RLWE的代码分析
void sample_poly_ternary(
std::shared_ptr<UniformRandomGenerator> prng, const EncryptionParameters &parms,
std::uint64_t *destination);
void sample_poly_normal(
std::shared_ptr<UniformRandomGenerator> prng, const EncryptionParameters &parms,
std::uint64_t *destination);
void sample_poly_cbd(
std::shared_ptr<UniformRandomGenerator> prng, const EncryptionParameters &parms,
std::uint64_t *destination);
void sample_poly_uniform(
std::shared_ptr<UniformRandomGenerator> prng, const EncryptionParameters &parms,
std::uint64_t *destination);
上面的函数是通过不同方式生成多项式,并将其保存在RNS中。方法如下:一是生成统一的三元多项式,二是通过正态分布生成多项式,三是通过中心二项式生成多项式,四是生成均匀随机的多项式。多项式是做同态运算的基础。接下来是代码的实现,由于这些方法比较近似,我就以第一个的实现进行举例。
void sample_poly_ternary(
shared_ptr<UniformRandomGenerator> prng, const EncryptionParameters &parms, uint64_t *destination)
{
auto coeff_modulus = parms.coeff_modulus();
size_t coeff_modulus_size = coeff_modulus.size();
size_t coeff_count = parms.poly_modulus_degree();
RandomToStandardAdapter engine(prng);
uniform_int_distribution<uint64_t> dist(0, 2);
SEAL_ITERATE(iter(destination), coeff_count, [&](auto &I) {
uint64_t rand = dist(engine);
uint64_t flag = static_cast<uint64_t>(-static_cast<int64_t>(rand == 0));
SEAL_ITERATE(
iter(StrideIter<uint64_t *>(&I, coeff_count), coeff_modulus), coeff_modulus_size,
[&](auto J) { *get<0>(J) = rand + (flag & get<1>(J).value()) - 1; });
});
}
上面是第一个函数的实现,首先是通过输入参数中的加密参数parms得到需要的变量,通过参数prng进行适配器的初始化,然后依次迭代目标参数生成统一的三元多项式。
void encrypt_zero_asymmetric(
const PublicKey &public_key, const SEALContext &context, parms_id_type parms_id, bool is_ntt_form,
Ciphertext &destination);
void encrypt_zero_symmetric(
const SecretKey &secret_key, const SEALContext &context, parms_id_type parms_id, bool is_ntt_form,
bool save_seed, Ciphertext &destination);
上面的两个方法分别是通过通过公钥和密钥创建零加密并存储在密文中。这里讲解一下公钥创建的过程。
void encrypt_zero_asymmetric(
const PublicKey &public_key, const SEALContext &context, parms_id_type parms_id, bool is_ntt_form,Ciphertext &destination)
{
#ifdef SEAL_DEBUG
if (!is_valid_for(public_key, context))
{
throw invalid_argument("public key is not valid for the encryption parameters");
}
#endif
// We use a fresh memory pool with `clear_on_destruction' enabled
MemoryPoolHandle pool = MemoryManager::GetPool(mm_prof_opt::mm_force_new, true);
auto &context_data = *context.get_context_data(parms_id);
auto &parms = context_data.parms();
auto &coeff_modulus = parms.coeff_modulus();
size_t coeff_modulus_size = coeff_modulus.size();
size_t coeff_count = parms.poly_modulus_degree();
auto ntt_tables = context_data.small_ntt_tables();
size_t encrypted_size = public_key.data().size();
// Make destination have right size and parms_id
// Ciphertext (c_0,c_1, ...)
destination.resize(context, parms_id, encrypted_size);
destination.is_ntt_form() = is_ntt_form;
destination.scale() = 1.0;
// c[j] = public_key[j] * u + e[j] where e[j] <-- chi, u <-- R_3
// Create a PRNG; u and the noise/error share the same PRNG
auto prng = parms.random_generator()->create();
// Generate u <-- R_3
auto u(allocate_poly(coeff_count, coeff_modulus_size, pool));
sample_poly_ternary(prng, parms, u.get());
// c[j] = u * public_key[j]
for (size_t i = 0; i < coeff_modulus_size; i++)
{
ntt_negacyclic_harvey(u.get() + i * coeff_count, ntt_tables[i]);
for (size_t j = 0; j < encrypted_size; j++)
{
dyadic_product_coeffmod(
u.get() + i * coeff_count, public_key.data().data(j) + i * coeff_count, coeff_count,coeff_modulus[i], destination.data(j) + i * coeff_count);
// Addition with e_0, e_1 is in non-NTT form
if (!is_ntt_form)
{
inverse_ntt_negacyclic_harvey(destination.data(j) + i * coeff_count, ntt_tables[i]);
}
}
}
// Generate e_j <-- chi
// c[j] = public_key[j] * u + e[j]
for (size_t j = 0; j < encrypted_size; j++)
{
SEAL_NOISE_SAMPLER(prng, parms, u.get());
for (size_t i = 0; i < coeff_modulus_size; i++)
{
// Addition with e_0, e_1 is in NTT form
if (is_ntt_form)
{
ntt_negacyclic_harvey(u.get() + i * coeff_count, ntt_tables[i]);
}
add_poly_coeffmod(
u.get() + i * coeff_count, destination.data(j) + i * coeff_count, coeff_count, coeff_modulus[i],destination.data(j) + i * coeff_count);
}
}
}
上面的生成零加密的全过程。我们进行逐行的分析。
#ifdef SEAL_DEBUG
if (!is_valid_for(public_key, context))
{
throw invalid_argument("public key is not valid for the encryption parameters");
}
首先进行判断,如果seal处于dehug模式,判断公钥是否对加密参数有效。
#endif
// We use a fresh memory pool with `clear_on_destruction' enabled
MemoryPoolHandle pool = MemoryManager::GetPool(mm_prof_opt::mm_force_new, true);
auto &context_data = *context.get_context_data(parms_id);
auto &parms = context_data.parms();
auto &coeff_modulus = parms.coeff_modulus();
size_t coeff_modulus_size = coeff_modulus.size();
size_t coeff_count = parms.poly_modulus_degree();
auto ntt_tables = context_data.small_ntt_tables();
size_t encrypted_size = public_key.data().size();
通过传进来的加密参数得到模的大小和加密后的大小的变量。
// Make destination have right size and parms_id
// Ciphertext (c_0,c_1, ...)
destination.resize(context, parms_id, encrypted_size);
destination.is_ntt_form() = is_ntt_form;
destination.scale() = 1.0;
// c[j] = public_key[j] * u + e[j] where e[j] <-- chi, u <-- R_3
// Create a PRNG; u and the noise/error share the same PRNG
auto prng = parms.random_generator()->create();
// Generate u <-- R_3
auto u(allocate_poly(coeff_count, coeff_modulus_size, pool));
sample_poly_ternary(prng, parms, u.get());
设置目标零加密的大小和id,生成噪声或者错误的随机数以及通过之前的函数生成统一三元多项式。
// c[j] = u * public_key[j]
for (size_t i = 0; i < coeff_modulus_size; i++)
{
ntt_negacyclic_harvey(u.get() + i * coeff_count, ntt_tables[i]);
for (size_t j = 0; j < encrypted_size; j++)
{
dyadic_product_coeffmod(
u.get() + i * coeff_count, public_key.data().data(j) + i * coeff_count, coeff_count,coeff_modulus[i], destination.data(j) + i * coeff_count);
// Addition with e_0, e_1 is in non-NTT form
if (!is_ntt_form)
{
inverse_ntt_negacyclic_harvey(destination.data(j) + i * coeff_count, ntt_tables[i]);
}
}
}
// Generate e_j <-- chi
// c[j] = public_key[j] * u + e[j]
for (size_t j = 0; j < encrypted_size; j++)
{
SEAL_NOISE_SAMPLER(prng, parms, u.get());
for (size_t i = 0; i < coeff_modulus_size; i++)
{
// Addition with e_0, e_1 is in NTT form
if (is_ntt_form)
{
ntt_negacyclic_harvey(u.get() + i * coeff_count, ntt_tables[i]);
}
add_poly_coeffmod(
u.get() + i * coeff_count, destination.data(j) + i * coeff_count, coeff_count, coeff_modulus[i],destination.data(j) + i * coeff_count);
}
}
最后就是两次迭代生成结果。
以上便是我的代码分析过程。
总结
经过了三周的学习,逐渐了解了SEAL全同态加密算法的主要内容。初期的了解仅限于加密是将明文加密成密文,解密是将密文解密成明文。在阅读了一些有关同态加密的相关文献以及与老师进行了多次交流之后,才对研究的内容有了更深层次的认识。第一次涉足密码学的知识,刚开始选这个课的时候还觉得是自己抢占了网安同学的资格,现在看来,软工同学也是可以很好的研究密码学的,算是个人的小成就吧。这次研究RLWE也是经过深思熟虑的,毕竟作为队长,要考虑到代码的分工以及队友的能力上下限,适当安排分工。RLWE算是这个算法优于其他算法的关键,在保证安全性的同时,可以减小消耗。
最后,感谢孔老师的指导,感谢戴老师和其他审核老师的阅读!