2016-11-26 321 views
1

我有一个Python应用程序和PHP网站,它们通过一些特定的网络层发送消息进行通信。我的任务是使用该通道发送AES加密和base64编码的所有消息。加密密钥是手动为双方预先共享的。使用openssl_encrypt AES-CBC进行Python-to-PHP兼容的AES加密

在我的PHP,我用这个代码来创建一个名为$payload最终消息文本:

$key = substr('abdsbfuibewuiuizasbfeuiwhfashgfhj56urfgh56rt7856rh', 0, 32); 
$magic = 'THISISANENCRYPTEDMESSAGE'; 

function crypted($data) { 
     global $key, $magic; 

     // serialize 
     $payload = json_encode($data); 

     // encrypt and get base64 string with padding (==): 
     $payload = @openssl_encrypt($payload, 'AES-192-CBC', $key); 

     // prepend with magic 
     $payload = $magic.$payload; 
     return $payload; 
    } 

我和我的Python应用程序收到这样的消息,剥离神奇,越来越的base64字节的数据。我无法找到一个示例使兼容的AES密码解码此消息的问题。

关键和“魔术”只是预先分享和双方都知道的值,这是正确的吗?我需要IV吗?

这是来自SO的Python解决方案,不适用于我的加密消息。

from base64 import b64encode, b64decode 
from Crypto.Cipher import AES 


class AESCipher: 

    class InvalidBlockSizeError(Exception): 
     """Raised for invalid block sizes""" 
     pass 

    def __init__(self, key): 
     self.key = key 
     self.iv = bytes(key[0:16], 'utf-8') 

    def __pad(self, text): 
     text_length = len(text) 
     amount_to_pad = AES.block_size - (text_length % AES.block_size) 
     if amount_to_pad == 0: 
      amount_to_pad = AES.block_size 
     pad = chr(amount_to_pad) 
     return text + pad * amount_to_pad 

    def __unpad(self, text): 
     pad = ord(text[-1]) 
     return text[:-pad] 

    def encrypt(self, raw): 
     raw = self.__pad(raw) 
     cipher = AES.new(self.key, AES.MODE_CBC, self.iv) 
     return b64encode(cipher.encrypt(raw)) 

    def decrypt(self, enc): 
     enc = b64decode(enc) 
     cipher = AES.new(self.key, AES.MODE_CBC, self.iv) 
     r = cipher.decrypt(enc) # type: bytes 
     return self.__unpad(r.decode("utf-8", errors='strict')) 

它在解码问题的最后一行失败。 “忽略”解码模式返回空字符串。

# with magic: "THISISANENCRYPTEDMESSAGE8wZVLZpm7UNyUf26Kds9Gwl2TBsPRo3zYDFQ59405wI=" 
# contains: {'test': 'hello world'} 
payload = '8wZVLZpm7UNyUf26Kds9Gwl2TBsPRo3zYDFQ59405wI=' 

aes = AESCipher('abdsbfuibewuiuizasbfeuiwhfashgfh') 
print(aes.decrypt(payload)) 

举:

Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "../test.py", line 36, in decrypt 
    return self.__unpad(cipher.decrypt(enc).decode("utf-8")) 
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x9e in position 0: invalid start byte 

我在想什么?

+1

为什么你使用'text [-1]'(一个'x02'字节,所以你忽略了最后2个字节)来确定加密数据字符串的长度? –

+0

使用密钥作为IV是一个非常聪明的想法。像真的不聪明。使用完全由ASCII字母和数字组成的密钥可显着减少可能的密钥空间。 –

回答

2

您正在使用Cipher Block Chaining,但未通过IV到openssl_encrypt();这意味着IV是NUL字节的16倍。但是,您的Python代码使用密钥作为IV,所以会产生完全不同的解密结果。

接下来,您选择了AES-192-CBC而不是AES-256-CBC,因此只有192位用于密钥。 192位== 24字节,并且不是 32正如你所想的那样。

您还需要完全删除__unpad()调用,加密数据中没有填充,在解密之前从最后删除数据只会导致解密失败。

所以解密的Python端,使用24个字符为重点,给予静脉输液是16倍\x00,并在传给你从Base64编码解码所有数据:

>>> from Crypto.Cipher import AES 
>>> from base64 import b64decode 
>>> key = 'abdsbfuibewuiuizasbfeuiwhfashgfh'[:24] 
>>> key 
'abdsbfuibewuiuizasbfeuiw' 
>>> payload = '8wZVLZpm7UNyUf26Kds9Gwl2TBsPRo3zYDFQ59405wI=' 
>>> enc = b64decode(payload) 
>>> cipher = AES.new(key, AES.MODE_CBC, '\x00' * 16) 
>>> cipher.decrypt(enc) 
b'{"test":"hello world"}\n\n\n\n\n\n\n\n\n\n' 

如果你想要使用密钥的完整32个字符,请改为使用AES-256-CBC。

你真的想产生一个随机的IV,这样有人在流量上探听就无法确定模式(每次有相同的有效负载产生相同的加密消息)。生成IV,将其包含在您发送的数据中,然后在Python端解压缩以传递到AES.new()函数。

+0

非常好,非常明确,信息丰富。 – Johny99