SM4算法的长数据加密——第二部分

==国密算法的实现方式·第二部分==

一、SM4的长明数据加密

    长数据加密其实很简单,大概想一下,把数据分成与组长相等的数据段,分别加密,然后拼起来,就大功告成了!没错就是这么简单,不过这只是其中一种加密方式,在讲述分组密码的长数据加密时,通常会讲到工作模式:

1.分组密码的工作模式

    ECB-电码本(Electronic Code Book)模式: 这是最简单的、直接使用分组密码算法的工作模式,就如同上文提到的那样,将明文分组填充至分组长度,分别加密再按原顺序连接成密文。工作描述如下图:

    这种模式优劣势很明显,如果仅仅对密文做小改动,可能仅需修改对应位置上的几个分组即可,无需大段落修改密文,但也可能暴露明文特征,比如包含有相同密文分组的情况。这种工作模式支持并行实现,所以只要硬件并行数高,软件线程够多,就可以线性地提高运行效率。至于如何在应用时填补这些不足,与本篇的主题无关,有兴趣可以单独讨论,这里暂不赘述。

    CBC-密文分组链接(Cipher Block Chaining)模式:该工作模式下,上一顺序的密文产生后,要与下一组数据的明文异或后进入下一组加密,即下一组加密的输入部分与上一组输出的密文有关。工作描述如下图:

    这种模式也是一种较常见的工作模式,稍作分析,这里的每一组密文的产生都是由所有前面分组的所有数据共同作用产生的,首先避免了分组特征的暴露,其次破译难度更高因为其具有更高的混乱度。但劣势也很明显,因为其中数据纠缠非常复杂,故哪怕产生一点错误,都会导致错误点分组之后的所有数据发生错误,难以纠正。故这一工作模式对系统运行稳定性和正确性要求较高。另外,该模式的实现不能利用并行操作,每一组的加密都需要等待接受上一组的密文,故只有上一组加密完成后才能够进入下一轮,每次仅有一个分组能够进行加密。

    OFB-输出反馈(OFB―Output feedback)模式:为什么每次分组都要把那一组数据补充完成?没什么必要吧,这一工作模式将密钥和明文的关系改变了,直观上看来更像是序列密码算法。工作描述如下图:

    这里的密钥不直接参与与明文的计算,而是与一个移位寄存器进行计算,将其输出的密钥流与明文异或,与序列密码的思想非常相像。该模式下无需将数据长度扩展至分组长度的倍数,而是与原数据等长,还兼具易修改、能隐藏明文特征等特性,是一个比较巧妙的解决方法。这样的做法不会产生错误扩散,但是具备序列密码的一些通病。

    CFB-密文反馈(CFB―Cipher Feedback)模式:同理我们将上一轮的计算结果与下一组明文进行异或后,输入到下一轮加密中,很熟悉吧,工作描述如下图:

    该模式同样能够隐蔽明文特征,但不能阻止错误扩散,需要较高的系统稳定性和正确性。

    其它:事实上分组密码的应用十分灵活,不再过多介绍,我们本次主要实现ECB和CBC模式,这里说一个分组密码的一种工作模式:CBC-MAC

    如同CBC模式一样,分组密码对一段明文进行加密,这最后一组数据中包含着消息中所有比特位的运算,最后这组数据单拿出来,可以作为某一段数据的认证码,也叫消息认证码MAC,用作某一段数据的完整性检测,只要数据发生差错,最后生成的MAC码即与原码不一致,反之可证明数据正确无误。

2.ECB模式下的长数据加密实现

    我们对第一部分https://www.cnblogs.com/Qujinkongyuyin/p/10339297.html 中的SM4实现代码进行了一个小小的处理,使之成为一个SM4类,便于对数据进行快速处理,来看一下我们是如何处理的:

    以下部分是从写好的类中复制出来的两个方法,其中functionT、functionTK如上篇文章,这里只表达ECB加密的代码实现方法:

