国密算法Go语言实现(详解)(五) ——SM4(分组算法)

原创代码:https://github.com/ZZMarquis/gm

引用时,请导入原创代码库。本文仅以注释方式详解代码逻辑,供学习研究使用。

对原创代码的修改内容

  1. 修改了部分常量、变量、结构体属性的名称, 以便与GO语言标准包规范相统一
  2. 加入中文注释,解释代码逻辑

注释者及联系邮箱

Paul Lee
paul_lee0919@163.com

// Encrypt() 为SM4的加密方法函数。
// (1) 校验输入消息字节数组的长度
// (2) 校验输出消息字节数组的长度
// (3) 调用分组消息处理函数processBlock()
func (c *sm4Cipher) Encrypt(dst, src []byte) {
	if len(src) < BlockSize {
		panic("sm4: input not full block")
	}
	if len(dst) < BlockSize {
		panic("sm4: output not full block")
	}
	processBlock(c.enc, src, dst)
}

Encrypt() 为SM4的加密方法函数。其中:

  • (1) 校验输入消息字节数组的长度
  • (2) 校验输出消息字节数组的长度
  • (3) 调用分组消息处理函数processBlock( )
// Decrypt() 为SM4的解密方法函数。
// (1) 校验输入消息字节数组的长度
// (2) 校验输出消息字节数组的长度
// (3) 调用分组处理函数processBlock()
func (c *sm4Cipher) Decrypt(dst, src []byte) {
	if len(src) < BlockSize {
		panic("sm4: input not full block")
	}
	if len(dst) < BlockSize {
		panic("sm4: output not full block")
	}
	processBlock(c.dec, src, dst)
}

Decrypt( ) 为SM4的解密方法函数。其中:

  • (1) 校验输入消息字节数组的长度
  • (2) 校验输出消息字节数组的长度
  • (3) 调用分组数据处理函数processBlock( )
// tau() 为国标(6.2.(a))规定的Sbox非线性变换τ(.),其中:
// (1) 如果将Sbox二维表逐行展开为一维数组s[],则数组元素的序号就是Sbox行序号乘16加列序号;
// (2) 因为Sbox二维表本身为16x16的表,而1个字节存储单元能够存储8位二进制数,折算16进制就是2位16进制数;
// (3) 所以,若将1个字节所表示的二进制数字a,折算成16进制数字(假设为EF),然后将其高位E作为行坐标、将低位F作为列坐标,
//     则数字a就可以用来表示Sbox二维表中某个元素的行列坐标(E,F):
// (4) Sbox二维表中,坐标为(E,F)的元素展开成数组s[]后,其数组序号就是: Ex16 + F,这其实就是数字a的值;
// (5) 所以,根据非线性变换τ(.)的定义,Sbox(a)=s[a]
func tau(a uint32) uint32 {
	var aArr [4]byte
	var bArr [4]byte
	binary.BigEndian.PutUint32(aArr[:], a)
	bArr[0] = sBox[aArr[0]]
	bArr[1] = sBox[aArr[1]]
	bArr[2] = sBox[aArr[2]]
	bArr[3] = sBox[aArr[3]]
	return binary.BigEndian.Uint32(bArr[:])
}

tau( ) 为国标(6.2.(a))规定的Sbox非线性变换τ(.),其中:

  • (1) 如果将Sbox二维表逐行展开为一维数组s[],则数组元素的序号就是Sbox行序号乘16加列序号;
  • (2) 因为Sbox二维表本身为16x16的表,而1个字节存储单元能够存储8位二进制数,折算16进制就是2位16进制数;
  • (3) 所以,若将1个字节所表示的二进制数字a,折算成16进制数字(假设为EF),然后将其高位E作为行坐标、将低位F作为列坐标,则数字a就可以用来表示Sbox二维表中某个元素的行列坐标(E,F);
  • (4) Sbox二维表中,坐标为(E,F)的元素展开成数组s[]后,其数组序号就是: Ex16 + F,这其实就是数字a的值;
  • (5) 所以,根据非线性变换τ(.)的定义,Sbox(a)=s[a]
// processBlock 为SM4核心算法函数:
// (1) 将in[]数组存储的输入消息(128位)按4个字节为1个“字”(32位)来分组,划分成4组;
// (2) 结合轮秘钥rk,根据国标(7.1.(a))规定,按轮函数F算法进行32轮迭代加密;
// (3) 根据国标(7.1.(b))规定,进行反序变换;
// (4) 将推算结果写入out[]数组。
func processBlock(rk []uint32, in []byte, out []byte) {
   var x [BlockSize / 4]uint32
   x[0] = binary.BigEndian.Uint32(in[0:4])
   x[1] = binary.BigEndian.Uint32(in[4:8])
   x[2] = binary.BigEndian.Uint32(in[8:12])
   x[3] = binary.BigEndian.Uint32(in[12:16])

   for i := 0; i < 32; i += 4 {
   	x[0] = f0(x[:], rk[i])
   	x[1] = f1(x[:], rk[i+1])
   	x[2] = f2(x[:], rk[i+2])
   	x[3] = f3(x[:], rk[i+3])
   }
   r(x[:])

   binary.BigEndian.PutUint32(out[0:4], x[0])
   binary.BigEndian.PutUint32(out[4:8], x[1])
   binary.BigEndian.PutUint32(out[8:12], x[2])
   binary.BigEndian.PutUint32(out[12:16], x[3])
}

