# 密码学算法之 SMS4 国密算法

"""

对称加密算法，解密、加密密钥相同，顺序相反；加密密钥：k1,k2,k3,k4     解密密钥：k4,k3,k2,k1
国密算法 SMS4      加密分组长度 128 bit   加密密钥位数与其等同

和 SMS4 文档明文加密结果相同
successfully!!!!!!
"""

# S 盒    未公布其构成原因
SBox=np.array([0xd6,0x90,0xe9,0xfe,0xcc,0xe1,0x3d,0xb7,0x16,0xb6,0x14,0xc2,0x28,0xfb,0x2c,0x05,
0x2b,0x67,0x9a,0x76,0x2a,0xbe,0x04,0xc3,0xaa,0x44,0x13,0x26,0x49,0x86,0x06,0x99,
0x9c,0x42,0x50,0xf4,0x91,0xef,0x98,0x7a,0x33,0x54,0x0b,0x43,0xed,0xcf,0xac,0x62,
0xe4,0xb3,0x1c,0xa9,0xc9,0x08,0xe8,0x95,0x80,0xdf,0x94,0xfa,0x75,0x8f,0x3f,0xa6,
0x47,0x07,0xa7,0xfc,0xf3,0x73,0x17,0xba,0x83,0x59,0x3c,0x19,0xe6,0x85,0x4f,0xa8,
0x68,0x6b,0x81,0xb2,0x71,0x64,0xda,0x8b,0xf8,0xeb,0x0f,0x4b,0x70,0x56,0x9d,0x35,
0x1e,0x24,0x0e,0x5e,0x63,0x58,0xd1,0xa2,0x25,0x22,0x7c,0x3b,0x01,0x21,0x78,0x87,
0xd4,0x00,0x46,0x57,0x9f,0xd3,0x27,0x52,0x4c,0x36,0x02,0xe7,0xa0,0xc4,0xc8,0x9e,
0xea,0xbf,0x8a,0xd2,0x40,0xc7,0x38,0xb5,0xa3,0xf7,0xf2,0xce,0xf9,0x61,0x15,0xa1,
0x1d,0xf6,0xe2,0x2e,0x82,0x66,0xca,0x60,0xc0,0x29,0x23,0xab,0x0d,0x53,0x4e,0x6f,
0xd5,0xdb,0x37,0x45,0xde,0xfd,0x8e,0x2f,0x03,0xff,0x6a,0x72,0x6d,0x6c,0x5b,0x51,
0x8d,0x1b,0xaf,0x92,0xbb,0xdd,0xbc,0x7f,0x11,0xd9,0x5c,0x41,0x1f,0x10,0x5a,0xd8,
0x0a,0xc1,0x31,0x88,0xa5,0xcd,0x7b,0xbd,0x2d,0x74,0xd0,0x12,0xb8,0xe5,0xb4,0xb0,
0x89,0x69,0x97,0x4a,0x0c,0x96,0x77,0x7e,0x65,0xb9,0xf1,0x09,0xc5,0x6e,0xc6,0x84,
0x18,0xf0,0x7d,0xec,0x3a,0xdc,0x4d,0x20,0x79,0xee,0x5f,0x3e,0xd7,0xcb,0x39,0x48])
SBox=SBox.reshape((16,16))          # 将 SBox 改为 16 行 16 列 二维数组

# 非线性变换 τ 函数
#   0x46a9bc1f =>经过 τ 函数 S() 变成=>     （0x）17296dcb
def S(plaintext):        # 加密 32 位（以字为单位）明文文本信息 （x0,x1,x2,x3 :128 bit）,其中x0 为
# 接收的 plaintext 实际上为整形 int,不需要转换成 16进制，但用 16进制说明更形象
plaintext=bin(plaintext)
s_plaintext=''          # 经 S 盒非线性变换后的16进制数，为此函数返回值
# 截取 plaintext 32位 二进制数
plaintext=change_binary(plaintext)
for i in range(4):
bin_8=plaintext[8*i:8*(i+1)]     # 取出每字节二进制数
# 查 S 盒表
x=int(bin_8[:4],2)    # 取行号 x
y=int(bin_8[4:],2)    # 取列号 y
# s_plaintext+=hex(SBox[x,y])[2:]    # 取 x 行，y 列值
temp=hex(SBox[x, y])  # 取 x 行，y 列值
s_plaintext += fill_up_0_to_16(temp,2,True)
return s_plaintext    # 返回字符串类型  17296dcb

