暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

python3.6以上版本AES中ECB模式的加密与解密

软件测试微课堂 2020-05-03
3996

问题产生背景

开发使用java语言对电话号码进行了加密入库,测试工程师测试时在原始版本python3.5中,对数据采用AES中ECB模式的加密与解密,输出为binascii类型时是正常,但是当我们将python版本升级为3.6.2以后会发现,执行加密函数时报错

TypeError: Object type <class 'str'> cannot be passed to C code

问题研究

一步步运行我们会发现代码发生错误的地方为

cipher.encrypt()
首先我们先看一下在python3.5中,我们写的加解密函数验证都是没问题的,具体代码搬运如下

def AES_encrypt01self, data, key ):
    BS = AES.block_size
    pad = lambda s: s + (BS - len( s ) % BS) * chr( BS - len( s ) % BS )
    a= pad( data )
    cipher = AES.new( self.add_to_16( key ), AES.MODE_ECB )
    encrypt_data = b2a_hex( cipher.encrypt( pad( data ) ) )  # b2a_hex:
    new_encrypt_data = encrypt_data.decode('utf-8')
    return new_encrypt_data


def AES_decrypt(self,src,key):
    unpad = lambda s: s[0:-ord(s[-1])]
    key = key[0:16]
    # cryptor=AES.new(key)  #没有iv,只需要传key
    cryptor = AES.new(self.add_to_16(key),AES.MODE_ECB )
    b_text = cryptor.decrypt(a2b_hex(src))  #去掉\0
    # print(b_text)
    text = b_text.decode( "utf8""ignore")
    return unpad(text)

def add_to_16self, text ):
    while len( text ) % 16 != 0:
        text += '0'   # 不够16位时用0补齐
    return str.encode( text )  # 返回bytes

当python升级为3.6+后会发现运行报如上错误,那么先从python源码库上看一下cipher.encrypt()的异同,python3.5截取如下

def encrypt(self, plaintext):
    if self.mode == MODE_OPENPGP:
        padding_length = (self.block_size - len(plaintext) % self.block_size) % self.block_size
        if padding_length>0:
            # CFB mode requires ciphertext to have length multiple of block size,
            # but PGP mode allows the last block to be shorter
            if self._done_last_block:
                raise ValueError("Only the last chunk is allowed to have length not multiple of %d bytes",
                    self.block_size)
            self._done_last_block = True
            padded = plaintext + b('\x00')*padding_length
            res = self._cipher.encrypt(padded)[:len(plaintext)]
        else:
            res = self._cipher.encrypt(plaintext)
        if not self._done_first_block:
            res = self._encrypted_IV + res
            self._done_first_block = True
        return res

    return self._cipher.encrypt(plaintext)

在python3.7中截取如下

    def encrypt(self, plaintext, output=None):
        if output is None:
            ciphertext = create_string_buffer(len(plaintext))
        else:
            ciphertext = output

            if not is_writeable_buffer(output):
                raise TypeError("output must be a bytearray or a writeable memoryview")

            if len(plaintext) != len(output):
                raise ValueError("output must have the same length as the input"
                                 "  (%d bytes)" % len(plaintext))

        result = raw_ecb_lib.ECB_encrypt(self._state.get(),
                                         c_uint8_ptr(plaintext),
                                         c_uint8_ptr(ciphertext),
                                         c_size_t(len(plaintext)))
        if result:
            if result == 3:
                raise ValueError("Data must be aligned to block boundary in ECB mode")
            raise ValueError("Error %d while encrypting in ECB mode" % result)

        if output is None:
            return get_raw_buffer(ciphertext)
        else:
            return None

可以发现在python3.7中增加了一个入参output,并且里面的实现方式也是不一样了,找到了问题所在就得去想解决方法。

问题解决

编不下去了直接上源码吧

