UnicodeDecodeError: el codec ‘utf-8’ no puede decodificar bytes en la posición 65534-65535: final de datos inesperado

Quiero cifrar el archivo con el cifrado AES simple, aquí está mi código fuente de python3.

import os, random, struct from Crypto.Cipher import AES 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) while True: chunk = infile.read(chunksize) if len(chunk) == 0: break elif len(chunk) % 16 != 0: chunk += ' ' * (16 - len(chunk) % 16) outfile.write(encryptor.encrypt(chunk.decode('UTF-8','strict'))) 

Funciona bien para algunos archivos, encuentra información de error para algunos archivos como los siguientes:

encrypt_file (“qwertyqwertyqwer”, ‘/ tmp / test1’, out_filename = None, chunksize = 64 * 1024)

No hay información de error, funciona bien.

encrypt_file (“qwertyqwertyqwer”, ‘/ tmp / test2’, out_filename = None, chunksize = 64 * 1024)

 Traceback (most recent call last): File "", line 1, in  File "", line 17, in encrypt_file UnicodeDecodeError: 'utf-8' codec can't decode bytes in position 65534-65535: unexpected end of data 

¿Cómo arreglar mi función encrypt_file?

Haz como tmadam dice, para arreglar

 outfile.write(encryptor.encrypt(chunk.decode('UTF-8','strict'))) 

como

 outfile.write(encryptor.encrypt(chunk)) 

Para probar con algún archivo.

 encrypt_file("qwertyqwertyqwer",'/tmp/test' , out_filename=None, chunksize=64*1024) Traceback (most recent call last): File "", line 1, in  File "", line 16, in encrypt_file TypeError: can't concat bytes to str 

El principal problema con tu código es que estás usando cadenas. AES trabaja con datos binarios, y si estuviera usando PyCryptodome, este código generaría un TypeError:

 Object type  cannot be passed to C code 

Pycrypto acepta cadenas, pero las codifica en bytes internamente, por lo que no tiene sentido decodificar sus bytes en cadena porque se codificará de nuevo en bytes. Además, se codifica con ASCII (probado con PyCrypto v2.6.1, Python v2.7) y, por lo tanto, este código, por ejemplo:

 encryptor.encrypt(u'ψ' * 16) 

elevaría un 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 

Siempre debe utilizar bytes al cifrar o descifrar datos. Luego puedes decodificar el texto plano a cadena, si es texto.

El siguiente problema es su método de relleno. Produce una cadena y, por lo tanto, obtienes un TypeError cuando intentas aplicarlo al texto plano, que debería ser bytes. Puedes arreglar esto si rellenas con bytes,

 chunk += b' ' * (16 - len(chunk) % 16) 

pero sería mejor utilizar el relleno PKCS7 (actualmente está utilizando el relleno cero, pero con un espacio en lugar de un byte cero).

PyCryptodome proporciona funciones de relleno, pero parece que estás usando PyCrypto. En este caso, podría implementar el relleno PKCS7, o mejor aún, copiar las funciones de relleno de 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] 

Las funciones de pad y unpad se copiaron de Crypto.Util.Padding y se modificaron para usar solo el relleno PKCS7. Tenga en cuenta que al usar el relleno PKCS7 es importante rellenar el último fragmento, incluso si su tamaño es un múltiplo del tamaño del bloque, de lo contrario no podrá desatascar correctamente.

Aplicando esos cambios a la función 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(' 

y la función decrypt_file coincidente,

 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(' 

Este código es compatible con Python2 / Python3, y debería funcionar con PyCryptodome o con PyCrypto.

Sin embargo, si está utilizando PyCrypto, recomiendo actualizar a PyCryptodome. PyCryptodome es una bifurcación de PyCrypto y expone la misma API (por lo que no tendrá que cambiar su código demasiado), además de algunas características adicionales: funciones de relleno, algoritmos de cifrado autenticado, KDF, etc. Por otra parte, PyCrypto no es se mantiene más y también, algunas versiones sufren una vulnerabilidad de desbordamiento de búfer basada en el montón: CVE-2013-7459 .

Además de la respuesta aceptada, creo que mostrar múltiples implementaciones de cifrado AES simple puede ser útil para los lectores / nuevos alumnos:

 import os import sys import pickle import base64 import hashlib import errno from Crypto import Random from Crypto.Cipher import AES DEFAULT_STORAGE_DIR = os.path.join(os.path.dirname(__file__), '.ncrypt') def create_dir(dir_name): """ Safely create a new directory. """ try: os.makedirs(dir_name) return dir_name except OSError as e: if e.errno != errno.EEXIST: raise OSError('Unable to create directory.') class AESCipher(object): DEFAULT_CIPHER_PICKLE_FNAME = "cipher.pkl" def __init__(self, key): self.bs = 32 # block size self.key = hashlib.sha256(key.encode()).digest() def encrypt(self, raw): raw = self._pad(raw) iv = Random.new().read(AES. block_size) cipher = AES.new(self.key, AES.MODE_CBC, iv) return base64.b64encode(iv + cipher.encrypt(raw)) def decrypt(self, enc): enc = base64.b64decode(enc) iv = enc[:AES.block_size] cipher = AES.new(self.key, AES.MODE_CBC, iv) return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8') def _pad(self, s): return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs) @staticmethod def _unpad(s): return s[:-ord(s[len(s)-1:])] 

E ilustrando ejemplos del uso anterior:

 while True: option = input('\n'.join(["="*80, "| Select an operation:", "| 1) E : Encrypt", "| 2) D : Decrypt", "| 3) H : Help", "| 4) G : Generate new cipher", "| 5) Q : Quit", "="*80, "> "])).lower() print() if option == 'e' or option == 1: plaintext = input('Enter plaintext to encrypt: ') print("Encrypted: {}".format(cipher.encrypt(plaintext).decode("utf-8"))) elif option == 'd' or option == 2: ciphertext = input('Enter ciphertext to decrypt: ') print("Decrypted: {}".format(cipher.decrypt(ciphertext.encode("utf-8")))) elif option == 'h' or option == 3: print("Help:\n\tE: Encrypt plaintext\n\tD: Decrypt ciphertext.") elif option == 'g' or option == 4: if input("Are you sure? [yes/no]: ").lower() in ["yes", "y"]: cipher = AESCipher(key=input('Enter cipher password: ')) with open(pickle_fname, 'wb') as f: pickle.dump(cipher, f) print("Generated new cipher.") elif option == 'q' or option == 5: raise EOFError else: print("Unknown operation.")