有限域GF2^8的Lagrange插值分析ZUC流密码S盒的代数结构
背景
欧洲2000-2003年的NESSIE计划和2004-2008年的eSTREAM计划大大促进了流密码的发展,提出了很多新兴的流密码的设计思路和分析方法,很多新型的密码部件都在计划中提出了。提出了具有代表性的多个流密码,例如Grain,rivium,Mickey。我国密码专家在充分分析研究了前人的方案后,提出了国产的流密码方案——ZUC流密码。
ZUC流密码的结构
ZUC流密码的S盒
在密码学中,S盒(Substitution-box)是对称密钥算法执行置换计算的基本结构。S盒用在对称密码算法中,是唯一的非线性结构,其S盒的指标的好坏直接决定了密码算法的好坏。因此ZUC流密码结构中出现的两个S盒直接影响着ZUC流密码性能好坏。而然站在密码分析的角度,我们分析该密码部件的非线性度最佳方式就是对S盒的代数结构进行分析。所以我尝试采用C++语言来尝试用有限域 G F 2 8 GF2^8 GF28的Lagrange插值得到可以代表S盒代数结构的多项式。
S盒(左)
S盒(右)
C++求解ZUC流密码的S盒的代数结构
由于不是简单的计算某个Lagrange差值的数值结果,而是需要得到 L a g r a n g e Lagrange Lagrange差值得到的多项式。因此我采用 256 256 256维数组代表这个最高次数为 255 255 255的多项式,数组存的便是这个多项式的系数。也就是程序运行的结果是一个 256 256 256维数组。
有限域GF2^8的运算
多项式的加法和乘法运算过程中,系数在有限域
G
F
2
8
GF2^8
GF28上进行运算,因此我首先写了进行有限域
G
F
2
8
GF2^8
GF28加减乘除运算的类,代码核心是:采用查表的形式提高有限域运算的效率。有限域的素多项式选择为:
f
(
x
)
=
x
8
+
x
4
+
x
3
+
x
+
1
f(x)=x^8+x^4+x^3+x+1
f(x)=x8+x4+x3+x+1
有限域的生成元选择为:
g
(
x
)
=
x
+
1
g(x)=x+1
g(x)=x+1
类的私有成员用来存储三张表:正表,反表,逆表。正表的下标代表有限域元素的阶,对应的数组内容即为该元素。反表的下标代表有限域元素本身,对应的数组内容为该元素对应的阶,逆表的每个下标对应的有限域元素与对应的数组内容存储的有限域元素互为逆元。
class Operator
{
private:
int P_Table[255];//正表:下标为阶数,元素为值,取值范围[0,255].
int N_Table[256];//反表:下标为值,取值范围[0,255],元素为阶数.
int R_Table[255];//逆元表:下标值与下标对应元素的值互为逆元。
public:
Operator()//构造函数:用于得到运算所需的表
{
//用于构造正表
P_Table[0] = 1;
for(int i=1; i<255; ++i)
{
P_Table[i] = (P_Table[i-1] << 1) ^ P_Table[i-1];//计算g^i
if(P_Table[i] & 0x100)//判断上述过程得到的多项式次数是否达到8
{
P_Table[i] ^= 0x11b;//用技巧模m(x) = x^8 + x^4 + x^3 +x +1
}
}
//用于构造反表
for(int i=0; i<255; ++i)
{
N_Table[ P_Table[i] ] = i;
}
//用于构造逆元表
for(int i=1; i<256; ++i)//0没有逆元,所以从1开始
{
int k = N_Table[i];
k = (255 - k) % 255;//m_table的取值范围为 [0, 254]
R_Table[i] = P_Table[k];
}
}
//有限域上的加法
int GF_Add(const int &a, const int &b)
{
return a^b;
}
//有限域上的乘法
int GF_Mul(const int &x, const int &y)
{
return(((x == 0) || (y == 0)) ? 0 : P_Table[(N_Table[x] + N_Table[y]) % 255] );
}
//有限域上的除法
int GF_Div(const int &x, const int &y)
{
return GF_Mul(x, R_Table[y]);
}
//观察正表,反表,逆表的内容
void Show()
{
for(int i=0; i<255; ++i)
{
cout << hex << P_Table[i] << " ";
}
cout << "\n" << endl;
for(int i=1; i<256; ++i)
{
cout << N_Table[i] << " ";
}
cout << "\n" << endl;
for(int i=1; i<255; ++i)
{
cout << hex << R_Table[i] << " ";
}
cout << "\n" << endl;
}
};
}
多项式的运算
在整个 L a g r a n g e Lagrange Lagrange差值过程中,多项式的运算只需要多项式的加法和多项式的乘法。因此本代码也只包含多项式的这两种运算。
多项式的加法
//多项式加法
template <typename Type>
Type* P_A(const Type *a, const int &Len1, const Type *b, const int &Len2)
{
int Len3 = Max(Len1, Len2);//获得用于表示和多项式的数组的长度
Type *arr = new Type[Len3];//为和多项式的数组开辟动态内存空间
for(int i = 0; i<Len3; ++i)//将每个元素初始化为0
{
arr[i] = 0;
}
if (Len1 == Len3)
{
for (int i=0; i<Len3; ++i)//将多项式最高次的次数高的数组赋给新的数组
{
arr[i] = a[i];
}
for (int i=Len2-1 ; i>=0; --i)//将多项式最高次的次数低的数组与新的数组相加
{
arr[Len1-(Len2-i)] = Operate_main.GF_Add(arr[Len1-(Len2-i)], b[i]);
}
}
else
{
for (int i=0; i<Len3; ++i)//将多项式最高次的次数高的数组赋给新的数组
{
arr[i] = b[i];
}
for (int i=Len1-1; i>=0; --i)//将多项式最高次的次数低的数组与新的数组相加
{
arr[Len2-(Len1-i)] = Operate_main.GF_Add(arr[Len2-(Len1-i)], a[i]);
}
}
return arr;
}
多项式的乘法
//多项式乘法
template <typename Type>
Type* P_M(const Type *a, const int &Len1, const Type *b, const int &Len2)
{
Type *arr = new Type[Len1 + Len2 - 1];//为积多项式的数组开辟动态内存空间
for(int i = 0; i<Len1 + Len2 - 1; ++i)//将每个元素初始化为0
{
arr[i] = 0;
}
for (int i=Len2-1; i>=0; --i)//用数组b的元素分别去乘数组a
{
for (int j=Len1-1; j>=0 ; --j)
{
arr[j+i] = Operate_main.GF_Add(arr[j+i], Operate_main.GF_Mul(b[i], a[j]));
}
}
return arr;
}
Lagrange差值
我将多项式Lagrange插值过程分为三步。
第一步,计算
L
a
g
r
a
n
g
e
Lagrange
Lagrange基函数的系数:
C
o
e
=
y
i
(
x
i
+
x
1
)
(
x
i
+
x
2
)
…
(
x
i
+
x
256
)
Coe=\frac{y_i}{(x_i+x_1)(x_i+x_2)\ldots(x_i+x_{256})}
Coe=(xi+x1)(xi+x2)…(xi+x256)yi
第二步,计算
L
a
g
r
a
n
g
e
Lagrange
Lagrange基函数:
B
a
s
=
y
i
(
x
−
x
1
)
(
x
−
x
2
)
…
(
x
−
x
256
)
(
x
i
+
x
1
)
(
x
i
+
x
2
)
…
(
x
i
+
x
256
)
Bas=\frac{y_i(x-x_1)(x-x_2)\ldots(x-x_{256})}{(x_i+x_1)(x_i+x_2)\ldots(x_i+x_{256})}
Bas=(xi+x1)(xi+x2)…(xi+x256)yi(x−x1)(x−x2)…(x−x256)
第三步,计算
L
a
g
r
a
n
g
e
Lagrange
Lagrange差值多项式:
P
o
l
=
y
1
(
x
+
x
2
)
…
(
x
+
x
256
)
(
x
1
+
x
2
)
…
(
x
1
+
x
256
)
+
y
2
(
x
+
x
1
)
…
(
x
+
x
256
)
(
x
2
+
x
1
)
…
(
x
2
+
x
256
)
+
…
+
y
256
(
x
+
x
1
)
…
(
x
+
x
255
)
(
x
256
+
x
1
)
…
(
x
256
+
x
255
)
Pol=\frac{y_1(x+x_2)\ldots(x+x_{256})}{(x_1+x_2)\ldots(x_1+x_{256})}+\frac{y_2(x+x_1)\ldots(x+x_{256})}{(x_2+x_1)\ldots(x_2+x_{256})}+\ldots+\frac{y_{256}(x+x_1)\ldots(x+x_{255})}{(x_{256}+x_1)\ldots(x_{256}+x_{255})}
Pol=(x1+x2)…(x1+x256)y1(x+x2)…(x+x256)+(x2+x1)…(x2+x256)y2(x+x1)…(x+x256)+…+(x256+x1)…(x256+x255)y256(x+x1)…(x+x255)
因此为此我以为写了三个函数分别来实现上述三步:
计算 L a g r a n g e Lagrange Lagrange基函数的系数
//定义插值基函数的系数
int Lag_Bas_Coe(const int S[16][16], const int &Row, const int &Column)
{
int X = Row * 0x10 + Column;//插值点的x值
int Den = 1;//初始化分母
for(int i=0; i<0x100; ++i)//迭代计算分母
{
if(i != X)
{
Den = Operate_main.GF_Mul(Den, Operate_main.GF_Add(X, i));
}
}
int Coe;//初始化系数
return Coe = Operate_main.GF_Div(S[Row][Column], Den);
}
计算 L a g r a n g e Lagrange Lagrange基函数
//定义拉格朗日插值的基函数
int* Lag_Bas_Fun(const int S[16][16], const int &Row, const int &Column)
{
int X = Row * 0x10 + Column;//插值点的x值
int tmp_1[2];//每一步迭代相乘的多项式
int *tmp_main = new int[2];//主迭代多项式
int count = 0;//用于迭代更新返回数组的长度
for(int i=0; i<0x100; ++i)
{
if(i != X)
{
tmp_1[0] = 1;
tmp_1[1] = i;
int *tmp_2 = new int[2 + count];
if(count == 0)
{
for(int j=0; j<2; ++j)
{
tmp_2[j] = tmp_1[j];
}
}
else
{
tmp_2 = Polynomial_Operation::P_M<int>(tmp_main, 1 + count, tmp_1, 2);
delete[] tmp_main;
}
tmp_main = new int[2 + count];//主迭代多项式
for(int j=0; j<2+count; ++j)
{
tmp_main[j] = tmp_2[j];
}
delete[] tmp_2;
count++;
}
}
int Coe = Lag_Bas_Coe(S, Row, Column);
for(int i=0; i<0x100; ++i)
{
tmp_main[i] = Operate_main.GF_Mul(tmp_main[i], Coe);
}
return tmp_main;
}
计算 L a g r a n g e Lagrange Lagrange差值多项式
//定义差值多项式函数
int* Lag_Pol(const int S[16][16])
{
int* tmp = new int[256];
int* Pol = new int[256];
for(int i=0; i<256; ++i)
{
Pol[i] = 0;
}
for(int i=0; i<0x10; ++i)
{
for(int j=0; j<0x10; ++j)
{
tmp = Lag_Bas_Fun(S, i, j);
Pol = Polynomial_Operation::P_A<int>(tmp, 256, Pol, 256);
}
}
delete[] tmp;
return Pol;
}
插值结果
现在我们来调用主函数查看差值多项式的结果:
int main()
{
int *Pol = new int[256];
//计算S盒的有限域拉格朗日插值多项式
Pol = Lagrange::Lag_Pol();
for(int i=0; i<256; ++i)
{
cout << hex << Pol[i] <<" ";
}
cout << endl;
delete[] Pol;
return 0;
}
S盒(左)的差值多项式 P o l 1 Pol_1 Pol1
S盒(右)的差值多项式 P o l 2 Pol_2 Pol2
插值结果验证
为了验证这里得到多项式的正确性,我们需要计算有限域上全部 256 256 256点对应的结果是否正确。为此我写了一个测试函数来测试结果的正确性:
//测试函数_用于验证多项式的正确性
int Test_Fun(const int *a, const int &len, const int &Row, const int &Column)
{
int test_1;
int test_2 = 0;
int X = Row * 16 + Column;
for(int i=0; i<256; ++i)
{
test_1 = 1;
for(int j=0; j<255-i; ++j)
{
test_1 = Operate_main.GF_Mul(test_1,X);
}
test_2 = Operate_main.GF_Add(Operate_main.GF_Mul(test_1, a[i]), test_2);
}
return test_2;
}
并在主函数中调用该函数来进行测试:
int main()
{
int* Pol = new int[256];
Pol = Lagrange::Lag_Pol();
for(int i=0; i<0x10; ++i)
{
for(int j=0; j<0x10; ++j)
{
cout << hex << Lagrange::Test_Fun(Pol, 256, i, j)<<" ";
}
cout << endl;
}
cout << endl;
delete[] Pol;
return 0;
}
验证S盒(左)的差值多项式 P o l 1 Pol_1 Pol1
验证S盒(右)的差值多项式 P o l 2 Pol_2 Pol2
经过验证,我发现得到的结果和ZUC流密码的两个S盒保持了一致。因此可以充分说明我成功的利用有限域
G
F
2
8
GF2^8
GF28的
L
a
g
r
a
n
g
e
Lagrange
Lagrange插值得到了可以用来描述S盒非线性性质的多项式。