# 将一个十六进制数转换成二进制数循环左移或右移>0 且小于等于该二进制数长度的位数
# 例：loop_k(s_plaintext='17296dcb',1,L_or_R='L') =>( bin 00010111001010010110110111001011 )=> 00101110010100101101101110010110
def loop_k(s_plaintext,k,L_or_R='L'):
s_plaintext_2=bin(int(s_plaintext, 16))
s_plaintext=change_binary(s_plaintext_2) # 将不符合 0b+32位=34位的二进制改成去掉0b的32位二进制，即二进制前面添加 000
if L_or_R=='L':
return s_plaintext[k:]+s_plaintext[:k]
elif L_or_R=='R':
return s_plaintext[-k:]+s_plaintext[:len(s_plaintext)-k]     # 返回字符串类型

# 线性变换 L_m ，主要是移位和异或    L_m(B)=B^(B<<<2)^(B<<<10)^(B<<<18)^(B<<<24)  其中 '<<<' 代表循环左移
def L_m(s_plaintext):      # 输入 '17296dcb'     对明文加密函数
s_0=int(s_plaintext,16)   # 将字符串转为整数，便于异或         例：0xff^15==0xf0
s_2=int(loop_k(s_plaintext,2),2)   # B<<<2  ;| int(str,2)将字符串11001转换成二进制数
s_10=int(loop_k(s_plaintext,10),2)
s_18=int(loop_k(s_plaintext,18),2)
s_24=int(loop_k(s_plaintext,24),2)
return  s_0^s_2^s_10^s_18^s_24     # 返回10进制数,整型 int

''''
密钥扩展算法
'''
#系统参数 FK
FK=np.array([0xa3b1bac6,0x56aa3350,0x677d9197,0xb27022dc])

#固定参数 CK
CK=np.array(['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'])

# 固定参数 CK 生成函数  ck(i,j)为CKi的第j个字节（i=0,1,2,...,31;j=0,1,2,3）    ck(i,j)=(4i+j)*7%256
def build_CK():
CK_=['']*32
for i in range(32):
for j in range(4):
strj=hex((4*i+j)*7%256)[2:]
if len(strj)!=2:
strj='0'*(2-len(strj))+strj      # 使strj 由 7 变成 07 之类的
CK_[i] +=strj
return CK_      # 返回列表数组

# 线性变换 L' ,用于加密密钥
def L_c(s_key):     # s_key 为16进制字符串
s_0=int(s_key,16)
s_13=int(loop_k(s_key,13),2)
s_23=int(loop_k(s_key,23),2)
return s_0^s_13^s_23     # 返回10进制数,整型 int

'''
求轮密钥 rki(i=0,1,2...31)
初始密钥 MK=（MK0,MK1,MK2,MK3）
(K0,K1,K2,K3)=(FK0^MK0,FK1^FK1,FK2^MK2,FK3^MK3)
rk(i)=K(i+4)=K(i)^T'(K(i+1)^K(i+2)^K(i+3)^CK(i))

'''
# 主扩展函数
def key_extend(key_128):      # 输入 16进制字符串密钥 128 bit '0x1f3a...'
# 求 K0,K1,K2,K3
MK=['']*4
for i in range(4):
MK[i]=key_128[2+8*i:2+8*(i+1)]

K=[0]*36              # 定义 K 为有32个数据的列表
Key=[0]*32            # Key 为实际密钥数组列表
for i in range(4):
mk=int(MK[i],16)
fk=FK[i]
K[i]=mk^fk

