本文将详细介绍GOST R 34.12-2015中定义为Grasshopper的块加密算法。 它基于什么,块密码算法的数学是什么,以及如何在Java中实现该算法。
谁,如何,何时以及为什么开发这种算法将不在本文讨论范围之内,因为在这种情况下,我们几乎没有兴趣,除了:
蚱= =库兹涅佐夫(Nechaev AND Company)。

由于密码学主要基于数学,因此进一步的解释不会引起很多问题,因此您应该首先分析构建该算法的基本概念和数学函数。
菲尔兹·加洛瓦(Fields Galois)
Galois字段的算术是多项式算术,也就是说,该字段的每个元素都是一个多项式。 任何操作的结果也是该字段的元素。 特定的Galois字段由固定范围的数字组成。 场特性称为一些质数p。 现场订单,即 其元素的数量是一定的自然特征
p 米 ,其中m ϵN。对于m = 1,该字段称为简单字段。 在m> 1的情况下,为了形成场,还需要生成次数为m的生成多项式;这种场称为扩展场。
G f (p m ) -指定Galois油田。 生成多项式是不可约的,即是简单的(类似于质数,它可以被1整除而没有余数)。 由于处理任何信息都使用字节,并且字节为8位,因此将其作为字段
G f (2 8 ) 和生成多项式:
x 8 + x 7 + x 6 + x + 1。
但是,首先,我们将在一个更简单的领域中分析基本操作
G f (2 3 ) 与生成多项式
f ( x ) = x 3 + x + 1 。
加法运算
最简单的是加法运算,在Galois场算术中,该运算是简单的按位加法模2(R)。
我立即提请注意以下事实:这里的“ +”符号是指按位XOR的操作,而不是通常形式的加法。
HOR函数的真值表

一个例子:
5 + 3 = 101 + 011 = 110 2 = 6 10在多项式形式中,该运算看起来像
(x2+1)+(x+1)=x2+x=1102=610
乘法运算
要进行乘法运算,必须将数字转换为多项式形式:
5=1012=1∗x2+0∗x1+1∗x0=x2+1
如您所见,多项式形式的数字是一个多项式,其系数是数字的二进制表示形式中的数字值。
将多项式形式的两个数相乘:
5∗7=(x2+1)∗(x2+x+1)=x4+x3+x2+x2+x+1=
=x4+x3+x+1=110112=2710
乘法结果27不在使用的字段中。
Gf(23) (如上所述,它由0到7组成)。 为了解决这个问题,有必要使用生成多项式。
还假设x满足方程
f(x)=x3+x+1=0 然后

让我们做一个乘法表:

非常重要的是伽罗瓦域元素的度表。 类似于乘法,也以多项式形式进行幂次提升。
一个例子:
52=(x2+1)2=x4+x2+x2+1=x4+x2+x+x2+x+1=
=x(x3+x+1)+x2+x+1=x2+x+1=1112=710
因此,我们编译一个度表:

度表是循环的:第七度对应于零,因此第八度对应于第一个,依此类推。 您可以根据需要进行检查。
在Galois字段中,有一个原始术语的概念-字段的度数包含该字段的所有非零元素的元素。 可以看出,所有元素都与此条件相对应(当然,除了1之外)。 但是,并非总是如此。
对于我们正在考虑的字段,即具有特征2的情况下,始终选择2。作为基本成员,鉴于其属性,可以根据基本成员的程度来表示字段的任何元素。

