Python que encripta con PyCrypto AES

Acabo de encontrar pycrypto hoy, y he estado trabajando en mi clase de cifrado AES. Desafortunadamente solo funciona a medias. self.h.md5 genera el hash md5 en formato hexadecimal y es de 32 bytes. Esta es la salida. Parece descifrar el mensaje, pero coloca caracteres aleatorios después del descifrado, en este caso \ n \ n \ n … Creo que tengo un problema con el tamaño de bloque de self.data. ¿Alguien sabe cómo solucionarlo?

Jans-MacBook-Pro: test2 jan $ ../../bin/python3 data.py b’RLfGmn5jf5WTJphnmW0hXG7IaIYcCRpjaTTqwXR6yi JnnlWWJhGGIIIYcCRpjaTTqwXR6y 5Priv.Alf.Ant. \ 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:]) 

Para ser honesto, los caracteres “\ n \ n \ n \ n \ n \ n \ n \ n \ n \ n \ n” no me parecen tan aleatorios. 😉

Está utilizando AES en modo CBC. Eso requiere que la longitud del texto plano y el texto cifrado sean siempre un múltiplo de 16 bytes. Con el código que muestra, realmente debería ver una excepción que se inicia cuando los data pasan a encrypt() no cumplen con esa condición. Parece que ha agregado suficientes caracteres de nueva línea ( ‘\ n’ a cualquiera que sea la entrada hasta que el texto en claro esté alineado.

Aparte de eso, hay dos formas comunes de resolver el problema de alineación:

  1. Cambie de CBC ( AES.MODE_CBC ) a CFB ( AES.MODE_CFB ). Con el segment_size predeterminado utilizado por PyCrypto, no tendrá ninguna restricción en el texto plano y en la longitud del texto cifrado.

  2. Mantenga CBC y use un esquema de relleno como PKCS # 7, que es:

    • antes de cifrar un texto sin formato de X bytes, agregue en la parte posterior tantos bytes como sea necesario para alcanzar el siguiente límite de 16 bytes. Todos los bytes de relleno tienen el mismo valor: el número de bytes que está agregando:

       length = 16 - (len(data) % 16) data += bytes([length])*length 

      Eso es estilo Python 3. En Python 2, tendrías:

       length = 16 - (len(data) % 16) data += chr(length)*length 
    • después de descifrar, elimine de la parte posterior del texto sin formato tantos bytes como lo indique el relleno:

       data = data[:-data[-1]] 

Aunque entiendo que en su caso es solo un ejercicio de clase, me gustaría señalar que es inseguro enviar datos sin ningún tipo de autenticación (por ejemplo, un MAC).

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

Puede utilizar un carácter de corrección siempre que recuerde la longitud de su carga útil inicial, de modo que no “tire” los bytes finales útiles. Prueba esto:

 import base64 from Crypto.Cipher import AES def encrypt(payload, salt, key): return AES.new(key, AES.MODE_CBC, salt).encrypt(r_pad(payload)) def decrypt(payload, salt, key, length): return AES.new(key, AES.MODE_CBC, salt).decrypt(payload)[:length] def r_pad(payload, block_size=16): length = block_size - (len(payload) % block_size) return payload + chr(length) * length print(decrypt(encrypt("some cyphertext", "b" * 16, "b" * 16), "b" * 16, "b" * 16, len("some cyphertext"))) 

AES.new().encrypt() y .decrypt() toman como entrada y como salida cadenas cuya longitud es un múltiplo de 16. .decrypt() corregirlo de una forma u otra. Por ejemplo, puede almacenar la longitud real al inicio y usarla para truncar la cadena descifrada.

Tenga en cuenta también que si bien es la única restricción para AES, otros módulos (especialmente en Crypto.PublicKey ) tienen restricciones adicionales que provienen de su implementación matemática y que no deberían (en mi opinión) ser visibles para el usuario final, pero sí lo son. Por ejemplo, Crypto.PublicKey.ElGamal cifrará cualquier cadena corta, pero si comienza con caracteres nulos, se pierden al descifrarlos.