Шифрование Python с помощью PyCrypto AES
Я просто нашел pycrypto сегодня, и я работал над своим классом шифрования AES. К сожалению, он работает только наполовину. self.h.md5 выводит хеш md5 в шестнадцатеричном формате и составляет 32 байт.
Это результат. Кажется, что оно расшифровывает сообщение, но после дешифрования оно помещает случайные символы, в этом случае \n\n\n... Я думаю, что у меня проблема с размером блока self.data, кто-нибудь знает, как это исправить?
Jans-MacBook-Pro: test2 jan $../../bin/python3 data.py b'RLfGmn5jf5WTJphnmW0hXG7IaIYcCRpjaTTqwXR6yiJCUytnDib + GQYlFORm + jIctest 1 2 3 4 5 endtest\n\n\n\n\n\n\n\n\n\n '
from Crypto.Cipher import AES
from base64 import b64encode, b64decode
from os import urandom
class Encryption():
def __init__(self):
self.h = Hash()
def values(self, data, key):
self.data = data
self.key = key
self.mode = AES.MODE_CBC
self.iv = urandom(16)
if not self.key:
self.key = Cfg_Encrypt_Key
self.key = self.h.md5(self.key, True)
def encrypt(self, data, key):
self.values(data, key)
return b64encode(self.iv + AES.new(self.key, self.mode, self.iv).encrypt(self.data))
def decrypt(self, data, key):
self.values(data, key)
self.iv = b64decode(self.data)[:16]
return AES.new(self.key, self.mode, self.iv).decrypt(b64decode(self.data)[16:])
Ответы
Ответ 1
Если честно, символы "\n\n\n\n\n\n\n\n\n\n" не кажутся мне случайными.; -)
Вы используете AES в режиме CBC. Это требует, чтобы длина открытого текста и зашифрованного текста всегда была кратной 16 байтам. С помощью кода, который вы показываете, вы должны увидеть, что возникает исключение, когда data
, переданное в encrypt()
, не выполняет такое условие. Похоже, вы добавили достаточно новых символов строки ('\n', чтобы вход был до тех пор, пока не был выровнен текст.
Кроме того, существует два распространенных способа решения проблемы выравнивания:
-
Переключитесь с CBC (AES.MODE_CBC
) на CFB (AES.MODE_CFB
). При использовании по умолчанию segment_size
, используемого PyCrypto, у вас не будет никаких ограничений на длину открытого текста и зашифрованного текста.
-
Сохраните CBC и используйте схему заполнения, такую как PKCS # 7, а именно:
-
перед тем, как зашифровать открытый текст из X
байтов, добавьте в обратную сторону столько же байтов, сколько необходимо для достижения следующей 16-байтовой границы. Все байты заполнения имеют одинаковое значение: количество добавляемых байтов:
length = 16 - (len(data) % 16)
data += bytes([length])*length
Этот стиль Python 3. В Python 2 у вас будет:
length = 16 - (len(data) % 16)
data += chr(length)*length
-
после дешифрования удалите из задней части открытого текста столько байтов, сколько указано в дополнении:
data = data[:-data[-1]]
Несмотря на то, что в вашем случае я понимаю, что это просто упражнение класса, я хотел бы указать, что отправлять данные без какой-либо проверки подлинности (например, MAC) небезопасно.
Ответ 2
from hashlib import md5
from Crypto.Cipher import AES
from Crypto import Random
import base64
def derive_key_and_iv(password, salt, key_length, iv_length):
d = d_i = ''
while len(d) < key_length + iv_length:
d_i = md5(d_i + password + salt).digest()
d += d_i
return d[:key_length], d[key_length:key_length+iv_length]
def encrypt(in_file, out_file, password, key_length=32):
bs = AES.block_size
salt = Random.new().read(bs - len('Salted__'))
key, iv = derive_key_and_iv(password, salt, key_length, bs)
cipher = AES.new(key, AES.MODE_CBC, iv)
#print in_file
in_file = file(in_file, 'rb')
out_file = file(out_file, 'wb')
out_file.write('Salted__' + salt)
finished = False
while not finished:
chunk = in_file.read(1024 * bs)
if len(chunk) == 0 or len(chunk) % bs != 0:
padding_length = bs - (len(chunk) % bs)
chunk += padding_length * chr(padding_length)
finished = True
out_file.write(cipher.encrypt(chunk))
in_file.close()
out_file.close()
def decrypt(in_file, out_file, password, key_length=32):
bs = AES.block_size
in_file = file(in_file, 'rb')
out_file = file(out_file, 'wb')
salt = in_file.read(bs)[len('Salted__'):]
key, iv = derive_key_and_iv(password, salt, key_length, bs)
cipher = AES.new(key, AES.MODE_CBC, iv)
next_chunk = ''
finished = False
while not finished:
chunk, next_chunk = next_chunk, cipher.decrypt(in_file.read(1024 * bs))
if len(next_chunk) == 0:
padding_length = ord(chunk[-1])
if padding_length < 1 or padding_length > bs:
raise ValueError("bad decrypt pad (%d)" % padding_length)
# all the pad-bytes must be the same
if chunk[-padding_length:] != (padding_length * chr(padding_length)):
# this is similar to the bad decrypt:evp_enc.c from openssl program
raise ValueError("bad decrypt")
chunk = chunk[:-padding_length]
finished = True
out_file.write(chunk)
in_file.close()
out_file.close()
def encode(in_file, out_file):
in_file = file(in_file, 'rb')
out_file = file(out_file, 'wb')
data = in_file.read()
out_file.write(base64.b64encode(data))
in_file.close()
out_file.close()
def decode(in_file, out_file):
in_file = file(in_file, 'rb')
out_file = file(out_file, 'wb')
data = in_file.read()
out_file.write(base64.b64decode(data))
in_file.close()
out_file.close()
Ответ 3
AES.new().encrypt()
и .decrypt()
принимают как строки ввода и вывода, длина которых кратная 16. Вы должны так или иначе исправить ее. Например, вы можете сохранить реальную длину в начале и использовать ее для обрезания дешифрованной строки.
Обратите внимание также, что, хотя это единственное ограничение для AES, другие модули (особенно в Crypto.PublicKey
) имеют дополнительные ограничения, которые исходят из их математической реализации и которые не должны (по-моему) быть видимыми конечному пользователю, но находятся. Например, Crypto.PublicKey.ElGamal
будет шифровать любую короткую строку, но если она начинается с нулевых символов, они теряются при расшифровке.