问题产生背景
开发使用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_encrypt01( self, 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_16( self, 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_hex( self, 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_16( self, 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 140, in 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")==0) else 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进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