processBlock( ) 为分组数据处理函数,是SM4核心算法函数:

  • (1) 将in[ ]数组存储的输入消息(128位)按4个字节为1个“字”(32位)来分组,划分成4组;
  • (2) 结合轮秘钥rk,根据国标(7.1.(a))规定,按轮函数F算法(详见下文)进行32轮迭代加密;
  • (3) 根据国标(7.1.(b))规定,进行反序变换(详见下文);
  • (4) 将推算结果写入out[ ]数组。
// l() 为国标(6.2.(b))规定的合成置换函数T(.)的第二步骤:线性变换函数L()。
func l(b uint32) uint32 {
	return b ^ bits.RotateLeft32(b, 2) ^ bits.RotateLeft32(b, 10) ^
		bits.RotateLeft32(b, 18) ^ bits.RotateLeft32(b, 24)
}

// t() 为国标(6.2)规定的合成置换函数T(.)
func t(z uint32) uint32 {
	return l(tau(z))
}

// f0() 代表国标(6.1和7.1.(a))定义的轮函数F(),在i=0时对应的实例。
func f0(x []uint32, rk uint32) uint32 {
	return x[0] ^ t(x[1]^x[2]^x[3]^rk)
}

// f1() 代表国标(6.1和7.1.(a))定义的轮函数F(),在i=1时对应的实例。
func f1(x []uint32, rk uint32) uint32 {
	return x[1] ^ t(x[2]^x[3]^x[0]^rk)
}

// f2() 代表国标(6.1和7.1.(a))定义的轮函数F(),在i=2时对应的实例。
func f2(x []uint32, rk uint32) uint32 {
	return x[2] ^ t(x[3]^x[0]^x[1]^rk)
}

// f3() 代表国标(6.1和7.1.(a))定义的轮函数F(),在i=3时对应的实例。
func f3(x []uint32, rk uint32) uint32 {
	return x[3] ^ t(x[0]^x[1]^x[2]^rk)
}

以上代码为四层嵌套调用函数:

  1. 最外层为fi( ) 函数,代表i = (0, 1, 2…3) 时国标(6.1和7.1.(a)定义的轮函数F( )算法
  2. 第二层为t( )函数,代表国标(6.2)规定的合成置换函数T(.)
  3. 第三层为l( )函数,代表国标(6.2.(b))规定的合成置换函数T(.)的第二步骤----线性变换函数L( )
  4. 第四层为τ( )函数,代表国标(6.2.(b))规定的合成置换函数T(.)的第一个步骤----非线性变换函数τ( ) (详见上文)
// r() 为国标(7.1.(b))定义的反序变换函数:
// (1) 两个二进制数A0和B0, 初次异或运算结果为A1, 则A1=A0^B0;
// (2) A1与B0进行第二次异或运算结果为B1,则B1=A1^B0=A0^B0^B0=A0;
// (3) A1与B1进行第三次异或运算结果为A2,则A2=A1^B1=A0^B0^A0=B0;
// (4) 若A、B均为变量,将A0/A1/A2均用变量A存储,B0/B1均用变量B存储,则
//     上述计算可简述为:三次异或运算交换变量值。
func r(a []uint32) {
	a[0] = a[0] ^ a[3]
	a[3] = a[0] ^ a[3]
	a[0] = a[0] ^ a[3]
	a[1] = a[1] ^ a[2]
	a[2] = a[1] ^ a[2]
	a[1] = a[1] ^ a[2]
}

r( ) 为国标(7.1.(b))定义的反序变换函数:

  1. 两个二进制数A0和B0, 初次异或运算结果为A1, 则A1=A0^B0;
  2. A1与B0进行第二次异或运算结果为B1,则B1=A1 ^ B0 = A0 ^ B0 ^ B0 = A0;
  3. A1与B1进行第三次异或运算结果为A2,则A2 = A1 ^ B1 = A0 ^ B0 ^ A0 = B0;
  4. 若A、B均为变量,将A0/A1/A2均用变量A存储,B0/B1均用变量B存储,则上述计算可简述为:三次异或运算交换变量值。

(未完待续)