一个例子:
5=26.7=25使用此属性,并考虑度表的周期性,我们将尝试再次将数字相乘:
5∗7=26∗25=2((6+5)=2(11mod7)=24=6
结果与我们先前计算的相符。
现在让我们进行划分:
6/5=24/26=2((4−6)=2((−2)mod7)=25=7
获得的结果也是正确的。
好吧,为了完整起见,让我们来看一下提升能力:
5 ^ 2 =(2 ^ 6)^ 2 = 2 ^ {{(6 * 2)} = 2 ^ {(12mod7)} = 2 ^ 5 = 7
这样的乘法和除法方法比使用多项式的实际运算要简单得多,并且对于它们而言,不需要存储大型的乘法表,而只需存储原始字段成员的一行度。
现在回到我们的领域
Gf(28)字段的零元素为1,第一个元素为2,从第二个元素到第254个元素的每个后续元素的计算方式为前一个元素乘以2,并且如果该元素在字段之外,即其值大于
(28−1) 然后对数字进行异或
19510 ,该数字表示该字段的不可约多项式
x8+x7+x6+x+1=28+27++26+2+1=451 ,我们将此号码带入现场
451−256=$19 。 第255个元素再次为1。因此,我们有一个包含256个元素的字段,即一个完整的字节集,并且已经分析了在该字段中执行的基本操作。

字段的二的幂表
Gf(28)为什么需要它-Grasshopper算法中的部分计算是在Galois字段中执行的,因此,计算结果是该字段的元素。
Feistel网络
Feistel Network是1971年由IBM的Horst Feistel开发的一种块加密方法。 如今,Feistel的网络已成为众多加密协议的基础。
Feistel网络使用明文块运行:
- 该块分为两个相等的部分-左L和右R。
- 左子块L通过功能f使用键K进行更改:X = f(L,K)。 作为功能,可以有任何变换。
- 将得到的子块X与右子块R模2加,后者保持不变:X = X +R。
- 生成的零件被互换并粘合。
此动作序列称为Feistel电池。
图1. Feistel细胞Feistel网络由几个单元组成。 在第一单元的输出处获得的子块进入第二单元的输入,第二单元的结果子块进入第三单元的输入,依此类推。
加密算法
现在,我们已经熟悉了所使用的操作,并且可以继续讨论主要主题-Grasshopper加密算法。
该算法的基础是所谓的SP网络-替代置换网络。 基于SP网络的密码在输入端接收一个块和一个密钥,并执行由替换阶段和置换阶段组成的几轮交替回合。 蚱hopper执行了九轮完整回合,每个回合包括三个连续的操作:
- 对密钥和输入数据块应用循环密钥或按位XOR的操作;
- 非线性转换,即按照表简单地将一个字节替换为另一个字节;
- 线性变换。 来自该块的每个字节在Galois字段中乘以该序列的系数之一(148、32、133、16、194、192、1、251、1、192、194、16、133、32、148、1)字节号(为从15号到0号的序列号提供了一个序列,如图所示)。 字节以模2方式加在一起,并且该块的所有16个字节都向低位移位,并且将写入的结果数替换为读取的字节。

最后的第十轮未完成,仅包括第一个XOR操作。
Grasshopper是一种块算法,适用于128位或16字节长的数据块。 密钥长度为256位(32字节)。
图2.数据块的加密和解密方案该图显示了操作顺序,其中S是非线性变换,L是线性变换,Ki是圆键。 问题立即出现-圆形键从何而来。
回合密钥形成
迭代(或回合)密钥是基于主密钥通过某些转换而获得的,众所周知,其长度为256位。 此过程首先将主密钥分成两半,这样就获得了第一对圆形密钥。 Feistel网络的八次迭代用于生成每个后续的配对密钥,在每次迭代中使用一个常数,该常数是通过将算法的线性变换应用于迭代次数的值来计算的。
获取迭代(圆形)密钥的方案如果我们回想一下图1,那么左子块L是原始键的左半部分,右子块R是原始键的右半部分,K是常数Ci,函数f是操作序列R XOR Ci,非线性变换,线性变换。
使用迭代序列号的L变换来获得迭代常数Ci。
因此,为了加密文本块,我们首先需要计算32个迭代常数,然后根据该密钥计算10个回合密钥,然后执行图2所示的操作序列。
让我们从计算常量开始:
第一常量
C1=110=000000012=0116 ,但是,我们算法中的所有变换都是使用16个字节长的块执行的,因此有必要用该块的长度来补充常量,即在右边添加15个零字节,我们得到
C1=01000000000000000000000000000000
将其乘以以下序列(1,148,32,133,16,194,192,1,251,1,192,194,16,133,32,148),如下所示:
a15=a15∗148+a14∗32+a13∗133+a12∗16+
+a11∗194+a10∗192+a9∗1+a8∗251+a7∗1+a6∗192+
+a5∗194+a4∗16+a3∗133+a2∗32+a1∗148+a0∗1
(此等式在Galois字段的操作中给出)
由于除零字节以外的所有内容都等于0,并且零字节乘以1,我们得到1,并将其写入数字的高位,将所有字节移至低位,则得到:
C1=00000000000000000000000000000001
重复相同的操作。 这次
a15=1 ,所有其他字节均为0,因此,术语中仅第一个保留
a15∗148=1∗148=14810=9416 我们得到:
C1=00000000000000000000000000000194
我们进行第三次迭代,这是两个非零项:
a15∗148+a14∗32=148∗148+1∗32=
=10010100∗10010100+00000001∗00100000=
=(x7+x4+x2)∗(x7+x4+x2)+1∗x5=x14+x8+x4+x5=
=x6(x8+x7+x6+x+1)+x13+x12+x7+x6+x8+x4+x5=
=x5(x8+x7+x6+x+1)+x11+x5+x7+x8+x4+x5=
=x3(x8+x7+x6+x+1)+x10+x9+x3+x8+x7=
=x2(x8+x7+x6+x+1)+x2+x7=x7+x2=13210
13210=8416
根据度数表,可以轻松解决:
148∗148+1∗32=245∗245+25=290+25=164+32=132
C1=00000000000000000000000000000000019484
此外,一切都完全相同,每个常数只有16次迭代
C1=000000000000000000000000019484DD
C1=0000000000000000000000019484DD10
C1=00000000000000000000019484DD10BD
C1=000000000000000000019484DD10BD27
C1=0000000000000000019484DD10BD275D
C1=00000000000000019484DD10BD275DB8
C1=000000000000019484DD10BD275DB87A
C1=0000000000019484DD10BD275DB87A48
C1=00000000019484DD10BD275DB87A486C
C1=000000019484DD10BD275DB87A486C72
C1=0000019484DD10BD275DB87A486C727
C1=00019484DD10BD275DB87A486C7276A2
最后一个常量:
C1=019484DD10BD275DB87A486C7276A2E6
其他常数:
C2=02EBCB7920B94EBAB3F490D8E4EC87DC
C3=037F4FA4300469E70B8ED8B4969A25B2
C4=041555F240B19CB7A52BE3730B1BCD7B
C5=0581D12F500CBBEA1D51AB1F796D6F15
C6=06FE9E8B6008D20D16DF73ABEFF74AA7
C7=076A1A5670B5F550AEA53BC79D81E8C9
C8=082AAA2780A1FBAD895605E6163659F6
C9=09BE2EFA901CDCF0312C4D8A6440FB98
C10=0AC1615EA018B5173AA2953EF2DADE2A
C11=0B55E583B0A5924A82D8DD5280AC7C44
C12=0C3FFFD5C010671A2C7DE6951D2D948D
C13=0DAB7B08D0AD40479407AEF96F5B36E3
C14=0ED434ACE0A929A09F89764DF9C11351
C15=0F40B071F0140EFD27F33E218BB7B13F
C16=1054974EC3813599D1AC0A0F2C6CB22F
C17=11C01393D33C12C469D642635E1A1041
C18=12BF5C37E3387B2362589AD7C88035F3
C19=132BD8EAF3855C7EDA22D2BBBAF6979D
C20=1441C2BC8330A92E7487E97C27777F54
C21=15D54661938D8E73CCFDA1105501DD3A
C22=16AA09C5A389E794C77379A4C39BF888
C23=173E8D18B334C0C97F0931C8B1ED5AE6
C24=187E3D694320CE3458FA0FE93A5AEBD9
C25=19EAB9B4539DE969E0804785482C49B7
C26=1A95F6106399808808EEB0E9F31DEB66C05
C27=1B0172CD7324A7D35374D75DACC0CE6B
C28=1C6B689B03915283FDD1EC9A314126A2
C29=1DFFEC46132C75DE45ABA4F6433784CC
C30=1E80A3E223281C394E257C42D5ADA17E
C31=1F14273F33953B64F65F342EA7DB0310
C32=20A8ED9C45C16AF1619B141E58D8A75E
现在,我们将根据上述方案计算轮回密钥,并使用加密密钥:
K=7766554433221100FFEEDDCCBBAA9988
EFCDAB89674523011032547698BADCFE
然后
K1=7766554433221100FFEEDDCCBBAA9988
K2=EFCDAB89674523011032547698BADCFE
K1 将是Feistel网络的左侧子块,并且
K2 -对
让我们做手术
K1+C1第一个字节
K1 等于
7716=011101112第一个字节
C1 等于
0116=000000012011101112+000000012=011101102=7616
结果,其余字节以相同的方式转换
X(K1,C1)=K1+C1 :
X(K1,C1)=76F2D199239F365D479495A0C9DC3BE6
接下来,我们执行非线性变换
S(X(K1,C1)) 。 它根据表执行:
非线性换算表数字0替换为252、1替换为238、17替换为119等。
7616=11810
S(118)=13810=8A16
S(X(K1,C1))=8A741BE85A4A8FB7AB7A94A737CA9809
现在执行线性变换
L(S(X(K1,C1))) ,在计算迭代常数时会对其进行详细考虑,因此在此我们仅给出最终结果:
L(S(X(K1,C1)))=A644615E1D0757926A5DB79D9940093D
根据Feistel单元的方案,我们对右侧的子块(即
K2 :
X(L(S(X(X(K1,C1))),K2)=4989CAD77A4274937A6FE3EB01FAD5C3
在第一个Feistel单元的输出处获得的结果是:
EFCDAB89674523011032547698BADCFE4989CAD77A4274937A6FE3EB01FAD5C3
此值减半,然后转到第二个Feistel单元格的输入,其中第二个常数已使用
C2 。 经过八个单元格后,我们得到以下两个键
K3 和
K4 。 我们将对它们执行Feistel网络的八次迭代,获取下一个密钥对,依此类推。 每个键对八次迭代,因为我们最初有第一对,所以总共执行32次迭代,每个迭代都有自己的常数。
其余键:
K3=448CC78CEF6A8D2243436915534831DB
K4=04FD9F0AC4ADEB1568ECCFE9D853453D
K5=ACF129F44692E5D3285E4AC468646457
K6=1B58DA3428E832B532645C16359407BD
K7=B198005A26275770DE45877E7540E651
K8=84F98622A2912AD73EDD9F7B0125795A
K9=17E5B6CD732FF3A52331C77853E244BB
K10=43404A8EA8BA5D755BF4BC1674DDE972
块加密
我们计算了所有密钥,现在终于可以直接对文本块进行加密了,如果您仔细阅读了上面编写的所有内容,则对文本进行加密将不会很困难,因为已详细检查了此过程中使用的所有操作及其顺序。
取纯文本块:
T=8899AABBCCDDEEFF0077665544332211
执行操作顺序X,S,L
X(T,K1)=FFFFFFFFFFFFFFFFFFFFFF99BB99FF99BB99
S(X(T,K1))=B6B6B6B6B6B6B6B6B6E87DE8B6E87DE8
L(S(X(T,K1)))=30081449922F4ACFA1B055E386B697E2
T1=30081449922F4ACFA1B055E386B697E2
X(T1,K2)=DFC5BFC0F56A69CEB18201951E0C4B1C
S(X(T1,K2))=61AC3B07F47891E74524EE945F23A214
L(S(X(T1,K2)))=7290C6A158426FB396D562087A495E28
T2=7290C6A158426FB396D562087A495E28
依此类推,最终结果将如下所示:
T10=CDEDD4B9428D465A3024BCBE909D677F
块解密
要解密文本,您需要以相反的顺序使用逆运算(请参见图2)。
XOR运算本身就是逆运算,而运算S的逆运算将根据下表进行替换:
逆非线性变换表对函数L的逆变换将是:
a0=a15∗148+a14∗32+a13∗133+a12∗16+
+a11∗194+a10∗192+a9∗1+a8∗251+a7∗1+a6∗192+a5∗194+
+a4∗16+a3∗133+a2∗32+a1∗148+a0∗1
并向高级方向转变。 (重复操作16次)
Java实现
首先,我们定义必要的常数
static final int BLOCK_SIZE = 16;
让我们创建所有主要功能:
要实现函数L,我们需要几个辅助函数,一个用于计算Galois字段中数字的乘法,另一个用于移位。
反函数:
好了,主要功能
static byte[] key_1 = {0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00, (byte) 0xff, (byte) 0xee, (byte) 0xdd, (byte) 0xcc, (byte) 0xbb, (byte) 0xaa, (byte) 0x99, (byte) 0x88}; static byte[] key_2 = {(byte) 0xef, (byte) 0xcd, (byte) 0xab, (byte) 0x89, 0x67, 0x45, 0x23, 0x01, 0x10, 0x32, 0x54, 0x76, (byte) 0x98, (byte) 0xba, (byte) 0xdc, (byte) 0xfe}; static byte[] blk = DatatypeConverter.parseHexBinary("8899aabbccddeeff0077665544332211"); public static void main(String[] args) { GOST_Kuz_Expand_Key(key_1, key_2); byte[] encriptBlok = GOST_Kuz_Encript(blk); System.out.println(DatatypeConverter.printHexBinary(encriptBlok)); byte[] decriptBlok = GOST_Kuz_Decript(encriptBlok); System.out.println(DatatypeConverter.printHexBinary(decriptBlok)); }
我们学会了对数据块进行加密,以便对长度大于数据块长度的文本进行加密;标准-GOST 34.13-2015中描述了几种模式:
- 简单替换模式(电子密码书,ECB);
- 伽玛模式(计数器,点击率);
- 带输出反馈(输出反馈,OFB)的伽马模式;
- 简单的更换齿轮模式(Cipher Block Chaining,CBC);
- 带有密文反馈的伽马模式(密码反馈,CFB);
- 消息验证码(MAC)模式。
在所有模式下,文本长度应始终是块长度的倍数,因此,文本始终始终用一位填充到右边,而块的长度则填充为零。
最简单的模式是简单替换模式。 在这种模式下,将文本分为多个块,然后将每个块与其余块分别加密,然后将密文的块粘合在一起,我们得到一个加密的消息。 此模式既最简单又最易受攻击,几乎在实践中从未应用过。
其他模式可能会在后面详细介绍。