流密码常见概念
流密码(Stream cipher)是对称加密的一种。流密码将明文(Plaintex)看成二进制数据流,通过初始密钥生成加密密钥流 z i z_i zi与明文流 p i p_i pi做异或操作获得密文流 c i c_i ci,即 c i = p i ⊕ z i c_i=p_i\oplus{z_i} ci=pi⊕zi。由于异或操作是可逆的,则解密操作可以由相同的 z i z_i zi获得,即 p i = c i ⊕ z i p_i=c_i\oplus{z_i} pi=ci⊕zi。
反馈位移寄存器
位移寄存器是利用有限长度的密钥生成任意长度的密钥流
z
z
z的重要工具,其工作原理是每生成一位加密密钥比特
z
i
z_i
zi,都利用寄存器自身的某些比特对寄存器的第1位进行更新,其他比特位则右移。下图展示了一个反馈位移寄存器的工作原理,寄存器的第1到4位直接右移,空出来的第1位由寄存器上一步状态的第4,5位做乘法运算并与第1位做异或运算得到。
如果寄存器的更新操作中涉及非线性运算(上图中的乘法操作就是非线性的),则称为非线性反馈位移寄存器(Non-Linear feedback shift register,NFSR),否则称为线性反馈位移寄存器(Linear feedback shift register,LFSR)。
轮函数
轮函数实际上是反馈位移寄存器更新操作的数学表达式,如上图展示的寄存器的表达式为: s 1 r = s 1 r − 1 ⊕ s 4 r − 1 s 5 r − 1 s_1^r=s_1^{r-1}\oplus{s_4^{r-1}s_5^{r-1}} s1r=s1r−1⊕s4r−1s5r−1,其中上标 r r r表示轮数,下标 i i i表示寄存器的第 i i i位。通常,为了使流密码足够复杂,在输出第1位加密密钥比特之前要进行 R R R次轮函数迭代。
Ancry
现实使用的流密码为了密码强度都设计得非常复杂(密钥多达几十甚至几百位,初始轮数
R
R
R也非常大),因此几乎只能从数学上对密码进行立方攻击,而实际上的破解过程需要交由计算机,这对初学者学习立方攻击造成了一定的困扰。为此本文设计了一个名为Ancry的密码算法,该算法只包含5个密钥比特,初始轮数
R
=
10
R=10
R=10,每一个工作步骤都十分简洁明了。Ancry流密码的工作流程如下图所示。
Ancry的第1个寄存器的初始状态存放5bit的密钥,第2个寄存器的初始状态存放5bit的公开变量,这些变量是公开可控的。令
s
=
(
x
,
v
)
s=(x,v)
s=(x,v),则Ancry的轮函数的数学表达式为:
s
1
r
=
s
8
r
−
1
+
s
7
r
−
1
s
10
r
−
1
,
(
1
)
s_1^r=s_8^{r-1}+s_7^{r-1}s_{10}^{r-1},(1)
s1r=s8r−1+s7r−1s10r−1,(1)
s
6
r
=
s
2
r
−
1
+
s
4
r
−
1
s
5
r
−
1
,
(
2
)
s_6^r=s_2^{r-1}+s_4^{r-1}s_5^{r-1},(2)
s6r=s2r−1+s4r−1s5r−1,(2)
z
i
=
s
3
i
+
9
+
s
9
i
+
9
z_i=s_3^{i+9}+s_9^{i+9}
zi=s3i+9+s9i+9
由上述第3个表达式可以得出,输出第一位加密密钥比特之前进行了10次初始化迭代,即
R
=
10
R=10
R=10。以下为Ancry的Python实现代码。
# Author: AngieJC
# Date: 2022/01/17
# Mail: htk90uggk@outlook.com
import sys
def Ancry(vec_x, vec_v, plaintext): # 流密码,vec_x为密钥x,vec_v为公开参数v,plaintext为明文
z = [] # z为输出的密钥比特
# 初始轮数r=10
R = 10
x = vec_x.copy()
v = vec_v.copy()
for i in range(R):
for j in range(len(vec_x) - 1):
x[j + 1] = vec_x[j]
x[0] = vec_v[2] ^ (vec_v[1] * vec_v[4])
for j in range(len(vec_v) - 1):
v[j + 1] = vec_v[j]
v[0] = vec_x[1] ^ (vec_x[3] * vec_x[4])
vec_x = x.copy()
vec_v = v.copy()
# 计算z
cyphertext = []
for i in range(len(plaintext)):
# 添加一位密钥比特
z.append(vec_x[2] ^ vec_v[3])
cyphertext.append(z[-1] ^ plaintext[i])
# 更新x与v
for j in range(len(vec_x) - 1):
x[j + 1] = vec_x[j]
x[0] = vec_v[2] ^ (vec_v[1] * vec_v[4])
for j in range(len(vec_v) - 1):
v[j + 1] = vec_v[j]
v[0] = vec_x[1] ^ (vec_x[3] * vec_x[4])
vec_x = x.copy()
vec_v = v.copy()
return cyphertext
if __name__ == "__main__":
x = input("密钥:")
if(len(x) != 5):
print("密钥长度应当为5!")
sys.exit()
vec_x = []
for i in range(len(x)):
vec_x.append(int(x[i]))
# vec_x = [int(x[0]), int(x[1])]
v = input("公开参数:")
if (len(v) != 5):
print("公开参数长度应当为5!")
sys.exit()
vec_v = []
for i in range(len(v)):
vec_v.append(int(v[i]))
# vec_v = [int(v[0]), int(v[1])]
p = input("明文:")
plaintext = []
for i in range(len(p)):
plaintext.append(int(p[i]))
cyphertext = Ancry(vec_x, vec_v, plaintext)
print("明文:", plaintext)
print("密文:", cyphertext)
Ancry简要分析
在介绍立方攻击之前先对Ancry进行一些简单的分析,以方便理解立方攻击的工作原理。
由于流密码的加密模式为
c
i
=
p
i
⊕
z
i
c_i=p_i\oplus{z_i}
ci=pi⊕zi,因此攻击者知道明文
p
p
p和明文
c
c
c就相当于知道密钥流
z
z
z。
Ancry第1位加密密钥比特
z
z
z(在不引起歧义的情况下,以下使用
z
z
z代替
z
1
z_1
z1)的表达式为:
z
=
s
3
10
+
s
9
10
z=s_3^{10}+s_9^{10}
z=s310+s910根据反馈位移寄存器的特点我们知道,一个比特生成并保存到寄存器的第一个位置后,一直在做右移操作,直到右移到寄存器的最右边被丢弃,而值并不发生任何变化,因此有
s
3
10
=
s
1
8
s_3^{10}=s_1^8
s310=s18,
s
9
10
=
s
6
7
s_9^{10}=s_6^7
s910=s67。再根据等式(1)和等式(2),有
z
=
s
8
7
+
s
7
7
s
10
7
+
s
2
6
+
s
4
6
s
5
6
z=s_8^7+s_7^7s_{10}^7+s_2^6+s_4^6s_5^6
z=s87+s77s107+s26+s46s56。对
z
z
z一直进行递归分解,最终可以得到:
z
=
v
1
+
x
5
v
3
+
x
1
v
3
+
x
1
v
2
v
5
+
x
5
v
3
+
x
5
v
2
v
5
+
x
2
x
3
x
5
+
x
4
v
2
v
3
+
x
4
v
2
v
5
+
x
2
x
3
x
4
v
2
+
v
2
v
3
+
x
2
x
3
v
2
v
3
+
v
2
v
5
+
x
2
x
3
v
2
v
5
+
v
1
v
3
v
4
+
x
2
x
3
v
1
v
3
v
4
+
v
1
v
2
v
4
v
5
+
x
2
x
3
v
1
v
2
v
4
v
5
+
x
4
+
x
3
v
1
+
v
1
v
2
+
v
1
v
4
+
x
5
v
2
v
3
+
x
5
v
1
v
3
v
4
z=v_1+x_5v_3+x_1v_3+x_1v_2v_5+x_5v_3+x_5v_2v_5+x_2x_3x_5+x_4v_2v_3+x_4v_2v_5\\+x_2x_3x_4v_2+v_2v_3+x_2x_3v_2v_3+v_2v_5+x_2x_3v_2v_5+v_1v_3v_4+x_2x_3v_1v_3v_4\\+v_1v_2v_4v_5+x_2x_3v_1v_2v_4v_5+x_4+x_3v_1+v_1v_2+v_1v_4+x_5v_2v_3+x_5v_1v_3v_4
z=v1+x5v3+x1v3+x1v2v5+x5v3+x5v2v5+x2x3x5+x4v2v3+x4v2v5+x2x3x4v2+v2v3+x2x3v2v3+v2v5+x2x3v2v5+v1v3v4+x2x3v1v3v4+v1v2v4v5+x2x3v1v2v4v5+x4+x3v1+v1v2+v1v4+x5v2v3+x5v1v3v4这个表达式称为
z
z
z的代数范式(Algebraic Normal Form, ANF)。以上表达式是
z
1
z_1
z1的代数表达式,理论上来说,如果我们能够推出所有
z
i
z_i
zi的代数表达式,那么在实际加密过程中,也可以完全抛弃反馈位移寄存器而直接使用代数表达式计算密钥流。
读者也可以自己推出 z z z的代数表达式
注意:由于二进制中有以下特性
性质1: 1 n = 1 , 0 n = 0 1^n=1,0^n=0 1n=1,0n=0
性质2: 1 + 1 = 0 , 0 + 0 = 0 1+1=0,0+0=0 1+1=0,0+0=0因此 z z z的代数表达式中 x n x^n xn可以简化成 x x x, x + x x+x x+x可以直接约去
立方攻击
立方攻击由Dinur和Shamir在2009年的欧密会上提出,其中的Shamir就是设计了RSA算法的三位作者中的S。
立方攻击只关心输出的第1位加密密钥比特,其中心思想是:选取一个关于公开变量
v
v
v的立方索引
I
I
I,把
z
z
z的代数表达式分解成:
z
=
p
(
x
,
v
)
t
I
+
q
(
x
,
v
)
z=p(x,v)t_I+q(x,v)
z=p(x,v)tI+q(x,v)其中
t
I
t_I
tI为被
I
I
I索引的公开变量的连乘,即
t
I
=
∏
i
∈
I
v
i
t_I=\prod_{i\in{I}}v_i
tI=∏i∈Ivi。说直白一点就是对
z
z
z的代数表达式提取公因子
t
I
t_I
tI,
p
(
x
,
v
)
p(x,v)
p(x,v)为提取
t
I
t_I
tI之后的多项式,
q
(
x
,
v
)
q(x,v)
q(x,v)则为无法提取
t
I
t_I
tI的多项式。
例子1:
x = ( x 1 , x 2 , x 3 ) , v = ( v 1 , v 2 , v 3 ) x=(x_1,x_2,x_3),v=(v_1,v_2,v_3) x=(x1,x2,x3),v=(v1,v2,v3)
z = x 1 v 1 v 3 + x 1 v 2 + x 2 v 1 v 2 + x 2 x 3 v 1 v 3 + v 1 v 3 z=x_1v_1v_3+x_1v_2+x_2v_1v_2+x_2x_3v_1v_3+v_1v_3 z=x1v1v3+x1v2+x2v1v2+x2x3v1v3+v1v3
令 I = { 1 , 3 } I=\{1,3\} I={1,3}
则 z = ( x 1 + x 2 x 3 + 1 ) v 1 v 3 + x 1 v 1 + x 2 v 1 v 2 z=(x_1+x_2x_3+1)v_1v_3+x_1v_1+x_2v_1v_2 z=(x1+x2x3+1)v1v3+x1v1+x2v1v2
其中 p ( x , v ) = x 1 + x 2 x 3 + 1 , t I = v 1 v 3 , q ( x , v ) = x 1 v 1 + x 2 v 1 v 2 p(x,v)=x_1+x_2x_3+1,t_I=v_1v_3,q(x,v)=x_1v_1+x_2v_1v_2 p(x,v)=x1+x2x3+1,tI=v1v3,q(x,v)=x1v1+x2v1v2
将 z z z分解成 p ( x , v ) t I + q ( x , v ) p(x,v)t_I+q(x,v) p(x,v)tI+q(x,v)后我们可以得到一个非常有趣的结论,即 ∑ v ∈ C z = p ( x , v ) , ( 3 ) \sum_{v\in{C}}z=p(x,v),(3) v∈C∑z=p(x,v),(3)其中 C C C是 I I I所代表的立方,即 v i ∈ I v_i\in{I} vi∈I取遍所有的 2 ∣ I ∣ 2^{|I|} 2∣I∣个0-1组合, ∣ I ∣ |I| ∣I∣代表集合 I I I中元素的个数。
例子1续:
I = { 1 , 3 } I=\{1,3\} I={1,3}
则 C = { ( 0 , 0 , 0 ) ( 0 , 0 , 1 ) ( 1 , 0 , 0 ) ( 1 , 0 , 1 ) C= \begin{cases} (\textcolor{red}0,0,\textcolor{red}0)\\ (\textcolor{red}0,0,\textcolor{red}1)\\ (\textcolor{red}1,0,\textcolor{red}0)\\ (\textcolor{red}1,0,\textcolor{red}1) \end{cases} C=⎩⎪⎪⎪⎨⎪⎪⎪⎧(0,0,0)(0,0,1)(1,0,0)(1,0,1)
由于 v 2 v_2 v2并不是立方元素,可以是任意值或者直接置0
证明:
∑ v ∈ C z = ∑ v ∈ C p ( x , v ) t I + ∑ v ∈ C q ( x , v ) \sum_{v\in{C}}z=\sum_{v\in{C}}p(x,v)t_I+\sum_{v\in{C}}q(x,v) v∈C∑z=v∈C∑p(x,v)tI+v∈C∑q(x,v)
p ( x , v ) t I = { p ( x , v ) 所 有 的 v i ∈ I 均 等 于 1 ( C 中 的 最 后 一 种 情 况 ) 0 C 中 其 他 三 种 情 况 p(x,v)t_I= \begin{cases} p(x,v)&&所有的v_i\in{I}均等于1(C中的最后一种情况)\\ 0&&C中其他三种情况 \end{cases} p(x,v)tI={p(x,v)0所有的vi∈I均等于1(C中的最后一种情况)C中其他三种情况
则 ∑ v ∈ C p ( x , v ) t I = p ( x , v ) \sum_{v\in{C}}p(x,v)t_I=p(x,v) ∑v∈Cp(x,v)tI=p(x,v)
又由于 v 2 = 0 v_2=0 v2=0,则 q ( x , v ) = x 1 v 1 + x 2 v 1 v 2 = x 1 v 1 q(x,v)=x_1v_1+x_2v_1v_2=x_1v_1 q(x,v)=x1v1+x2v1v2=x1v1, ∑ v ∈ C q ( x , v ) = 0 x 1 + 0 x 1 + 1 x 1 + 1 x 1 = x 1 + x 1 \sum_{v\in{C}}q(x,v)=0x_1+0x_1+1x_1+1x_1=x_1+x_1 ∑v∈Cq(x,v)=0x1+0x1+1x1+1x1=x1+x1,由性质2可知, ∑ v ∈ C q ( x , v ) = 0 \sum_{v\in{C}}q(x,v)=0 ∑v∈Cq(x,v)=0
事实上,由于 C C C中共有 2 ∣ I ∣ 2^{|I|} 2∣I∣个元素,即偶数个元素,根据性质2, ∑ v ∈ C q ( x , v ) \sum_{v\in{C}}q(x,v) ∑v∈Cq(x,v)必然为0
综上, ∑ v ∈ C z = p ( x , v ) \sum_{v\in{C}}z=p(x,v) ∑v∈Cz=p(x,v)得证
现在我们我们把目光再次聚焦到Ancry, z z z的表达式已经知道,根据立方 I I I选取的不同,可以将 z z z分解成一下几种形式(由于 ∑ v ∈ C q ( x , v ) = 0 \sum_{v\in{C}}q(x,v)=0 ∑v∈Cq(x,v)=0,我们并不关心其具体表达式,因此在下列例子中都没有显式给出 q ( x , v ) q(x,v) q(x,v)的表达式):
- I = { 1 , 2 , 3 , 4 , 5 } , 无 法 分 解 I=\{1,2,3,4,5\},无法分解 I={1,2,3,4,5},无法分解
- I = { 1 , 2 , 4 , 5 } , p ( x , v ) = x 2 x 3 + 1 I=\{1,2,4,5\},p(x,v)=x_2x_3+1 I={1,2,4,5},p(x,v)=x2x3+1
- I = { 1 , 3 , 4 } , p ( x , v ) = x 2 x 3 + x 5 + 1 I=\{1,3,4\},p(x,v)=x_2x_3+x_5+1 I={1,3,4},p(x,v)=x2x3+x5+1
- I = { 2 , 3 } , p ( x , v ) = x 2 x 3 + x 4 + x 5 + 1 I=\{2,3\},p(x,v)=x_2x_3+x_4+x_5+1 I={2,3},p(x,v)=x2x3+x4+x5+1
- I = { 2 , 5 } , p ( x , v ) = x 1 + x 2 x 3 + x 4 + x 5 + 1 I=\{2,5\},p(x,v)=x_1+x_2x_3+x_4+x_5+1 I={2,5},p(x,v)=x1+x2x3+x4+x5+1
- I 只 有 1 个 元 素 分 解 意 义 不 大 I只有1个元素分解意义不大 I只有1个元素分解意义不大
Ancry攻击实例
基本信息:
x = ( 1 , 0 , 0 , 1 , 0 ) x=(1,0,0,1,0) x=(1,0,0,1,0)
I = { 2 , 5 } I=\{2,5\} I={2,5}
p ( x , v ) = x 1 + x 2 x 3 + x 4 + x 5 + 1 p(x,v)=x_1+x_2x_3+x_4+x_5+1 p(x,v)=x1+x2x3+x4+x5+1
根据实际加密结果,有:
z
=
{
1
,
v
=
(
0
,
0
,
0
,
0
,
0
)
1
,
v
=
(
0
,
0
,
0
,
0
,
1
)
1
,
v
=
(
0
,
1
,
0
,
0
,
0
)
0
,
v
=
(
0
,
1
,
0
,
0
,
1
)
z= \begin{cases} 1,v=(0,\textcolor{red}0,0,0,\textcolor{red}0)\\ 1,v=(0,\textcolor{red}0,0,0,\textcolor{red}1)\\ 1,v=(0,\textcolor{red}1,0,0,\textcolor{red}0)\\ 0,v=(0,\textcolor{red}1,0,0,\textcolor{red}1) \end{cases}
z=⎩⎪⎪⎪⎨⎪⎪⎪⎧1,v=(0,0,0,0,0)1,v=(0,0,0,0,1)1,v=(0,1,0,0,0)0,v=(0,1,0,0,1)
则
∑
v
∈
C
z
=
p
(
x
,
v
)
=
x
1
+
x
2
x
3
+
x
4
+
x
5
+
1
=
1
+
1
+
1
+
0
=
1
\sum_{v\in{C}}z=p(x,v)=x_1+x_2x_3+x_4+x_5+1=1+1+1+0=1
∑v∈Cz=p(x,v)=x1+x2x3+x4+x5+1=1+1+1+0=1,即
x
1
+
x
2
x
3
+
x
4
+
x
5
=
0
,
(
4
)
\textcolor{red}{x_1+x_2x_3+x_4+x_5=0},(4)
x1+x2x3+x4+x5=0,(4)
在没有等式(4)之前,想要通过穷举的方式破解Ancry需要遍历
2
5
=
32
2^5=32
25=32种密钥可能的取值。而符合等式(4)的密钥取值为
x
=
{
(
0
,
0
,
0
,
0
,
0
)
(
0
,
0
,
0
,
1
,
1
)
(
0
,
0
,
1
,
0
,
0
)
(
0
,
0
,
1
,
1
,
1
)
(
0
,
1
,
0
,
0
,
0
)
(
0
,
1
,
0
,
1
,
1
)
(
0
,
1
,
1
,
0
,
1
)
(
0
,
1
,
1
,
1
,
0
)
(
1
,
0
,
0
,
0
,
1
)
(
1
,
0
,
0
,
1
,
0
)
,
正
确
密
钥
(
1
,
0
,
1
,
0
,
1
)
(
1
,
0
,
1
,
1
,
0
)
(
1
,
1
,
0
,
0
,
1
)
(
1
,
1
,
0
,
1
,
0
)
(
1
,
1
,
1
,
0
,
0
)
(
1
,
1
,
1
,
1
,
1
)
x= \begin{cases} (0, 0, 0, 0, 0)\\ (0, 0, 0, 1, 1)\\ (0, 0, 1, 0, 0)\\ (0, 0, 1, 1, 1)\\ (0, 1, 0, 0, 0)\\ (0, 1, 0, 1, 1)\\ (0, 1, 1, 0, 1)\\ (0, 1, 1, 1, 0)\\ (1, 0, 0, 0, 1)\\ \textcolor{red}{(1, 0, 0, 1, 0)},正确密钥\\ (1, 0, 1, 0, 1)\\ (1, 0, 1, 1, 0)\\ (1, 1, 0, 0, 1)\\ (1, 1, 0, 1, 0)\\ (1, 1, 1, 0, 0)\\ (1, 1, 1, 1, 1) \end{cases}
x=⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧(0,0,0,0,0)(0,0,0,1,1)(0,0,1,0,0)(0,0,1,1,1)(0,1,0,0,0)(0,1,0,1,1)(0,1,1,0,1)(0,1,1,1,0)(1,0,0,0,1)(1,0,0,1,0),正确密钥(1,0,1,0,1)(1,0,1,1,0)(1,1,0,0,1)(1,1,0,1,0)(1,1,1,0,0)(1,1,1,1,1)共16种情况,再加上遍历立方
C
C
C的4种情况,一共需要20个步骤即可破解Ancry,比穷举破解Ancry的32种情况要优秀不少。
参考文献
[1] Delaune, S., Derbez, P., Gontier, A., & Prud’homme, C. (2021). A Simpler Model for Recovering Superpoly onTrivium. Cryptology ePrint Archive.