def set_encrypt_hexself, data, key = 'carniu_fighter' ):
    BS = AES.block_size
    pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
    cipher = AES.new(self.add_to_16(key), AES.MODE_ECB)
    datas = pad(data).encode(encoding='utf-8')
    encrypt_data = b2a_hex(cipher.encrypt(datas))  # b2a_hex:
    new_encrypt_data = encrypt_data.decode('utf-8')
    return new_encrypt_data

def set__decrypt_hex(self,src, key = "carniu_fighter"):
    cryptor = AES.new(self.add_to_16(key),AES.MODE_ECB )
    b_text = cryptor.decrypt(a2b_hex(src))  #去掉\0
    return b_text.decode().replace("\x06""").replace("\x05""").replace("\x07""").replace("\n""")
def add_to_16self, text ):
    while len( text ) % 16 != 0:
        text += '0'   # 不够16位时用0补齐
    return str.encode( text )  # 返回bytes 

好,这样就把phone加解密的问题解决了,但是我们测试工程师要多去思考,多去总结。这次是已经将本次的问题完美解决了,但是如果说要加密的是姓名呢,会发现报错如下了:

File "C:\Users\Admin\venv\lib\site-packages\Crypto\Cipher\_mode_ecb.py", line 140in encrypt
    raise ValueError("Data must be aligned to block boundary in ECB mode")
ValueError: Data must be aligned to block boundary in ECB mode

报错的大概意思就是ecb模式的位数得是16、128等这些位数
继续修改方案,但是一直还是有问题的,无法兼容。那么就改想到其它的方案来解决这个棘手问题,首先我们知道的是开发代码运行时绝对没问题的,那么是否可以通过调用开发方法来实现加解密呢,就好比python连hive一样。答案是可以的。python语言提供了这种方法,具体可以    去看这个包jpype。
首先我们需要创建一个连接java的类,通过实例化这个类间接的使用java。方法如下

from jpype import *
import os.path
import platform


class GetJavaClass():
    def __init__(self):
        #当前操作系统
        environment_type = platform.platform()
        # 系统java路径
        self.javapath = "C:/Program Files/Java/jdk1.8.0_73/jre/bin/server/jvm.dll" if(environment_type.find("Window")==0else jpype.getDefaultJVMPath()
        # jar 地址
        self.jarpath = os.path.join(os.path.abspath('./'), 'source/jar/')
        print(self.jarpath)
        # jar 依赖的jar包地址
        self.dependency = os.path.join(os.path.abspath('./'), 'source/dependency/')
        print(self.dependency)

    def get_java_obj(self, package_info, jar):
        """
        获取java实例对象
        :param package_info:jar包信息(test为jar包名;test3对应包内的类名 test.test3)
        :param jar:jar包名 例如:'test.jar'
        """

        # 当有依赖的JAR包存在时,一定要使用-Djava.ext.dirs参数进行引入
        # ("-Djava.ext.dirs=%s" %dependency)
        # 判断虚拟机是否存活,启动虚拟机
        if jpype.isJVMStarted():
            print("虚拟机已启动,无需再次启动虚拟机")
        else:
            print("启动虚拟机")
            jpype.startJVM(self.javapath, "-ea",
                         "-Djava.class.path=%s" % (self.jarpath + jar), "-Djava.ext.dirs=%s" % self.dependency)
        # 4、将JAVA类转化成Python类:cryption.aes是在java中AESUtils所在的包名                
        j_obj = jpype.JPackage('cryption.aes').AESUtils
        return j_obj

    @classmethod
    def shutdown(cls):
        shutdownJVM()

具体的调用方法是如何的呢?使用这个类里面的方法即可get_java_obj()

aetil = GetJavaClass().get_java_obj(package_info, jar)
aesResult = aetil.encrypt("zhangxingyuan","1234567891234567")
print(aesResult)
desResult = aetil.decrypt(aesResult,'1234567891234567')
print(desResult)

原创文章首发认准 软件测试微课堂



文章转载自软件测试微课堂,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论