Ответ 1
Основная проблема с вашим кодом заключается в том, что вы используете строки. AES работает с двоичными данными, и если бы вы использовали PyCryptodome, этот код повышал бы TypeError:
Object type <class 'str'> cannot be passed to C code
Pycrypto принимает строки, но кодирует их в байты внутри, поэтому не имеет смысла декодировать ваши байты в строку, потому что они будут закодированы обратно в байты. Кроме того, он кодирует ASCII (тестируется с PyCrypto v2.6.1, Python v2.7), и поэтому этот код, например:
encryptor.encrypt(u'ψ' * 16)
поднимет UnicodeEncodeError:
File "C:\Python27\lib\site-packages\Crypto\Cipher\blockalgo.py", line 244, in encrypt
return self._cipher.encrypt(plaintext)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-15
Вы должны всегда использовать байты при шифровании или расшифровке данных. Затем вы можете декодировать текст в строку, если это текст.
Следующий вопрос - ваш метод заполнения. Он создает строку, поэтому вы получаете TypeError при попытке применить его к открытому тексту, который должен быть байтами. Вы можете исправить это, если вы набиваете байты,
chunk += b' ' * (16 - len(chunk) % 16)
но было бы лучше использовать дополнение PKCS7 (в настоящее время вы используете нулевое дополнение, но вместо пробела вместо нулевого байта).
PyCryptodome предоставляет функции заполнения, но, похоже, вы используете PyCrypto. В этом случае вы можете реализовать дополнение PKCS7 или, еще лучше, скопировать функции заполнения PyCryptodome.
try:
from Crypto.Util.Padding import pad, unpad
except ImportError:
from Crypto.Util.py3compat import bchr, bord
def pad(data_to_pad, block_size):
padding_len = block_size-len(data_to_pad)%block_size
padding = bchr(padding_len)*padding_len
return data_to_pad + padding
def unpad(padded_data, block_size):
pdata_len = len(padded_data)
if pdata_len % block_size:
raise ValueError("Input data is not padded")
padding_len = bord(padded_data[-1])
if padding_len<1 or padding_len>min(block_size, pdata_len):
raise ValueError("Padding is incorrect.")
if padded_data[-padding_len:]!=bchr(padding_len)*padding_len:
raise ValueError("PKCS#7 padding is incorrect.")
return padded_data[:-padding_len]
Функции pad
и unpad
были скопированы из Crypto.Util.Padding
и изменены, чтобы использовать только дополнение PKCS7. Обратите внимание, что при использовании дополнения PKCS7 важно проложить последний кусок, даже если его размер кратен размеру блока, иначе вы не сможете правильно отладить.
Применяя эти изменения к функции encrypt_file
,
def encrypt_file(key, in_filename, out_filename=None, chunksize=64*1024):
if not out_filename:
out_filename = in_filename + '.enc'
iv = os.urandom(16)
encryptor = AES.new(key, AES.MODE_CBC, iv)
filesize = os.path.getsize(in_filename)
with open(in_filename, 'rb') as infile:
with open(out_filename, 'wb') as outfile:
outfile.write(struct.pack('<Q', filesize))
outfile.write(iv)
pos = 0
while pos < filesize:
chunk = infile.read(chunksize)
pos += len(chunk)
if pos == filesize:
chunk = pad(chunk, AES.block_size)
outfile.write(encryptor.encrypt(chunk))
и соответствующую функцию decrypt_file
,
def decrypt_file(key, in_filename, out_filename=None, chunksize=64*1024):
if not out_filename:
out_filename = in_filename + '.dec'
with open(in_filename, 'rb') as infile:
filesize = struct.unpack('<Q', infile.read(8))[0]
iv = infile.read(16)
encryptor = AES.new(key, AES.MODE_CBC, iv)
with open(out_filename, 'wb') as outfile:
encrypted_filesize = os.path.getsize(in_filename)
pos = 8 + 16 # the filesize and IV.
while pos < encrypted_filesize:
chunk = infile.read(chunksize)
pos += len(chunk)
chunk = encryptor.decrypt(chunk)
if pos == encrypted_filesize:
chunk = unpad(chunk, AES.block_size)
outfile.write(chunk)
Этот код совместим с Python2/Python3, и он должен работать либо с PyCryptodome, либо с PyCrypto.
Однако, если вы используете PyCrypto, я рекомендую обновить PyCryptodome. PyCryptodome - это вилка PyCrypto, и она предоставляет один и тот же API (поэтому вам не придется слишком сильно менять свой код), а также некоторые дополнительные функции: дополнения, алгоритмы аутентифицированного шифрования, KDF и т.д. С другой стороны, PyCrypto не а также некоторые версии страдают от переполнения буфера на основе кучи: CVE-2013-7459.