У меня есть приложение Python и веб-сайт PHP, которые общаются через определенный сетевой уровень, отправляя сообщения. Моя задача — отправлять все сообщения, зашифрованные AES и base64, используя этот канал. Ключ шифрования предварительно передается обеим сторонам вручную.
В моем 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?
Вот решение Python от SO, которое не работает для моих зашифрованных сообщений.
from base64 import b64encode, b64decode
from Crypto.Cipher import AESclass 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
Что мне не хватает?
Ты используешь Цепочка блоков шифров, но не прошел в IV openssl_encrypt()
; это означает, что IV в 16 раз больше NUL байта. Но ваш код Python использует ключ как IV вместо этого, так что это приведет к совершенно другому результату дешифрования.
Далее вы выбрали AES-192-CBC
не AES-256-CBC
Таким образом, для ключа используются только 192 бита. 192 бита == 24 байта и не 32, как вы думали.
Вы также должны бросить __unpad()
Если говорить полностью, в ваших зашифрованных данных нет заполнения, удаление данных с конца до дешифрования приведет только к сбою дешифрования.
Таким образом, чтобы расшифровать на стороне Python, используйте 24 ключа для ключа, дайте IV, который в 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()
функция.
Других решений пока нет …