# 求 Ki i=4,5,6,...,31
for i in range(0,32):
K_tmp=K[i+1]^K[i+2]^K[i+3]^int(CK[i],16)   # K_tmp 为整数  0xff*0b1000==2040
s_K_tmp=S(K_tmp)  # S 非线性变换
L_s_K_tmp=L_c(s_K_tmp)   # L' 线性变换
K[i+4]=K[i]^L_s_K_tmp

for j in range(4,36):
temp=hex(K[j])
Key[j-4]='0x'+fill_up_0_to_16(temp,8,True)   # 例： Key[?]='0xd17dd163'
return Key    # 返回 16 进制字符串列表数组

"""

加密明文的主加密函数  输入 16进制字符串 明文，例：'0x1f6c3b...' 共128 bit

"""
def SMS4_en_decoding(plaintext,key):
X=[0]*36
# 求 x0,x1,x2,x3
for i in range(4):
X[i]=int(plaintext[2+8*i:2+8*(i+1)],16)

# 求 x(i) i=4,5,6...,31
for i in range(32):
p_tmp=X[i+1]^X[i+2]^X[i+3]^int(key[i],16)
s_p_tmp=S(p_tmp)        # S 非线性变换
L_s_p_tmp=L_m(s_p_tmp)     # L' 线性变换
X[i+4]=X[i]^L_s_p_tmp

# 将 X(i) 由整形转化成16进制字符串
for i in range(36):
temp=hex(X[i])
X[i]='0x'+fill_up_0_to_16(temp,8,True) # 补足 16 进制字符串的 0 字符，返回不带 0x 的16进制字符串
return X

# 从 Xi i=0,1,2,3...,35 中取出 X35,X34,X33,X32 并连接起来成 128 bit 的16进制字符串
def X35_X32(X):   # X 为引用参数
ciphertext=''
for i in range(35,31,-1):
ciphertext+=change_hex(X[i])  # 将不符合 0x+8位=10位的16进制改成去掉0x的8位16进制，即16进制前面添加 000
return ciphertext

if __name__=="__main__":
# plaintext = 'dsf~？f9 ！@$^.>)(fsdfjdnvdsn(0￥￥￥￥#￥￥￥$%%^&&%$^￥￥最佳东方大幅度发的范德萨发放水电费水电费水电费就斯蒂芬斯蒂芬时代峰峻' # 明文字符串（不得是单、双引号和空字符） plaintext='e'*105 # plaintext='' # 明文不可为 空字符串 print('明文',plaintext) key= 'REde4$%^&()><1rd'            # 密钥 16 字符 ascii 表中值 在区间[32,126]之间（不得是单、双引号和转义符'\'）
if key_to_16(key):
key=key_to_16(key)
else:
print('密钥错误，请重设');exit(10)
team=plaintext_to_team16(plaintext) # 明文分组每组 128 bit 并输出十六进制字符串列表数组  要求明文不得为空
ciphertext=''
# 加密
Key=[]
Key = key_extend(key)
for i in range(len(team)-1):    # 循环 team 中组数减一次（一表示附加组'7'）
c = SMS4_en_decoding('0x'+team[i], Key)
ciphertext+=X35_X32(c)   # 生成一组密文
# 密文最后两位作为 补足最后分组中的 '7'的个数，用 10 进制表示
if len(str(team[-1]))!=2:
temp7='0'*(2-len(str(team[-1])))+str(team[-1])          # 将team[-1]中添加的'7'的个数加在密文后面
else:
temp7=str(team[-1])

ciphertext+=temp7
print("密文："+ciphertext)
print(len(ciphertext))
# 解密
plaintext16=[]
for i in range(len(team)-1):
if cipher16_divide_group(ciphertext[:-2]):
group=cipher16_divide_group(ciphertext[:-2])
else:
print('密文分组出现错误');exit(11)
m = SMS4_en_decoding('0x'+group[i], Key[::-1])
plaintext16.append(X35_X32(m))       # 生成一组明文
plaintext16.append(int(ciphertext[-2:]))
print(plaintext16)
restore=restore_16_to_plaintext(plaintext16)
print("恢复成原明文：",restore)
if plaintext==restore:
print("加解密成功")
else:
print("加解密失败")