2016-11-26 252 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

相關問題