def ECB_enc( self , plain , MK , FK ) :
    print( "轮密钥生成中……" )
    RK = []
    Attr = strxor( MK , FK , 16 )
    for i in range (0,32):
        Attr = self.functionTK( Attr , i )
        RK.append( Attr[12:16] )
        #print( "RK [" , i+1 , "]" , str2hex(RK[i]) )
    #ECB加密模式
    cipher = ""
    for l in range (0,len(plain)//16):
        Attr = plain[16*l:16*(l+1)]
        for i in range (0,32) :
            Attr = self.functionT( Attr , RK , 1 , i )
        cipher = cipher + Attr[12:16] + Attr[8:12] + Attr[4:8] + Attr[0:4]
    return cipher
        
def ECB_dec( self , cipher , MK , FK ) :
    print( "轮密钥生成中……" )
    RK = []
    Attr = strxor( MK , FK , 16 )
    for i in range (0,32):
        Attr = self.functionTK( Attr , i )
        RK.append( Attr[12:16] )
        #print( "RK [" , i+1 , "]" , str2hex(RK[i]) )
    ##加密轮函数启动
    plain = ""
    for l in range (0,len(cipher)//16):
        Attr = cipher[16*l:16*(l+1)]
        for i in range (0,32) :
            Attr = self.functionT( Attr , RK , 0 , i )
        plain = plain + Attr[12:16] + Attr[8:12] + Attr[4:8] + Attr[0:4]
    return plain

    代码的整体改动不大,就是简单的分别加密后组合在一起。解密过程中,我们只需要将每一组的密文分别进行解密,就可以还原得到明文数据。

3.CBC模式下的长数据加密实现

    还是如上,我们从写好的类中挑出了两个函数进行解释:

def CBC_enc( self , plain , MK , FK ) :
    print( "轮密钥生成中……" )
    RK = []
    Attr = strxor( MK , FK , 16 )
    for i in range (0,32):
        Attr = self.functionTK( Attr , i )
        RK.append( Attr[12:16] )
        #print( "RK [" , i+1 , "]" , str2hex(RK[i]) )
    cipher = ""
    for l in range (0,len(plain)//16):
        if l == 0 :
            Attr = plain[0:16]
        else :
            Attr = strxor( plain[16*l:16*(l+1)] , Attr[12:16] + Attr[8:12] + Attr[4:8] + Attr[0:4] , 16 )
        for i in range (0,32) :
            Attr = self.functionT( Attr , RK , 1 , i )
        cipher = cipher + Attr[12:16] + Attr[8:12] + Attr[4:8] + Attr[0:4]
    return cipher

def CBC_dec( self , cipher , MK , FK ) :
    print( "轮密钥生成中……" )
    RK = []
    Attr = strxor( MK , FK , 16 )
    for i in range (0,32):
        Attr = self.functionTK( Attr , i )
        RK.append( Attr[12:16] )
        #print( "RK [" , i+1 , "]" , str2hex(RK[i]) )
    ##加密轮函数启动
    plain = ""
    for l in range (0,len(cipher)//16):
        Attr = cipher[16*l:16*(l+1)]
        for i in range (0,32) :
            Attr = self.functionT( Attr , RK , 0 , i )
        if l > 0 :
            plain = plain + strxor( Attr[12:16] + Attr[8:12] + Attr[4:8] + Attr[0:4] , cipher[16*(l-1):16*l] , 16 )
        else :
            plain = plain + Attr[12:16] + Attr[8:12] + Attr[4:8] + Attr[0:4]
    return plain

    与EBC工作模式不同的是,在下一组加密轮函数启动前,我们将上一组加密产生的密文与下一组待加密的明文进行了一次异或操作,作为下一组加密轮函数的输入数据参与下一组函数运算。在解密过程中,仍然是将上一组的密文与下一组解密产生的数据进行异或操作,产生最终可输出的明文。

三、SM4类展示

    这里展示的是作者自己写的一个SM4类,前面的字符串运算函数没有放到类里面因为主逻辑程序也有用到的时候,将下面代码段整体粘贴即可实现加解密功能,其中包含着运算参数,包含着最常用的几种功能,下面向大家介绍:

  • strxor:两个字符串异或函数,返回两个字符串前len个值异或后的字符串
  • str2hex:将字符串的16进制呈现,返回一个可打印的字符串表达形式
  • strldc:将字符串向左循环移位,位数单位为比特(bit)
  • SM4_Object.SearchSbox:查询S盒,Act输入的不同将影响查询模式,1为加密查询,否则为解密查询。返回一个字符串查表后的映射结果
  • SM4_Object.functionT:加密轮函数,返回一轮解密后的状态区数据,格式为字符串string
  • SM4_Object.functionTK:轮密钥生成轮函数,返回一轮运算后的状态区取值,格式为字符串string
  • SM4_Object.ECB_enc:ECB(电码本)工作模式下的字符串加密,返回密文字符串
  • SM4_Object.ECB_dec:ECB(电码本)工作模式下的字符串解密,返回明文字符串
  • SM4_Object.CBC_enc:CBC(密文分组链接)工作模式下的字符串加密,返回密文字符串
  • SM4_Object.CBC_dec:CBC(密文分组链接)工作模式下的字符串解密,返回明文字符串
  • SM4_Object.ECB_enc_file:ECB(电码本)工作模式下的文件加密,加密密文存储在文件中,向上层函数返回密文字符串
  • SM4_Object.ECB_dec_file:ECB(电码本)工作模式下的文件解密,恢复明文存储在文件中,向上层函数返回明文字符串
  • SM4_Object.CBC_enc_file:CBC(密文分组链接)工作模式下的文件加密,加密密文存储在文件中,向上层函数返回密文字符串
  • SM4_Object.CBC_dec_file:CBC(密文分组链接)工作模式下的文件解密,恢复明文存储在文件中,向上层函数返回明文字符串
  • SM4_Object.RoundKeyGen:轮密钥生成,返回一次加密时所用的32个轮密钥,形式为列表
import pickle

def strxor( message , key , len ):
        out = ""
        for i in range ( 0 , len ):
            ch = ord(message[i]) ^ ord(key[i])
            out = out + chr(ch)
        return out

def str2hex( string ):
    out = ""
    for i in range ( 0 , len(string) ):
        out = out + " " + hex(ord( string[i] ))
    return out

def strldc( string , bit ):
    byte = bit // 8
    bit = bit % 8
    out = ""
    if bit == 0 :
        out = string[byte:] + string[:byte]
    else :
        reg = string[byte:] + string[:byte+1]
        for i in range (0,len(reg)-1):
            out = out + chr(((ord(reg[i])*(2**bit))+(ord(reg[i+1])//(2**(8-bit))))%256)
        out = out[:len(string)]
    return out

class SM4_Object :
    ## 这里定义一些常量,随时取用
    BlockSize = 16
    KeySize = 16
    CK = [ '\x00\x07\x0e\x15' , '\x1c\x23\x2a\x31' , '\x38\x3f\x46\x4d' , '\x54\x5b\x62\x69' , '\x70\x77\x7e\x85' , '\x8c\x93\x9a\xa1' , '\xa8\xaf\xb6\xbd' , '\xc4\xcb\xd2\xd9' ,
    '\xe0\xe7\xee\xf5' , '\xfc\x03\x0a\x11' , '\x18\x1f\x26\x2d' , '\x34\x3b\x42\x49' , '\x50\x57\x5e\x65' , '\x6c\x73\x7a\x81' , '\x88\x8f\x96\x9d' , '\xa4\xab\xb2\xb9' ,
    '\xc0\xc7\xce\xd5' , '\xdc\xe3\xea\xf1' , '\xf8\xff\x06\x0d' , '\x14\x1b\x22\x29' , '\x30\x37\x3e\x45' , '\x4c\x53\x5a\x61' , '\x68\x6f\x76\x7d' , '\x84\x8b\x92\x99' ,
    '\xa0\xa7\xae\xb5' , '\xbc\xc3\xca\xd1' , '\xd8\xdf\xe6\xed' , '\xf4\xfb\x02\x09' , '\x10\x17\x1e\x25' , '\x2c\x33\x3a\x41' , '\x48\x4f\x56\x5d' , '\x64\x6b\x72\x79' ]
    Sbox = ['\xd6' , '\x90' , '\xe9' , '\xfe' , '\xcc' , '\xe1' , '\x3d' , '\xb7' , '\x16' , '\xb6' , '\x14' , '\xc2' , '\x28' , '\xfb' , '\x2c' , '\x05',  
    '\x2b' , '\x67' , '\x9a' , '\x76' , '\x2a' , '\xbe' , '\x04' , '\xc3' , '\xaa' , '\x44' , '\x13' , '\x26' , '\x49' , '\x86' , '\x06' , '\x99',  
    '\x9c' , '\x42' , '\x50' , '\xf4' , '\x91' , '\xef' , '\x98' , '\x7a' , '\x33' , '\x54' , '\x0b' , '\x43' , '\xed' , '\xcf' , '\xac' , '\x62',  
    '\xe4' , '\xb3' , '\x1c' , '\xa9' , '\xc9' , '\x08' , '\xe8' , '\x95' , '\x80' , '\xdf' , '\x94' , '\xfa' , '\x75' , '\x8f' , '\x3f' , '\xa6',  
    '\x47' , '\x07' , '\xa7' , '\xfc' , '\xf3' , '\x73' , '\x17' , '\xba' , '\x83' , '\x59' , '\x3c' , '\x19' , '\xe6' , '\x85' , '\x4f' , '\xa8',  
    '\x68' , '\x6b' , '\x81' , '\xb2' , '\x71' , '\x64' , '\xda' , '\x8b' , '\xf8' , '\xeb' , '\x0f' , '\x4b' , '\x70' , '\x56' , '\x9d' , '\x35',  
    '\x1e' , '\x24' , '\x0e' , '\x5e' , '\x63' , '\x58' , '\xd1' , '\xa2' , '\x25' , '\x22' , '\x7c' , '\x3b' , '\x01' , '\x21' , '\x78' , '\x87',  
    '\xd4' , '\x00' , '\x46' , '\x57' , '\x9f' , '\xd3' , '\x27' , '\x52' , '\x4c' , '\x36' , '\x02' , '\xe7' , '\xa0' , '\xc4' , '\xc8' , '\x9e',  
    '\xea' , '\xbf' , '\x8a' , '\xd2' , '\x40' , '\xc7' , '\x38' , '\xb5' , '\xa3' , '\xf7' , '\xf2' , '\xce' , '\xf9' , '\x61' , '\x15' , '\xa1',  
    '\xe0' , '\xae' , '\x5d' , '\xa4' , '\x9b' , '\x34' , '\x1a' , '\x55' , '\xad' , '\x93' , '\x32' , '\x30' , '\xf5' , '\x8c' , '\xb1' , '\xe3',  
    '\x1d' , '\xf6' , '\xe2' , '\x2e' , '\x82' , '\x66' , '\xca' , '\x60' , '\xc0' , '\x29' , '\x23' , '\xab' , '\x0d' , '\x53' , '\x4e' , '\x6f',  
    '\xd5' , '\xdb' , '\x37' , '\x45' , '\xde' , '\xfd' , '\x8e' , '\x2f' , '\x03' , '\xff' , '\x6a' , '\x72' , '\x6d' , '\x6c' , '\x5b' , '\x51',  
    '\x8d' , '\x1b' , '\xaf' , '\x92' , '\xbb' , '\xdd' , '\xbc' , '\x7f' , '\x11' , '\xd9' , '\x5c' , '\x41' , '\x1f' , '\x10' , '\x5a' , '\xd8',  
    '\x0a' , '\xc1' , '\x31' , '\x88' , '\xa5' , '\xcd' , '\x7b' , '\xbd' , '\x2d' , '\x74' , '\xd0' , '\x12' , '\xb8' , '\xe5' , '\xb4' , '\xb0',  
    '\x89' , '\x69' , '\x97' , '\x4a' , '\x0c' , '\x96' , '\x77' , '\x7e' , '\x65' , '\xb9' , '\xf1' , '\x09' , '\xc5' , '\x6e' , '\xc6' , '\x84',  
    '\x18' , '\xf0' , '\x7d' , '\xec' , '\x3a' , '\xdc' , '\x4d' , '\x20' , '\x79' , '\xee' , '\x5f' , '\x3e' , '\xd7' , '\xcb' , '\x39' , '\x48']

    

    def SearchSbox( self , reg , Act ):
        out = ""
        if Act == 1 :
            for i in range (0,4) :
                out = out + self.Sbox[ord(reg[i])]
        else :
            for i in range (0,4) :
                out = out + chr(self.Sbox.index(reg[i]))
        return reg

    def functionT( self , Attr , RK , Act , i ) :
        if Act == 1 :
            reg = strxor( strxor( Attr[4:8] , Attr[8:12] , 4 ) , strxor( Attr[12:16] , RK[i] , 4 ) , 4 )
            reg = self.SearchSbox ( reg , 1 )
        else :
            reg = strxor( strxor( Attr[4:8] , Attr[8:12] , 4 ) , strxor( Attr[12:16] , RK[31-i] , 4 ) , 4 )
            reg = self.SearchSbox ( reg , -1 )
        reg = strxor( strxor( strxor( reg , strldc(reg,2) , 4 ) , strxor( strldc(reg,10) , strldc(reg,18) , 4 ) , 4 ) , strldc(reg,24) , 4 )
        Attr = Attr[4:16] + strxor(Attr[0:4],reg,4)
        return Attr

    def functionTK( self , Attr , i ) :
        reg = strxor( strxor( Attr[4:8] , Attr[8:12] , 4 ) , strxor( Attr[12:16] , self.CK[i] , 4 ) , 4 )
        reg = self.SearchSbox ( reg , 1 )
        reg = strxor( strxor( reg , strldc(reg,13) , 4 ) , strldc(reg,23) , 4 )
        Attr = Attr[4:16] + strxor(Attr[0:4],reg,4)
        return Attr

    def ECB_enc( self , plain , MK , FK ) :
        print( "轮密钥生成中……" )
        RK = []
        Attr = strxor( MK , FK , 16 )
        for i in range (0,32):
            Attr = self.functionTK( Attr , i )
            RK.append( Attr[12:16] )
            #print( "RK [" , i+1 , "]" , str2hex(RK[i]) )
        #ECB加密模式
        cipher = ""
        for l in range (0,len(plain)//16):
            Attr = plain[16*l:16*(l+1)]
            for i in range (0,32) :
                Attr = self.functionT( Attr , RK , 1 , i )
            cipher = cipher + Attr[12:16] + Attr[8:12] + Attr[4:8] + Attr[0:4]
        return cipher

    def CBC_enc( self , plain , MK , FK ) :
        print( "轮密钥生成中……" )
        RK = []
        Attr = strxor( MK , FK , 16 )
        for i in range (0,32):
            Attr = self.functionTK( Attr , i )
            RK.append( Attr[12:16] )
            #print( "RK [" , i+1 , "]" , str2hex(RK[i]) )
        cipher = ""
        for l in range (0,len(plain)//16):
            if l == 0 :
                Attr = plain[0:16]
            else :
                Attr = strxor( plain[16*l:16*(l+1)] , Attr[12:16] + Attr[8:12] + Attr[4:8] + Attr[0:4] , 16 )
            for i in range (0,32) :
                Attr = self.functionT( Attr , RK , 1 , i )
            cipher = cipher + Attr[12:16] + Attr[8:12] + Attr[4:8] + Attr[0:4]
        return cipher

    def ECB_dec( self , cipher , MK , FK ) :
        print( "轮密钥生成中……" )
        RK = []
        Attr = strxor( MK , FK , 16 )
        for i in range (0,32):
            Attr = self.functionTK( Attr , i )
            RK.append( Attr[12:16] )
            #print( "RK [" , i+1 , "]" , str2hex(RK[i]) )
        ##加密轮函数启动
        plain = ""
        for l in range (0,len(cipher)//16):
            Attr = cipher[16*l:16*(l+1)]
            for i in range (0,32) :
                Attr = self.functionT( Attr , RK , 0 , i )
            plain = plain + Attr[12:16] + Attr[8:12] + Attr[4:8] + Attr[0:4]
        return plain

    def CBC_dec( self , cipher , MK , FK ) :
        print( "轮密钥生成中……" )
        RK = []
        Attr = strxor( MK , FK , 16 )
        for i in range (0,32):
            Attr = self.functionTK( Attr , i )
            RK.append( Attr[12:16] )
            #print( "RK [" , i+1 , "]" , str2hex(RK[i]) )
        ##加密轮函数启动
        plain = ""
        for l in range (0,len(cipher)//16):
            Attr = cipher[16*l:16*(l+1)]
            for i in range (0,32) :
                Attr = self.functionT( Attr , RK , 0 , i )
            if l > 0 :
                plain = plain + strxor( Attr[12:16] + Attr[8:12] + Attr[4:8] + Attr[0:4] , cipher[16*(l-1):16*l] , 16 )
            else :
                plain = plain + Attr[12:16] + Attr[8:12] + Attr[4:8] + Attr[0:4]
        return plain

    def ECB_enc_file( self , path_plain , path_cipher , MK , FK ) :
        f_plain = open( path_plain , "r" )
        f_cipher = open( path_cipher , "wb" )
        plain = f_plain.read()
        print( "plain =" , str2hex(plain) )
        if len(plain) % 16 != 0 :
            for i in range (0,16-len(plain)%16):
                plain =  plain + " "
        print ( " plain =" , str2hex(plain) )
        print ( "Length =" , len(plain) )
        f_plain.close()
        cipher = self.ECB_enc( plain , MK , FK )
        print( "Cipher =" , str2hex(cipher) )
        # 这里由于正常密文存储有问题,涉及到非正常字符的存取,目前还没有解决编码的问题,用一个特殊的文件存储方法pickle
        pickle.dump( cipher , f_cipher )
        f_cipher.close()
        return cipher

    def ECB_dec_file( self , path_plain , path_cipher , MK , FK ) :
        f_plain = open( path_plain , "wb" )
        f_cipher = open( path_cipher , "rb" )
        #cipher = f_cipher.read()
        cipher = pickle.load( f_cipher )
        print ( "Cipher =" , str2hex(cipher) )
        print ( "Length =" , len(cipher) )
        f_cipher.close()
        plain = self.ECB_dec( cipher , MK , FK )
        print( " Plain =" , str2hex(plain) )
        plain = plain.encode("utf-8")
        f_plain.write(plain)
        f_plain.close()
        return plain

    def CBC_enc_file( self , path_plain , path_cipher , MK , FK ) :
        f_plain = open( path_plain , "r" )
        f_cipher = open( path_cipher , "wb" )
        plain = f_plain.read()
        print( "plain =" , str2hex(plain) )
        if len(plain) % 16 != 0 :
            for i in range (0,16-len(plain)%16):
                plain =  plain + " "
        print ( " plain =" , str2hex(plain) )
        print ( "Length =" , len(plain) )
        f_plain.close()
        cipher = self.CBC_enc( plain , MK , FK )
        print( "Cipher =" , str2hex(cipher) )
        # 这里由于正常密文存储有问题,涉及到非正常字符的存取,目前还没有解决编码的问题,用一个特殊的文件存储方法pickle
        pickle.dump( cipher , f_cipher )
        f_cipher.close()
        return cipher

    def CBC_dec_file( self , path_plain , path_cipher , MK , FK ) :
        f_plain = open( path_plain , "wb" )
        f_cipher = open( path_cipher , "rb" )
        #cipher = f_cipher.read()
        cipher = pickle.load( f_cipher )
        print ( "Cipher =" , str2hex(cipher) )
        print ( "Length =" , len(cipher) )
        #cipher = cipher.encode(encoding="ANSI")
        f_cipher.close()
        plain = self.CBC_dec( cipher , MK , FK )
        print( " Plain =" , str2hex(plain) )
        plain = plain.encode("utf-8")
        f_plain.write(plain)
        f_plain.close()
        return plain

    def RoundKeyGen( self , MK , FK ) :
        RK = []
        Attr = strxor( MK , FK , 16 )
        for i in range (0,32):
            Attr = self.functionTK( Attr , i )
            RK.append( Attr[12:16] )
            print( "RK [" , i+1 , "]" , str2hex(RK[i]) )
        return RK

    文件加密中,事实上也是将文件的内容提取为字符串,然后对字符串加密写入另外一个文件当中,至于为什么要使用pickle这样一个有点瑕疵的存储方式,我们在后面的问题分析中再进行解释。

四、进行可视化界面编写

    这里使用的GUI是Python最亲的TKinterface,毕竟自家的界面也是用这个写的,具体怎么编写这个程序我们不进行详述,供大家参考一下,主要目的为应用这个SM4类。

from tkinter import *
from tkinter import filedialog
from tkinter import messagebox

# 此处省略上文的代码……

def SM4_Data_Enc() :
    MK = eMK.get()
    if len(MK) > 16 :
        MK = MK[0:32]
    else :
        showwarning( "Error :" , "警告:您输入的主密钥长度过短,请确认MK输入栏中长度超过16字节!" , "确认" )
        return
    plain = eplain.get( 0.0 , END )
    plain = plain[0:len(plain)-1]
    if len(plain) % 16 != 0 :
        for i in range (0,16-len(plain)%16):
            plain =  plain + " "
    ENCRYPT = SM4_Object()
    if runmode.get() == 1 :
        cipher = ENCRYPT.ECB_enc( plain , eMK.get() , eFK.get() )
    else :
        cipher = ENCRYPT.CBC_enc( plain , eMK.get() , eFK.get() )
    eplainhex.delete( 0.0 , END )
    eplainhex.insert( 0.0 , str2hex(plain) )
    ecipher.delete( 0.0 , END )
    ecipher.insert( 0.0 , cipher )
    ecipherhex.delete( 0.0 , END )
    ecipherhex.insert( 0.0 , str2hex(cipher) )
    return

def SM4_Data_Dec() :
    MK = eMK.get()
    if len(MK) > 16 :
        MK = MK[0:32]
    else :
        messagebox.showwarning( "Error :" , "警告:您输入的主密钥长度过短,请确认MK输入栏中长度超过16字节!" )
        return
    cipher = ecipher.get( 0.0 , END )
    cipher = cipher[0:len(cipher)-1]
    ENCRYPT = SM4_Object()
    if runmode.get() == 1:
        plain = ENCRYPT.ECB_dec( cipher , eMK.get() , eFK.get() )
    else :
        plain = ENCRYPT.CBC_dec( cipher , eMK.get() , eFK.get() )
    eplainhex.delete( 0.0 , END )
    eplainhex.insert( 0.0 , str2hex(plain) )
    eplain.delete( 0.0 , END )
    eplain.insert( 0.0 , plain )
    ecipherhex.delete( 0.0 , END )
    ecipherhex.insert( 0.0 , str2hex(cipher) )
    return

def SM4_File_Enc() :
    f_plain = eSrc.get()
    f_cipher = eAim.get()
    ENCRYPT_FILE = SM4_Object()
    if runmode.get() == 1:
        cipher = ENCRYPT_FILE.ECB_enc_file( f_plain , f_cipher , eMK.get() , eFK.get() )
    else :
        cipher = ENCRYPT_FILE.CBC_enc_file( f_plain , f_cipher , eMK.get() , eFK.get() )
    Fplain = open( f_plain , "r" )
    plain = Fplain.read()
    if len(plain) % 16 != 0 :
        for i in range (0,16-len(plain)%16):
            plain =  plain + " "
    eplain.delete( 0.0 , END )
    eplain.insert( 0.0 , plain )
    eplainhex.delete( 0.0 , END )
    eplainhex.insert( 0.0 , str2hex(plain) )
    ecipher.delete( 0.0 , END )
    ecipher.insert( 0.0 , cipher )
    ecipherhex.delete( 0.0 , END )
    ecipherhex.insert( 0.0 , str2hex(cipher) )
    return

def SM4_File_Dec() :
    f_plain = eSrc.get()
    f_cipher = eAim.get()
    ENCRYPT_FILE = SM4_Object()
    if runmode.get() == 1:
        plain = ENCRYPT_FILE.ECB_dec_file( f_plain , f_cipher , eMK.get() , eFK.get() )
    else :
        plain = ENCRYPT_FILE.CBC_dec_file( f_plain , f_cipher , eMK.get() , eFK.get() )
    Fcipher = open( f_cipher , "rb" )
    cipher = pickle.load( cipher )
    eplain.delete( 0.0 , END )
    eplain.insert( 0.0 , plain )
    eplainhex.delete( 0.0 , END )
    eplainhex.insert( 0.0 , str2hex(plain) )
    ecipher.delete( 0.0 , END )
    ecipher.insert( 0.0 , cipher )
    ecipherhex.delete( 0.0 , END )
    ecipherhex.insert( 0.0 , str2hex(cipher) )
    return

def callbackSrc() :
    filepath =  filedialog.askopenfilename()
    eSrc.delete( 0 , END )
    eSrc.insert( 0 , filepath )
    return

def callbackAim() :
    filepath =  filedialog.askopenfilename()
    eAim.delete( 0 , END )
    eAim.insert( 0 , filepath )
    return

root = Tk()
root.title( "国家商用密码 SM4 调试程序 -v1.0" )
group = LabelFrame( root , text = " MK & FK settings ( FOR SM4 CIPHER ) : " , padx = 20 , pady = 20 )
group.grid( row = 1 , column = 0 , sticky = W , padx = 10 , pady = 10 )
Label( group , text = "  MK  =" ).grid( row = 1 , column = 1 )
Label( group , text = "  FK  =" ).grid( row = 2 , column = 1 )
Label( group , text = "Source:" ).grid( row = 3 , column = 1 )
Label( group , text = "Saveto:" ).grid( row = 4 , column = 1 )
eMK = Entry( group , width = 48 )
eMK.grid( row = 1 , column = 2 , padx = 10 , pady = 10 )
eFK = Entry( group , width = 48 )
eFK.grid( row = 2 , column = 2 , padx = 10 , pady = 10 )
eFK.delete( 0 , END )
eFK.insert( 0 , "ZhaoWenhao155104" )
eSrc = Entry( group , width = 48 )
eSrc.grid( row = 3 , column = 2 , padx = 10 , pady = 10 )
eSrc.delete( 0 , END )
eSrc.insert( 0 , "源文件路径……" )
eAim = Entry( group , width = 48 )
eAim.grid( row = 4 , column = 2 , padx = 10 , pady = 10 )
eAim.delete( 0 , END )
eAim.insert( 0 , "目标文件路径……" )
runmode = IntVar()
runmode.set(1)
Radiobutton( group , text = "ECB工作模式" , variable = runmode , value = 1 ).grid( row = 1 , column = 3 , sticky = W , padx = 20 )
Radiobutton( group , text = "CBC工作模式" , variable = runmode , value = 2 ).grid( row = 2 , column = 3 , sticky = W , padx = 20 )
Button( group , text = " 浏  览 " , command = callbackSrc ).grid( row = 3 , column = 3 , sticky = W , padx = 20 )
Button( group , text = " 浏  览 " , command = callbackAim ).grid( row = 4 , column = 3 , sticky = W , padx = 20 )
Button( group , text = "数据加密" , command = SM4_Data_Enc ).grid( row = 1 , column = 4 , sticky = W )
Button( group , text = "数据解密" , command = SM4_Data_Dec ).grid( row = 2 , column = 4 , sticky = W )
Button( group , text = "文件加密" , command = SM4_File_Enc ).grid( row = 3 , column = 4 , sticky = W )
Button( group , text = "文件解密" , command = SM4_File_Dec ).grid( row = 4 , column = 4 , sticky = W )
Label( group , text = "\t使用须知:\n数据加密无需输入文件路径,仅需确认MK和FK后填写Plain文本框,点击数据加密。\n解密时填写Cipher文本框,点击数据解密。\n文件加解密时需要浏览路径或手动输入,填写MK和FK后点击文件加/解密。" ).grid( row = 1 , rowspan = 4 , column = 5 )

group2 = LabelFrame( root , text = "Plain & Cipher" , padx = 10 , pady = 10 )
group2.grid( row = 5 , column = 0 , sticky = W , padx = 10 , pady = 10 )
Label(group2,text = "Plain:").grid(row = 1 , column = 1)
Label(group2,text = "Cipher:").grid( row = 1 , column = 2 )
eplain = Text( group2 , width = 70 , height = 14 )
eplain.grid(row = 2 , column = 1 , sticky = W , padx = 10 , pady = 10)
ecipher = Text(group2 , width = 70 , height = 14)
ecipher.grid( row = 2 , column = 2 , sticky = W , padx = 10 , pady = 10 )
Label(group2,text = "Plain(Hex):").grid(row = 3 , column = 1)
Label(group2,text = "Cipher(Hex):").grid( row = 3 , column = 2 )
eplainhex = Text( group2 , width = 70 , height = 14 )
eplainhex.grid(row = 4 , column = 1 , sticky = W , padx = 10 , pady = 10)
ecipherhex = Text(group2 , width = 70 , height = 14)
ecipherhex.grid( row = 4 , column = 2 , sticky = W , padx = 10 , pady = 10 )

mainloop()

    该窗体工具提供了SM4算法的简单应用,使用方法较为简单,其中加解密运算过程全部是调用SM4类中的方法实现,上段代码仅仅是控制了输入输出和控件布局,虽然界面比较简单,但程序运行较流畅,没有明显缺陷。下面给出该工具的实现效果:

    还行读者更多尝试,交流学习,不断改善,非常感谢!

五、实现难点

1.密文的存储方式

    众所周知,经过密码算法的生成的密文是一种高度随机的,无规律的字符流,也就是说会有很多比较特殊的字符存在,这就产生了一个问题。Python 3对文件中编码默认使用Unicode而并非ASCII码,在存储时会存储成宽字符,这样读取后的内容会与原文产生变化。在实际操作过程中,作者多次使用encode和decode方法对文件存储进行处理,但是经常会出现读取不正确、编码无法对应的情况,在显示十六进制时报错的现象。即使ANSI是ASCII码的一种扩展编码方式,但仍存在有些字符串无法编码保存的问题。

    这时候我退而求其次,选择了一个pickle方法,就是将某个变量存储在文件中,这样文件中会保存有关该变量的所有信息,包括变量名、类型、内容等项目,显然是多出了不必要的存储量,且只能用pickle方法取用该变量,解密方要求提高了。直接打开文本文件后看到的并非是一个原本的密文形态,而是经过了pickle方法处理后的包装形态,这样显然并不是一个很好的解决方式,当然我也在寻求一个更好的解决方法。

    pickle方法有个好处就是,我们可以将变量原封不动进行长久保存,也就是说,整型数取用时也还是整型数,字典还是字典,列表还是列表,所有的变量都可以使用该方法进行文件存储。该方法要求打开文件时使用二进制模式写入或读取。写入方法为pickle.dump(var,pickle_file),读取方法为pickle.load(pickle_file),可以保存任何你能想象出来的内容。

2.图形界面的字符串处理

    在图形界面中,我们在Plain文本框输入一段明文,然后将其处理成为分组倍数长度的字符串,但是在实现过程中发现了文本框会自动在文本结束处添加一个结束符\x0a,这样就会导致测试中,对一个明文循环进行加解密,数据会越来越长,且出现期望外的值,这样是影响该工具正常使用的。

    所以在进行读取文本框内容操作后,要立刻去除字符串的最后一个字节的内容,这样我们读取的内容才是正确的,不做无意义的额外操作。

    另外,Entry输入交互控件没有察觉到由于补充字节而产生的错误,故没有进行处理。MK和FK由于不用做商业软件,所以没有进行隐藏输入的处理,这样便于进行对比,以及测试操作。

六、本节感想

    本篇是对第一部分的SM4试做程序进行了功能扩展并设计了一个小工具。可以说对整个毕业设计的课题来说做了一些无用功,但这也算是一个题外的,让自己的代码体现价值的小加工,在做的时候也比较享受。在不断添加功能时会遇到很多新的问题,要想办法去解决它,就需要不断地提升自己,学会更多方法解决困难!

猜你喜欢

转载自www.cnblogs.com/Qujinkongyuyin/p/10358110.html