0. 算法概述
SM4算法是一种分组密码算法。其分组长度为128bit,密钥长度也为128bit。加密算法与密钥扩展算法均采用32轮非线性迭代结构,以字(32位)为单位进行加密运算,每一次迭代运算均为一轮变换函数F。SM4算法加/解密算法的结构相同,只是使用轮密钥相反,其中解密轮密钥是加密轮密钥的逆序。
1. 密钥及轮密钥
密钥长度为128比特,表示为MK=(MK(0),MK(1),MK(2),MK(3)),其中MKi(i=0,1,2,3)为字。
轮密钥表示为(rk(0),rk(1),...,rk(31)),其中rk(i)(i=0,...,31)为32比特字。轮密钥由秘钥生成。
2. 消息填充分组
首先,将明文转化为字节,由于SM4加密算法按照128个位进行分组,所以很大几率会出现最后一个分组不够128位的情况,需要进行填充,填充方式有很多,比如ZeroPadding、PKCS7Padding、PKCS5Padding,不管使用哪种方式,最后每个分组都是128位。每个分组按照32位一个字分成四个字。
ECB模式与CBC模式
-
ECB模式
电子密码本模式,最古老,最简单的模式,将加密的数据分成若干组,每组的大小跟加密密钥相同。不足的部分进行填充。
按照顺序将计算所得的数据连在一起即可,各段数据之间互不影响。优点:简单,有利于并行计算,误差不会被传递。
缺点:不能隐藏明文的模式,可能对明文进行主动攻击。
-
CBC模式
密文分组链接模式,也需要进行分组,不足的部分按照指定的数据进行填充。
需要一个初始化向量,每个分组数据与上一个分组数据加密的结果进行异或运算,最后再进行加密。将所有分组加密的结果连接起来就形成了最终的结果。
优点:不容易进行主动攻击,安全性好于ECB。
缺点:不利于并行计算,误差传递,需要初始化向量。
三种填充方式的比较
某些加密算法要求明文需要按一定长度对齐,叫做块大小(BlockSize),比如16字节,那么对于一段任意的数据,加密前需要对最后一个块填充到16 字节,解密后需要删除掉填充的数据。
-
ZeroPadding,数据长度不对齐时使用0填充,否则不填充。
-
PKCS7Padding,假设数据长度需要填充n(n>0)个字节才对齐,那么填充n个字节,每个字节都是n;如果数据本身就已经对齐了,则填充一块长度为块大小的数据,每个字节都是块大小。
-
PKCS5Padding,PKCS7Padding的子集,块大小固定为8字节。
由于使用PKCS7Padding/PKCS5Padding填充时,最后一个字节肯定为填充数据的长度,所以在解密后可以准确删除填充的数据,而使用ZeroPadding填充时,没办法区分真实数据与填充数据,所以只适合以\0结尾的字符串加解密。
3. 迭代运算
本加解密算法由32次迭代运算和1次反序变换R组成。
3.1 轮函数F和合成置换T
4. Caché实现
/// SM4算法是一种分组密码算法。其分组长度为128bit,密钥长度也为128bit。
/// 加密算法与密钥扩展算法均采用32轮非线性迭代结构,以字(32位)为单位进行加密运算,每一次迭代运算均为一轮变换函数F。
/// SM4算法加/解密算法的结构相同,只是使用轮密钥相反,其中解密轮密钥是加密轮密钥的逆序。
/// 本方法适用于 ECB模式,ZeroPadding填充模式
Class Utility.SM4 Extends %RegisteredObject
{
/// Creator: wyh
/// CreatDate: 2022-11-03
/// Description:SM4加密
/// Input: msg:原文 mk:128位密钥
/// Output: 密文
/// Debug: w ##class(Utility.SM4).Encrypt("342622199009262982", "F2D8D966CD3D47788449C19D5EF2081B")
ClassMethod Encrypt(msg, mk)
{
#; 1. 密钥及轮密钥
#; a) 密钥长度为128比特,表示为MK=(MK(0),MK(1),MK(2),MK(3)),其中MKi(i=0,1,2,3)为字
#; b) 轮密钥表示为(rk(0),rk(1),...,rk(31)),其中rk(i)(i=0,...,31)为32比特字。轮密钥由密钥生成。
#; 密钥扩展算法:
#; (K(0),K(1),K(2),K(3))=(MK(0)^FK(0),MK(1)^FK(1),MK(2)^FK(2),MK(3)^FK(3))
#; rk(i)=K(i+4)=K(i)^T'(K(i+1)^K(i+2)^K(i+3)^CK(i)),i=0,1,...,31
#; 系统参数FK(0)=(A3B1BAC6),FK(1)=(56AA3350),FK(2)=(677D9197),FK(3)=(B27022DC)
#; 固定参数CK(i)(i=0,1,...,31)为:
#; 00070E15, 1C232A31, 383F464D, 545B6269,
#; 70777E85, 8C939AA1, A8AfB6BD, C4CBD2D9,
#; E0E7EEF5, FC030A11, 181F262D, 343B4249,
#; 50575E65, 6C737A81, 888F969D, A4ABB2B9,
#; C0C7CED5, DCE3EAF1, F8FF060D, 141B2229,
#; 30373E45, 4C535A61, 686F767D, 848B9299,
#; A0A7AEb5, BCC3CAD1, D8DFE6ED, F4FB0209,
#; 10171E25, 2C333A41, 484F565D, 646B7279.
s mk = $zcvt(mk, "L")
f i = 0 : 1 : 3 d
.s MK(i) = $e(mk, 8 * i + 1, 8 * (i + 1))
s FK = "a3b1bac656aa3350677d9197b27022dc"
f i = 0 : 1 : 3 d
.s FK(i) = $e(FK, 8 * i + 1, 8 * (i + 1))
s CK = "00070e151c232a31383f464d545b626970777e858c939aa1a8afb6bdc4cbd2d9e0e7eef5fc030a11181f262d343b424950575e656c737a81888f969da4abb2b9c0c7ced5dce3eaf1f8ff060d141b222930373e454c535a61686f767d848b9299a0a7aeb5bcc3cad1d8dfe6edf4fb020910171e252c333a41484f565d646b7279"
f i = 0 : 1 : 31 d
.s CK(i) = $e(CK, 8 * i + 1, 8 * (i + 1))
f i = 0 : 1 : 3 d
.s K(i) = ..HexXOR(MK(i), FK(i))
f i = 4 : 1 : 35 d
.s K(i) = ..HexXOR(K(i - 4), ..T2(..HexXOR(..HexXOR(..HexXOR(K(i + 1 - 4), K(i + 2 - 4)),K(i + 3 - 4)), CK(i - 4))))
f i = 0 : 1 : 31 d
.s rk(i) = K(i + 4)
#; 2. 消息填充分组
#; 每组128位,每组再分X(0),X(1),X(2),X(3)作为明文输入.
s hex = ..s2hex(msg)
s len = $l(hex)/32
s rtn = ""
f i = 0 : 1 : len-1 d
.k X
.s M(i) = $e(hex, 32 * i + 1, 32 * (i + 1))
.f j = 0 : 1 : 3 d
..s X(j) = $e(M(i), 8 * j + 1, 8 * (j + 1))
#; 3. 迭代运算,密文输出(Y(0),Y(1),Y(2),Y(3))
#; a) 32次迭代运算
#; X(i+4)=F(X(i),X(i+1),X(i+2),X(i+3),rk(i)),i=0,1,...,31
#; F(X(i),X(i+1),X(i+2),X(i+3),rk(i))=X(i)^T(X(i+1)^X(i+2)^X(i+3)^rk(i)),i=0,1,...,31
#; b)反序变换
#; (Y(0),Y(1),Y(2),Y(3))=R(X(32),X(33),X(34),X(35))=(X(35),X(34),X(33),X(32))
.f k = 0 : 1 : 31 d
..s X(k + 4) = ..HexXOR(X(k), ..T(..HexXOR(..HexXOR(..HexXOR(X(k + 1), X(k + 2)), X(k + 3)), rk(k))))
.s rtn = rtn_X(35)_X(34)_X(33)_X(32)
q rtn
}
/// Creator: wyh
/// CreatDate: 2022-11-03
/// Description:SM4解密
/// 解密变换与加密变换结构相同,不同的仅是轮密钥的使用顺序,解密时使用轮密钥序(rk(31),rk(32),...,rk(0)).
/// Input: hex:密文 mk:128位密钥
/// Output: 明文
/// Debug: w ##class(Utility.SM4).Decrypt("5efcbbfdb7a326b340295acb1c0e20fe2622730932bdb5302b5a4ee308944ecc", "F2D8D966CD3D47788449C19D5EF2081B")
ClassMethod Decrypt(hex, mk)
{
s mk = $zcvt(mk, "L")
f i = 0 : 1 : 3 d
.s MK(i) = $e(mk, 8 * i + 1, 8 * (i + 1))
s FK = "a3b1bac656aa3350677d9197b27022dc"
f i = 0 : 1 : 3 d
.s FK(i) = $e(FK, 8 * i + 1, 8 * (i + 1))
s CK = "00070e151c232a31383f464d545b626970777e858c939aa1a8afb6bdc4cbd2d9e0e7eef5fc030a11181f262d343b424950575e656c737a81888f969da4abb2b9c0c7ced5dce3eaf1f8ff060d141b222930373e454c535a61686f767d848b9299a0a7aeb5bcc3cad1d8dfe6edf4fb020910171e252c333a41484f565d646b7279"
f i = 0 : 1 : 31 d
.s CK(i) = $e(CK, 8 * i + 1, 8 * (i + 1))
f i = 0 : 1 : 3 d
.s K(i) = ..HexXOR(MK(i), FK(i))
f i = 4 : 1 : 35 d
.s K(i) = ..HexXOR(K(i - 4), ..T2(..HexXOR(..HexXOR(..HexXOR(K(i + 1 - 4), K(i + 2 - 4)),K(i + 3 - 4)), CK(i - 4))))
f i = 0 : 1 : 31 d
.s rk(i) = K(35 - i)
s len = $l(hex)/32
s rtn = ""
f i = 0 : 1 : len-1 d
.k X
.s M(i) = $e(hex, 32 * i + 1, 32 * (i + 1))
.f j = 0 : 1 : 3 d
..s X(j) = $e(M(i), 8 * j + 1, 8 * (j + 1))
.f k = 0 : 1 : 31 d
..s X(k + 4) = ..HexXOR(X(k), ..T(..HexXOR(..HexXOR(..HexXOR(X(k + 1), X(k + 2)), X(k + 3)), rk(k))))
.s rtn = rtn_X(35)_X(34)_X(33)_X(32)
q ..hex2str(rtn)
}
/// 非线性变换τ构成
/// τ由4个并行的S盒,设输入A=(a0,a1,a2,a3),输出为B=(b0,b1,b2,b3)
/// (b0,b1,b2,b3)=τ(A)=(Sbox(a0),Sbox(a1),Sbox(a2),Sbox(a3))
/// w ##class(Utility.SM4).tau("942600f0")
ClassMethod tau(a)
{
f i = 0 : 1 : 7 d
.s a(i) = $e(a, i + 1)
s s(0) = "d690e9fecce13db716b614c228fb2c05"
s s(1) = "2b679a762abe04c3aa44132649860699"
s s(2) = "9c4250f491ef987a33540b43edcfac62"
s s(3) = "e4b31ca9c908e89580df94fa758f3fa6"
s s(4) = "4707a7fcf37317ba83593c19e6854fa8"
s s(5) = "686b81b27164da8bf8eb0f4b70569d35"
s s(6) = "1e240e5e6358d1a225227c3b01217887"
s s(7) = "d40046579fd327524c3602e7a0c4c89e"
s s(8) = "eabf8ad240c738b5a3f7f2cef96115a1"
s s(9) = "e0ae5da49b341a55ad933230f58cb1e3"
s s(10) = "1df6e22e8266ca60c02923ab0d534e6f"
s s(11) = "d5db3745defd8e2f03ff6a726d6c5b51"
s s(12) = "8d1baf92bbddbc7f11d95c411f105ad8"
s s(13) = "0ac13188a5cd7bbd2d74d012b8e5b4b0"
s s(14) = "8969974a0c96777e65b9f109c56ec684"
s s(15) = "18f07dec3adc4d2079ee5f3ed7cb3948"
f i = 0 : 1 : 15 d
.f j = 0 : 1 : 15 d
..s s(i, j) = $e(s(i), 2 * j + 1, 2 * (j + 1))
s rtn = ""
f i = 0 : 1 : 3 d
.s r = ..hex2int(a(2 * i))
.s c = ..hex2int(a(2 * i + 1))
.s rtn = rtn _ s(r, c)
return rtn
}
/// 线性变换L
/// 非线性变换τ的输出是线性变换L的输入.设输入为B,输出为C.
/// C=L(B)=B^(B<<2)^B(<<10)^B(<<18)^B(<<24)
ClassMethod L(b)
{
s rtn = ""
s rtn = ..HexXOR(..HexXOR(..HexXOR(..HexXOR(b ,..Hexleft(b, 2)), ..Hexleft(b, 10)), ..Hexleft(b, 18)), ..Hexleft(b, 24))
return rtn
}
/// L'
/// L'(B)=B^(B<<13)^B(<<23)
ClassMethod L2(b)
{
s rtn = ""
s rtn = ..HexXOR(..HexXOR(b, ..Hexleft(b, 13)), ..Hexleft(b, 23))
return rtn
}
/// 合成置换T
/// 是一个可逆变换,由非线性τ和线性变换L复合而成,即T(.)=L(τ(.)).
ClassMethod T(x)
{
return ..L(..tau(x))
}
/// T'
/// 将合成置换T的线性变换L替换为L'
ClassMethod T2(x)
{
return ..L2(..tau(x))
}
/// 消息填充
/// w ##class(Utility.SM4).s2hex("342622199009262982")
ClassMethod s2hex(msg)
{
s len = $l(msg)
s rtn = ""
f i = 1 : 1 : len d
.s num = $ascii($e(msg, i))
.s hex = $ZHEX(num)
.s rtn = rtn _ hex
s k = 32 - ($l(rtn) # 32)
f i = 1 : 1 : k d
.s rtn = rtn _ "0"
return rtn
}
/// 8位16进制异或
/// w ##class(Utility.SM4).HexXOR("9b977428", "e334500a")
ClassMethod HexXOR(a, b)
{
s a = ..hex2b(a)
s b = ..hex2b(b)
s rtn = ""
f i = 1 : 1 : 32 d
.i $e(a, i) = $e(b, i) d
..s rtn = rtn _ "0"
.e d
..s rtn = rtn _ "1"
s rtn = ..b2hex(rtn)
return rtn
}
/// 8位16进制左移
/// w ##class(Utility.SM4).Hexleft("61610000","3")
ClassMethod Hexleft(a, k)
{
s rtn = ""
s a = ..hex2b(a)
s k = k # 32
s a1 = $e(a, k + 1, 32)
s a2 = $e(a, 1, k)
s rtn = a1 _ a2
s rtn = ..b2hex(rtn)
return rtn
}
/// 16进制转二进制
/// w ##class(Utility.SM4).hex2b("61610000")
ClassMethod hex2b(hex)
{
s len = $l(hex)
s rtn = ""
f i = 1 : 1 : len d
.s h = $e(hex, i)
.i h = "0" d
..s rtn = rtn _ "0000"
.e i h = "1" d
..s rtn = rtn _ "0001"
.e i h = "2" d
..s rtn = rtn _ "0010"
.e i h = "3" d
..s rtn = rtn _ "0011"
.e i h = "4" d
..s rtn = rtn _ "0100"
.e i h = "5" d
..s rtn = rtn _ "0101"
.e i h = "6" d
..s rtn = rtn _ "0110"
.e i h = "7" d
..s rtn = rtn _ "0111"
.e i h = "8" d
..s rtn = rtn _ "1000"
.e i h = "9" d
..s rtn = rtn _ "1001"
.e i h = "a" d
..s rtn = rtn _ "1010"
.e i h = "b" d
..s rtn = rtn _ "1011"
.e i h = "c" d
..s rtn = rtn _ "1100"
.e i h = "d" d
..s rtn = rtn _ "1101"
.e i h = "e" d
..s rtn = rtn _ "1110"
.e i h = "f" d
..s rtn = rtn _ "1111"
return rtn
}
/// 二进制转16进制
/// w ##class(Utility.SM4).b2hex("01100001011000010000000000000000")
ClassMethod b2hex(bit)
{
s len = $l(bit)/4
s rtn = ""
f i = 1 : 1 : len d
.s bs = $e(bit, 4 * (i - 1) + 1, 4 * i)
.i bs = "0000" d
..s rtn = rtn _ "0"
.e i bs = "0001" d
..s rtn = rtn _ "1"
.e i bs = "0010" d
..s rtn = rtn _ "2"
.e i bs = "0011" d
..s rtn = rtn _ "3"
.e i bs = "0100" d
..s rtn = rtn _ "4"
.e i bs = "0101" d
..s rtn = rtn _ "5"
.e i bs = "0110" d
..s rtn = rtn _ "6"
.e i bs = "0111" d
..s rtn = rtn _ "7"
.e i bs = "1000" d
..s rtn = rtn _ "8"
.e i bs = "1001" d
..s rtn = rtn _ "9"
.e i bs = "1010" d
..s rtn = rtn _ "a"
.e i bs = "1011" d
..s rtn = rtn _ "b"
.e i bs = "1100" d
..s rtn = rtn _ "c"
.e i bs = "1101" d
..s rtn = rtn _ "d"
.e i bs = "1110" d
..s rtn = rtn _ "e"
.e i bs = "1111" d
..s rtn = rtn _ "f"
return rtn
}
/// 16进制1位转十进制
/// w ##class(Utility.SM4).hex2int("")
ClassMethod hex2int(hex)
{
s rtn = hex
i rtn = "a" d
.s rtn = 10
e i rtn = "b" d
.s rtn = 11
e i rtn = "c" d
.s rtn = 12
e i rtn = "d" d
.s rtn = 13
e i rtn = "e" d
.s rtn = 14
e i rtn = "f" d
.s rtn = 15
return rtn
}
/// 十六进制转字符
/// w ##class(Utility.SM4).hex2str("3334323632323139393030393236323938320000000000000000000000000000")
ClassMethod hex2str(msg)
{
s len = $l(msg)/2
s rtn = ""
f i = 1 : 1 : len d
.s hex = $e(msg, (i - 1) * 2 + 1 , i * 2)
.s int = 16 * ..hex2int($e(hex, 1)) + ..hex2int($e(hex, 2))
.s str = $char(int)
.s rtn = rtn _ str
return $p(rtn, $char(0))
}
}