上一节讲到CBD函数
C B D CBD CBD全称center binomial distribution也就是中心二项分布,是用来产生Kyber噪声向量的。
定义这样一个中心二项分布 B η B_\eta Bη,其中 η = 2 \eta=2 η=2或 η = 3 \eta=3 η=3
B η B_\eta Bη其实就是从 0 , 1 2 η {0,1}^{2\eta} 0,12η中采样出 ( a 1 , . . . , a η , b 1 , . . . . , b η ) (a_1,...,a_\eta,b_1,....,b_\eta) (a1,...,aη,b1,....,bη),并输出 ∑ i = 1 η ( a i − b i ) \sum_{i=1}^{\eta}\left(a_{i}-b_{i}\right) i=1∑η(ai−bi)
通过 B η B_\eta Bη可以采样出多项式的系数。对于多项式度数等于256情况,其实就是做256次这样的中心二项分布采样生成256个系数范围在 [ − η , η ] [-\eta,\eta] [−η,η]之间的系数。具体流程如下图算法伪代码所示
C函数实现:
C函数实现分为两部分,第一个部分是load函数,将字节流进行拼接返回
第二部分就是cbd函数,以 η = 2 \eta=2 η=2为例
static uint32_t load32_littleendian(const uint8_t x[4])
{
uint32_t r;
r = (uint32_t)x[0];
r |= (uint32_t)x[1] << 8;
r |= (uint32_t)x[2] << 16;
r |= (uint32_t)x[3] << 24;
return r;
}
static void cbd2(poly *r, const uint8_t buf[2*KYBER_N/4])
{
unsigned int i,j;
uint32_t t,d;
int16_t a,b;
for(i=0;i<KYBER_N/8;i++) {
t = load32_littleendian(buf+4*i);
d = t & 0x55555555;
d += (t>>1) & 0x55555555;
for(j=0;j<8;j++) {
a = (d >> (4*j+0)) & 0x3;
b = (d >> (4*j+2)) & 0x3;
r->coeffs[8*i+j] = a - b;
}
}
}
先从buf中得到32bit,因为0x55555555的二进制形式就是0x01010101010101010101010101010101因此d取t&0x55555555就是取d的奇数位。而t右移1位再&0x55555555取的就是偶数位。两者相加就是奇数位和偶数位1的个数(每两位计算,例如两比特加起来最多是10(01+01=10))。因此每次取4bit,a取低2bit( a = (d >> (4*j+0)) & 0x3),b取高2bit。然后相减。为什么是2bit(因为先减后求和也相当于先求和再减,因此这先求完汉明重量再进行相减)。
编码就是将多项式系数转变为字节流的过程,解码就是将字节流转化为多项式的过程。在C实现里可以采用位运算拼接的过程,以及比特与运算完成这两个过程。
编码
r[0] = (t[0] >> 0);
r[1] = (t[0] >> 8) | (t[1] << 2);
r[2] = (t[1] >> 6) | (t[2] << 4);
r[3] = (t[2] >> 4) | (t[3] << 6);
r[4] = (t[3] >> 2);
r += 5;
其中r是字节流数组,t是多项式系数数组,t中每个系数是10比特(经过compress之后),因此r[0]取t[0]的低8bit,t[0]还剩高2bit,r[1]取t[0]的高2bit(通过右移8bit降到低2bit),这个时候r[1]还缺6bit,因此将t[1]的低6bit放到r[1]的高6bit处。
解码
t[0] = (a[0] >> 0) | ((uint16_t)a[1] << 8);
t[1] = (a[1] >> 2) | ((uint16_t)a[2] << 6);
t[2] = (a[2] >> 4) | ((uint16_t)a[3] << 4);
t[3] = (a[3] >> 6) | ((uint16_t)a[4] << 2);
a += 5;
其中t是多项式系数数组,a是字节流数组。我们需要从a中恢复出多项式系数t。因此就是刚才编码的逆过程,把刚刚取出来的bit再放回去。对于t[0],他的10bit放到了a[0]的8bit,剩下的2bit在a[1]的低2bit。进行类似的拼接和位运算(左移/右移即可)。
压缩和解压缩的主要目的是丢掉一些密文比特并且不影响最终解密的正确性,这样还能减少密文的大小。
x ′ = D e c o m p r e s s q ( C o m p r e s s q ( x , d ) , d ) x^{\prime}=Decompress _{q}( Compress \left._{q}(x, d), d\right) x′=Decompressq(Compressq(x,d),d)
其中 x x x和 x ′ x' x′并不是完全一样的,他们大小关系满足如下式子:
∣ x ′ − x m o d ± q ∣ ≤ B q : = ⌈ q 2 d + 1 ⌋ \left|x^{\prime}-x \bmod ^{\pm} q\right| \leq B_{q}:=\left\lceil\frac{q}{2^{d+1}}\right\rfloor ∣∣x′−xmod±q∣∣≤Bq:=⌈2d+1q⌋
满足这两种要求的压缩解压缩函数定义如下:
Compress q ( x , d ) = ⌈ ( 2 d / q ) ⋅ x ⌋ m o d + 2 d , Decompress q ( x , d ) = ⌈ ( q / 2 d ) ⋅ x ⌋ . \begin{aligned} \text { Compress }_{q}(x, d) &=\left\lceil\left(2^{d} / q\right) \cdot x\right\rfloor \bmod ^{+} 2^{d}, \\ \text { Decompress }_{q}(x, d) &=\left\lceil\left(q / 2^{d}\right) \cdot x\right\rfloor . \end{aligned} Compress q(x,d) Decompress q(x,d)=⌈(2d/q)⋅x⌋mod+2d,=⌈(q/2d)⋅x⌋.
移步我的另一篇博客数论变换Number Theoretic Transform(NTT)
KYBER.CPAPKE和LPR加密方案类似,唯一区别就是KYBER.CPAPKE用的是Module-LWE问题而不是Ring-LWE问题
既然提到了Module-LWE和Ring-LWE,这里就简要说明一下他们之间的区别,具体可见 基于格的后量子密钥交换研究这篇论文。
1996年,Ajtai首先基于格上的计算困难问题,小整数分解(SIS)问题,构造了抗碰撞杂凑函数。2005年,Regev提出了带错误的学习问题(learning with errors problem,LWE),证明了LWE与格上困难问题有关。并且LWE在构建密码系统时比格上困难问题更方便。因此有大量LWE困难性以及在密码学应用的研究。
对于LWE问题,矩阵A的每个元素都是在 Z q \mathbb{Z}_{q} Zq上的。
对于LWE问题,矩阵A的每个元素都是在多项式环 R q = Z q [ x ] / ( x n + 1 ) R_{q}=\mathbb{Z}_{q}[x] /\left(x^{n}+1\right) Rq=Zq[x]/(xn+1)上的。
MLWE问题的好处:
因为基于环上的密码方案,基本是基于环的理想来构造的,而理想本身就具有一些良好的代数结构性质比如吸收律。而模没有理想,不具有这些代数结构,元素之间的关联度更低,出了事不容易一锅端。
模是比环和向量空间更抽象的概念。环就是一种特殊的模,向量空间也是特殊的模
回归Kyber。KYBER.CPAPKE通过 n , k , q , η 1 , η 2 , d u n, k, q, \eta_{1}, \eta_{2}, d_{u} n,k,q,η1,η2,du和 d v d_{v} dv进行参数化。在Kyber算法中, n = 256 , q = 3329 n=256,q=3329 n=256,q=3329。下面给出Kyber密钥生成、加密、解密的定义。
密钥生成的几个核心步骤就是矩阵A的生成(拒绝采样)、向量s、e的生成(中心二项分布),其次就是公钥向量t的计算,涉及到多项式乘法和多项式加法,乘法就用到NTT。私钥就是向量s。
加密涉及到的运算比密钥生成多一个压缩和解压缩,其他都是差不多的。