算法简介
ChaCha20-Poly1305是由ChaCha20流密码和Poly1305讯息认证码(MAC)结合的一种套用在网际网路安全协定中的认证加密算法,由Google公司率先在Andriod移动平台中的Chrome中代替RC4使用。由于其算法精简、安全性强、兼容性强等特点,目前Google致力于全面将其在移动端推广。
设计者
Daniel J. Bernstein教授,德裔美籍数学家、密码学家、程式设计师。Eindhoven University of Technology的数学与计算机教授,设计了salsa、chacha等着名的流密码算法和Poly1305讯息认证码,对国际密码学界影响深远。
算法介绍
ChaCha20
ChaCha系列流密码,作为salsa密码的改良版,具有更强的抵抗密码分析攻击的特性,“20”表示该算法有20轮的加密计算。
由于是流密码,故以位元组为单位进行加密,安全性的关键体现在密钥流生成的过程,即所依赖的伪随机数生成器(PRNG)的强度,加密过程即是将密钥流与明文逐位元组异或得到密文,反之,解密是将密文再与密钥流做一次异或运算得到明文。
算法过程:
(1)ChaCha20的初始矩阵
ChaCha20有一个初始矩阵,矩阵的输入为一个256位的密钥、64位随机数、64位计数器值以及4×32位的常数,它们均填充在32位整型数组中作为初始矩阵。排列方式如下。
0x61707865 0x3320646e 0x79622d32 0x6b206574
Key[0] Key[1] Key[2] Key[3]
Key[4] Key[5] Key[6] Key[7]
Counter[0] Counter[1] nonce[0] nonce[1]
这里256位密钥即流密码的初始密钥,常数为通信双方在握手协定中协商的定值,计数器值取一个从0开始每次自增1的暂存器(64位)中的值,随机数为伪随机数生成器产生,每次生成密钥矩阵时产生不同的随机数。
(2)初始矩阵置换
ChaCha20算法有20轮运算,其中奇数轮次与偶数轮次在执行轮函式前需要分别经过行置换和列置换,故20轮运算可以看作10次叠代,每次叠代先做行置换再做列置换,具体置换方法如下:
我们将初始矩阵存储在一个4×4的数组X中,数组编号从0到15,初始矩阵表示为:
X[0],X[1],X[2],X[3],
X[4],X[5],X[6],X[7],
X[8],X[9],X[A],X[B],
X[C],X[D],X[E],X[F]
经过行置换(奇次)后:
X[0],X[4],X[8],X[C],
X[1],X[5],X[9],X[D],
X[2],X[6],X[A],X[E],
X[3],X[7],X[B],X[F]
经过列置换(偶次)后:
X[0],X[5],X[A],X[F],
X[1],X[6],X[B],X[C],
X[2],X[7],X[8],X[D],
X[3],X[4],X[9],X[E]
(3)轮函式
在矩阵每次完成置换后,都需要执行一次轮函式QUARTERROUND,该函式输入为4个32位串,即4个数组中的元素,输出同样也为4个32位串,这样执行完轮函式后除了数据以外,矩阵结构未发生任何变化,这里拿第一次行置换后的矩阵执行轮函式举例,执行轮函式操作如下。
QUARTERROUND(X[0],X[4],X[8],X[C]);
QUARTERROUND(X[1],X[5],X[9],X[D]);
QUARTERROUND(X[2],X[6],X[A],X[E]);
QUARTERROUND(X[3],X[7],X[B],X[F]);
轮函式具体实施步骤,这里我令轮函式4个输入分别是a,b,c,d,经过一系列变换后得到输出a,b,c,d(更新后的),过程如下:
a+=b;d^=a;d<<<16;
c+=d;b^=c;b<<<12;
a+=b;d^=a;d<<<8;
c+=d;b^=c;b<<<7;
这里“+”是按位逻辑加,“^”是按位异或,“<<<”是循环左移。经过轮函式变换后得到一个新的矩阵,新矩阵再进行列置换,然后在执行轮函式,到此,初始矩阵已经执行过一次行置换、一次列置换以及两次轮函式,完成了一轮叠代即两个周期。
关于轮函数里的计算操作,举个例子,这里令a=0x 11111111,b=0x 01020304,
c=0x 77777777,d=0x 01234567,然后我们分别执行“+”“^”“<<<”运算如下:
c=c+d=0x 77777777+0x 01234567=0x 789abcde;
b=b^c=0x 01020304^0x 789abcde=0x 7998bfda;
b=b<<<7=0x 7998bfda<<<7=0x cc5fed3c;
(4)生成密钥流
在经过轮函式20轮周期(10轮行周期+10轮列周期)后,初始矩阵已经变成了一个新的4×4矩阵,此时将新矩阵与初始矩阵矢量相加,得到的矩阵再拆分倒序序列化处理后即得到一个512位的密钥比特流。
(5)加密
将需要加密的信息(明文)与密钥流按位异或即得到密文,当明文大于512位时,按照上述步骤依次生成密钥流,由于计数器是32位,理论上可以生成
2 ^ 512 bit(256GB)的密钥流,所以一般长度的信息加密完全足够。
(6)解密
接收方使用传送方传输过来的初始密钥、随机数以及协商好的常数和依次递增的计数器值按照ChaCha20的密钥流生成规则产生与加密相同的密钥流,按位与密文异或即可得到明文。
Poly1305讯息认证码
讯息认证码(带密钥的Hash函式):密码学中,通信实体双方使用的一种验证机制,保证讯息数据完整性的一种工具。构造方法由M.Bellare提出,安全性依赖于Hash函式,故也称带密钥的Hash函式。讯息认证码是基于密钥和讯息摘要所获得的一个值,可用于数据源发认证和完整性校验。
Poly1305是Daniel.J.Bernstein创建的讯息认证码,可用于检测讯息的完整性和验证讯息的真实性,现常在网路安全协定(SSL/TLS)中与salsa20或ChaCha20流密码结合使用。
Poly1305讯息认证码的输入为32位元组(256bit)的密钥和任意长度的讯息比特流,经过一系列计算生成16位元组(128bit)的摘要。算法的具体实施步骤如下。
(1) 密钥处理
首先将32位元组的密钥分成16位元组的两组,前16位元组称作“r”,后16位元组称作“s”,申请两个大小为16的数组r[]和s[],对于s,将其以位元组为单位,倒序排列,即s[0]=s[15],s[1]=s[14],……,s[15]=s[0](“=”前后的s[]为两个数组):
这里假设s=01:03:80:8a:fb:0d:b2:fd:4a:bf:f6:af:41:49:f5:1b
倒序排列后s=1bf54941aff6bf4afdb20dfb8a800301(这里去掉分组号, 形成比特流)。
对于r,将r[3],r[7],r[11],r[15]前4位清零(使其小于16),r[4],r[8],r[12]最后两位清零(能够被4整除),前4位清零操作可以通过与00001111逻辑乘得到,后2位清零可以通过与11111100逻辑乘得到,完成清零操作后,类似于s一样对r进行按位元组倒序排列:
这里假设r=85:d6:be:78:57:55:6d:33:7f:44:52:fe:42:d5:06:a8
清零操作后r=85:d6:be:08:54:55:6d:03:7c:44:52:0e:40:d5:06:08
倒序排列后r=806d5400e52447c036d555408bed685
(2) 加密函式
加密之前在系统中分配一个暂存器作为累加器ACC(程式中以数组表示),ACC初始值为0。
明文划分为16位元组一组,这里举例假设明文如下:
000 43 72 79 70 74 6f 67 72 61 70 68 69 63 20 46 6f Cryptographic Fo
016 72 75 6d 20 52 65 73 65 61 72 63 68 20 47 72 6f rum Research Gro
032 75 70 up
(最左边为序号,右边部分为明文所对应的讯息内容)
明文这里为34位元组,所以划分为3组,其中最后一组只有2位元组,加密之前每组明文需要按位元组倒序排列(类似上文中密钥的倒序排列操作),由于有3组明文,故加密需要3轮:
第一次加密:
ACC=00
Block(当前加密块)=6f4620636968706172676f7470797243
在明文前加上字元串01:
Block with 0x01 byte = 016f4620636968706172676f7470797243
累加器值与当前加密块值相加覆盖累加器值(这里是初始值00):
Acc + Block = 016f4620636968706172676f7470797243
与密钥r相乘
(Acc+Block) * r(806d5400e52447c036d555408bed685)=
b83fe991ca66800489155dcd69e8426ba2779453994ac90ed284034da565ecf
对常数P取余后将得到的值赋给ACC
Acc=(Acc+Block)*r)%P = 2c88c77849d64ae9147ddeb88e69c83fc
(P是常数2^130-5:3fffffffffffffffffffffffffffffffb,这里的取余是为了让ACC变成16.5位元组---132bit)
此时ACC中的值为第一轮加密后的结果
后面的加密模式和第一次相同,参与运算的为前一次运算结果(ACC中的值)和当前分组的明文。
第2、3次加密过程如下:
#2
Acc = 2c88c77849d64ae9147ddeb88e69c83fc
Block = 6f7247206863726165736552206d7572
Block with 0x01 byte = 016f7247206863726165736552206d7572
Acc + block = 437febea505c820f2ad5150db0709f96e
(Acc+Block) * r =
21dcc992d0c659ba4036f65bb7f88562ae59b32c2b3b8f7efc8b00f78e548a26
Acc=(Acc+Block)*r)%P = 2d8adaf23b0337fa7cccfb4ea344b30de
#3
Acc = 2d8adaf23b0337fa7cccfb4ea344b30de
Block = 7075
Block with 0x01 byte = 017075
Acc + block = 2d8adaf23b0337fa7cccfb4ea344ca153
(Acc + Block) * r =
16d8e08a0f3fe1de4fe4a15486aca7a270a29f1e6c849221e4a6798b8e45321f
((Acc + Block) * r) % P = 28d31b7caff946c77c8844335369d03a7
3轮加密后得到的ACC=28d31b7caff946c77c8844335369d03a7
将其与密钥s相加(s=1bf54941aff6bf4afdb20dfb8a800301):
Acc + s = 2a927010caf8b2bc2c6365130c11d06a8
可以发现ACC由于计算时附加01这半位元组的原因,一直都是16.5位元组,最后与s(16位元组)相加后仍为16.5位元组,此时将与s相加结果倒序排列后去掉最后半个位元组即得到16位元组的明文摘要:
Tag: a8:06:1d:c1:30:51:36:c6:c2:2b:8b:af:0c:01:27:a9
标準化
该算法的标準化文档为RFC7539