数据损坏:错误在哪里‽
最后编辑:我已经找到了问题所在(请看我下面的回答),但似乎无法将这个问题标记为已解决。如果有人能回答我下面回答中的问题,也就是,这是不是Cython的一个bug,还是Cython的预期行为,我会把那个回答标记为接受,因为我认为这是最有价值的教训。
首先,我得说我已经花了三天时间在搞这个问题,真的是头撞墙。根据文档来看,我觉得我做的没错。但显然,我做的并不对,因为如果对的话,我就不会有问题了(对吧?)。
无论如何,我正在为Python开发一个mcrypt的绑定。它应该可以在Python 2和Python 3上工作(不过在Python 2上还没测试过)。可以在我的网站上找到,因为文件太大,无法直接放在帖子里。由于我不知道自己哪里出错了,甚至无法找出可能出问题的代码。显示问题的脚本也在我的网站上。这个脚本只是输入100个全是字母"a"的块(根据加密算法/模式的块大小),结果应该是返回一个全是"a"的块。但实际上并不总是这样。以下是它单次运行的输出:
Wed Dec 15 10:35:44 EST 2010
test.py:5: McryptSecurityWarning: get_key() is not recommended
return ''.join(['{:02x}'.format(x) for x in o.get_key()])
key: b'\x01ez\xd5\xa9\xf9\x1f)\xa0G\xd2\xf2Z\xfc{\x7fn\x02?,\x08\x1c\xc8\x03\x061X\xb5\xc9\x99\xd0\xca'
key: b'\x01ez\xd5\xa9\xf9\x1f)\xa0G\xd2\xf2Z\xfc{\x7fn\x02?,\x08\x1c\xc8\x03\x061X\xb5\xc9\x99\xd0\xca'
16
self test result: 0
enc parameters: {'salt': '6162636465666768', 'mode': 'cbc', 'algorithm': 'rijndael-128', 'iv': '61626364616263646162636461626364'}
dec parameters: {'salt': '6162636465666768', 'mode': 'cbc', 'algorithm': 'rijndael-128', 'iv': '61626364616263646162636461626364'}
enc key: 01657ad5a9f91f29a047d2f25afc7b7f6e023f2c081cc803063158b5c999d0ca
dec key: 01657ad5a9f91f29a047d2f25afc7b7f6e023f2c081cc803063158b5c999d0ca
Stats: 88 / 100 good packets (88.0%)
#5: b'aaaaaaaaaaaaaaaa' != b'\xa6\xb8\xf9\td\x8db\xf6\x00Y"ST\xc6\x9b\xe7'
#6: b'aaaaaaaaaaaaaaaa' != b'aaaaaaa1\xb3@\x8d\xff\xf9\xafpy'
#13: b'aaaaaaaaaaaaaaaa' != b'\xb9\xc8\xaf\x1f\xb8\x8c\x0b_\x15s\x9d\xecN,*w'
#14: b'aaaaaaaaaaaaaaaa' != b'aaaaaaaaaaaaa\xeb?\x13'
#49: b'aaaaaaaaaaaaaaaa' != b'_C\xf2\x15\xd5k\xe1XKIF5k\x82\xa4\xec'
#50: b'aaaaaaaaaaaaaaaa' != b'aaaaaaaaaaa+\xdf>\x01\xee'
#74: b'aaaaaaaaaaaaaaaa' != b'\x1c\xdf0\x05\xc7\x0b\xe9\x93H\xc5B\xd7\xcfj+\x03'
#75: b'aaaaaaaaaaaaaaaa' != b'aaaaaaaaaaaaw+\xed\x0f'
#79: b'aaaaaaaaaaaaaaaa' != b"\xf2\x89\x1ct\xe1\xeeBWo\xb4-\xb9\x085'\xef"
#80: b'aaaaaaaaaaaaaaaa' != b'aaaaaaaaaaa\xcc\x01n\xf0<'
#91: b'aaaaaaaaaaaaaaaa' != b'g\x02\x08\xbf\xa5\xd7\x90\xc1\x84D\xf3\x9d$a)\x06'
#92: b'aaaaaaaaaaaaaaaa' != b'aaaaaaaaaaaaaaa\x01'
奇怪的是,对于给定的(算法,模式)组合,结果是完全一样的。我可以换算法,结果会不同,但如果不换算法,每次运行的结果都是一样的。我真的很困惑。而且,输出中可以看到,总是有两个连续的块是错误的:块5和6,13和14,等等。所以,虽然有规律可循,但我却不知道这个规律到底指向什么。
我意识到我可能问得太多了:我无法隔离出一小段代码,而且可能需要对mcrypt和Python都有一定了解。唉,经过三天的纠结,我需要暂时离开这个问题,所以我在这里发帖,希望在我休息的时候,(a) 有人能看到我哪里引入了bug,(b) 我在稍后回到这个问题时能发现我的bug,或者 (c) 有人或者我自己能找到问题,可能不是我代码的bug,而是绑定过程或库本身的bug。
我还没尝试过使用另一个版本的mcrypt库。我是在Ubuntu 10.10上用Cython 0.13、Python 3.1和mcrypt 2.5.8进行工作的(除了Cython是从PyPi下载的)。不过,我管理的PHP应用在Ubuntu 10.10上运行得很好,使用mcrypt也没有数据损坏,所以我没有理由相信是mcrypt的构建有问题,所以我想这只是我某个地方出错了。
无论如何,非常感谢任何能提供帮助的人。我开始觉得自己快要疯了,因为我几乎不停地在处理这个问题,感觉解决方案就在眼前,但我就是看不见。
编辑:有人指出我应该使用memcpy而不是strncpy。我照做了,但现在测试脚本显示每个块都是错误的。让我更加困惑……这是新的输出在pastebin上。
编辑2:我回到电脑前又看了一遍,正在到处添加打印语句,找出问题可能出在哪里。在raw_encrypt.step(input)函数中的以下代码:
cdef char* buffer = <char*>malloc(in_len)
print in_bin[:in_len]
memcpy(buffer, <const_void *>in_bin, in_len)
print "Before/after encryption"
print buffer[:in_len]
success = cmc.mcrypt_generic(self._mcStream, <void*>buffer, in_len)
print buffer[:in_len]
第一个打印语句显示的是预期的内容,也就是传入的明文。然而,第二个打印语句显示的却完全不同,应该是一样的。看起来Cython中发生了一些我不太理解的事情。
2 个回答
我遇到过类似的情况,通常是因为在加密和解密时使用了不同的初始化向量(IV),或者选择了不合适的加密模式。作为一个简单的检查,可以试着把加密模式从CBC换成ECB。
还有一种可能性是,你的某个变量在不该随机化的时候被随机化了(比如用新的时间种子)。在这种情况下,如果你在加密和解密之间加一个延迟,可能会更频繁地导致数据损坏。
唉,我不想这样做(回答我自己的问题),但我找到了答案:这是Cython的一个怪癖,我得好好研究一下(我不知道这是故意的怪癖,还是一个bug)。
问题出在memcpy这一行。我把第二个参数转换成了<const_void*>,这和pxd文件中的Cython定义是匹配的,但显然这让Cython以不同的方式编译代码,而不是用<char*>。后者迫使Cython传递一个指向实际字节的指针,而不是(我猜)指向Python对象/变量本身的指针。
所以,不是这样:
cdef char* buffer = <char*>malloc(in_len)
memcpy(buffer, <const_void *>in_bin, in_len)
success = cmc.mcrypt_generic(self._mcStream, <void*>buffer, in_len)
而是需要这样:
cdef char* buffer = <char*>malloc(in_len)
memcpy(buffer, <char *>in_bin, in_len)
success = cmc.mcrypt_generic(self._mcStream, <void*>buffer, in_len)
真是个奇怪的怪癖。老实说,我本来以为任何类型的转换都应该指向同一个位置,但看起来这个转换也会影响行为。