2012-09-21 540 views
112

Estoy intentando construir dos funciones usando PyCrypto que acepten dos parámetros: el mensaje y la clave, y luego encriptar/desencriptar el mensaje.Encriptar y descifrar usando PyCrypto AES 256

he encontrado varios enlaces en la web para que me ayude, pero cada uno de ellos tiene defectos:

This one at codekoala utiliza os.urandom, que está desalentado por PyCrypto.

Además, la clave que le doy a la función no garantiza que tenga la longitud exacta esperada. ¿Qué puedo hacer para que eso suceda?

Además, hay varios modos, ¿cuál es el recomendado? No sé qué usar:/

Finalmente, ¿qué es exactamente el IV? ¿Puedo proporcionar un IV diferente para encriptar y descifrar, o esto arrojará un resultado diferente?

Aquí es lo que he hecho hasta ahora:

from Crypto import Random 
from Crypto.Cipher import AES 
import base64 

BLOCK_SIZE=32 

def encrypt(message, passphrase): 
    # passphrase MUST be 16, 24 or 32 bytes long, how can I do that ? 
    IV = Random.new().read(BLOCK_SIZE) 
    aes = AES.new(passphrase, AES.MODE_CFB, IV) 
    return base64.b64encode(aes.encrypt(message)) 

def decrypt(encrypted, passphrase): 
    IV = Random.new().read(BLOCK_SIZE) 
    aes = AES.new(passphrase, AES.MODE_CFB, IV) 
    return aes.decrypt(base64.b64decode(encrypted)) 
+10

[os.urandom] (http://docs.python.org/3/library/os.html) está _ incentivado_ en [PyCrypto] (https://www.dlitz.net/software/pycrypto/) sitio web. Utiliza [CryptGenRandom] (http://msdn.microsoft.com/en-us/library/windows/desktop/aa379942 (v = vs.85) .aspx) de Microsoft es una función que [CSPRNG] (http: // en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator) –

+4

o '/ dev/urandom' en Unix –

+1

Solo para aclarar, en este ejemplo ** ** frase de contraseña es la _key_ que puede ser de 128, 192, o 256 bits (16, 24 , o 32 bytes) – Mark

Respuesta

84

Aquí es mi aplicación y funciona para mí con algunas correcciones y mejora la alineación de la frase clave y el secreto con 32 bytes y IV de 16 bytes:

import base64 
import hashlib 
from Crypto import Random 
from Crypto.Cipher import AES 

class AESCipher(object): 

    def __init__(self, key): 
     self.bs = 32 
     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:])] 
+8

Sé que esto ha estado por un tiempo, pero creo que esta respuesta puede generar cierta confusión. Esta función usa un block_size de 32 bytes (256 bytes) para rellenar los datos de entrada, pero AES usa un tamaño de bloque de 128 bits. En AES256, la * clave * es de 256 bits, pero no el tamaño del bloque. – Tannin

+4

para decirlo de otra manera, "self.bs" deben ser retirados y reemplazados por "AES.block_size" – Alexis

+0

¿Por qué la clave de hash? Si espera que esto sea algo así como una contraseña, entonces no debería usar SHA256; es mejor utilizar una función de derivación de clave, como PBKDF2, que proporciona PyCrypto. – tweaksp

6

se puede obtener una frase de contraseña de una contraseña arbitraria mediante el uso de una función hash criptográfica (NO de Python incorporada hash) como SHA-1 o SHA -256. Python incluye soporte tanto en su biblioteca estándar:

import hashlib 

hashlib.sha1("this is my awesome password").digest() # => a 20 byte string 
hashlib.sha256("another awesome password").digest() # => a 32 byte string 

Puede truncar un valor hash criptográfica sólo mediante el uso [:16] o [:24] y conservará su seguridad hasta la longitud que se especifique.

+10

No debe utilizar una función hash SHA-familia para generar una clave de una contraseña - véase [ensayo de Coda Hale sobre el tema] (http://codahale.com/how-to-safely-store-a-password/) . Considere el uso de una función de derivación de clave real (https://en.wikipedia.org/wiki/Key_derivation_function) como [scrypt] (https://pypi.python.org/pypi/scrypt/) en su lugar. (El ensayo de Coda Hale fue escrito antes de la publicación de scrypt.) –

+5

Para futuros lectores, si busca derivar una clave de una frase de contraseña, busque PBKDF2. Es bastante fácil de usar en python (https://pypi.python.org/pypi/pbkdf2). Sin embargo, si buscas contraseñas hash, bcrypt es una mejor opción. –

130

Es posible que necesite las dos funciones siguientes: pad (cuando se realiza el cifrado) y no pad (cuando se descifra) cuando la longitud de la entrada no es un múltiplo de BLOCK_SIZE.

BS = 16 
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) 
unpad = lambda s : s[:-ord(s[len(s)-1:])] 

¿Estás preguntando la longitud de la llave? Puede usar el md5sum de la clave en lugar de usarlo directamente.

más, de acuerdo a mi poca experiencia en el uso de PyCrypto, la IV se utiliza para mezclar la salida de un cifrado cuando la entrada es la misma, por lo que la IV se elige como una cadena aleatoria, y utilizarlo como parte de la encriptación salida, y luego usarlo para descifrar el mensaje.

Y aquí está mi aplicación, espero que sea útil para Usted:

import base64 
from Crypto.Cipher import AES 
from Crypto import Random 

class AESCipher: 
    def __init__(self, key): 
     self.key = key 

    def encrypt(self, raw): 
     raw = 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[:16] 
     cipher = AES.new(self.key, AES.MODE_CBC, iv) 
     return unpad(cipher.decrypt(enc[16:])) 
+1

¿Qué sucede si tiene una entrada que es exactamente un múltiplo de BLOCK_SIZE? Creo que la función de desbloqueo se confundiría un poco ... – Kjir

+1

@Kjir, luego se agregará una secuencia de valor chr (BS) en longitud BLOCK_SIZE a los datos de origen. – Marcus

+0

tienes razón, las funciones 'pad' y' unpad' ahora son más claras para mí, ¡gracias! – Kjir

5

Para el beneficio de los demás, aquí está mi aplicación de descifrado, que llegué a mediante la combinación de las respuestas de @Cyril y @Marcus. Esto supone que esto viene a través de HTTP Request con encryptedText citado y base64 codificado.

import base64 
import urllib2 
from Crypto.Cipher import AES 


def decrypt(quotedEncodedEncrypted): 
    key = 'SecretKey' 

    encodedEncrypted = urllib2.unquote(quotedEncodedEncrypted) 

    cipher = AES.new(key) 
    decrypted = cipher.decrypt(base64.b64decode(encodedEncrypted))[:16] 

    for i in range(1, len(base64.b64decode(encodedEncrypted))/16): 
     cipher = AES.new(key, AES.MODE_CBC, base64.b64decode(encodedEncrypted)[(i-1)*16:i*16]) 
     decrypted += cipher.decrypt(base64.b64decode(encodedEncrypted)[i*16:])[:16] 

    return decrypted.strip() 
6

para alguien que le gustaría urlsafe_b64encode uso y urlsafe_b64decode, aquí están los that're versión de trabajo para mí (después de pasar algún tiempo con el tema unicode)

BS = 16 
key = hashlib.md5(settings.SECRET_KEY).hexdigest()[:BS] 
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) 
unpad = lambda s : s[:-ord(s[len(s)-1:])] 

class AESCipher: 
    def __init__(self, key): 
     self.key = key 

    def encrypt(self, raw): 
     raw = pad(raw) 
     iv = Random.new().read(AES.block_size) 
     cipher = AES.new(self.key, AES.MODE_CBC, iv) 
     return base64.urlsafe_b64encode(iv + cipher.encrypt(raw)) 

    def decrypt(self, enc): 
     enc = base64.urlsafe_b64decode(enc.encode('utf-8')) 
     iv = enc[:BS] 
     cipher = AES.new(self.key, AES.MODE_CBC, iv) 
     return unpad(cipher.decrypt(enc[BS:])) 
+0

Este código se romperá si 'BS = 32' –

0
from Crypto import Random 
from Crypto.Cipher import AES 
import base64 

BLOCK_SIZE=16 
def trans(key): 
    return md5.new(key).digest() 

def encrypt(message, passphrase): 
    passphrase = trans(passphrase) 
    IV = Random.new().read(BLOCK_SIZE) 
    aes = AES.new(passphrase, AES.MODE_CFB, IV) 
    return base64.b64encode(IV + aes.encrypt(message)) 

def decrypt(encrypted, passphrase): 
    passphrase = trans(passphrase) 
    encrypted = base64.b64decode(encrypted) 
    IV = encrypted[:BLOCK_SIZE] 
    aes = AES.new(passphrase, AES.MODE_CFB, IV) 
    return aes.decrypt(encrypted[BLOCK_SIZE:]) 
+6

Proporcione no solo el código sino también explique qué está haciendo y por qué esto es mejor/qué es la diferencia a las respuestas existentes. –

2

Es un poco tarde pero creo que esto será muy útil. Nadie menciona sobre el esquema de uso como el relleno PKCS # 7. Puede usarlo en lugar de las funciones previas para rellenar (cuando se realiza el cifrado) y no usar el teclado (cuando se descifra). Proporcionará el código fuente completo a continuación.

import base64 
import hashlib 
from Crypto import Random 
from Crypto.Cipher import AES 
import pkcs7 
class Encryption: 

    def __init__(self): 
     pass 

    def Encrypt(self, PlainText, SecurePassword): 
     pw_encode = SecurePassword.encode('utf-8') 
     text_encode = PlainText.encode('utf-8') 

     key = hashlib.sha256(pw_encode).digest() 
     iv = Random.new().read(AES.block_size) 

     cipher = AES.new(key, AES.MODE_CBC, iv) 
     pad_text = pkcs7.encode(text_encode) 
     msg = iv + cipher.encrypt(pad_text) 

     EncodeMsg = base64.b64encode(msg) 
     return EncodeMsg 

    def Decrypt(self, Encrypted, SecurePassword): 
     decodbase64 = base64.b64decode(Encrypted.decode("utf-8")) 
     pw_encode = SecurePassword.decode('utf-8') 

     iv = decodbase64[:AES.block_size] 
     key = hashlib.sha256(pw_encode).digest() 

     cipher = AES.new(key, AES.MODE_CBC, iv) 
     msg = cipher.decrypt(decodbase64[AES.block_size:]) 
     pad_text = pkcs7.decode(msg) 

     decryptedString = pad_text.decode('utf-8') 
     return decryptedString 

import StringIO 
import binascii 


def decode(text, k=16): 
    nl = len(text) 
    val = int(binascii.hexlify(text[-1]), 16) 
    if val > k: 
     raise ValueError('Input is not padded or padding is corrupt') 

    l = nl - val 
    return text[:l] 


def encode(text, k=16): 
    l = len(text) 
    output = StringIO.StringIO() 
    val = k - (l % k) 
    for _ in xrange(val): 
     output.write('%02x' % val) 
    return text + binascii.unhexlify(output.getvalue()) 

+0

No sé quién rechazó la respuesta, pero me gustaría saber por qué. Tal vez este método no es seguro? Una explicación sería genial. –

+0

al menos yo no soy el que lo votó negativamente :) – xXxpRoGrAmmErxXx

+1

@CyrilN. Esta respuesta sugiere que la contraseña de hashing con una sola invocación de SHA-256 es suficiente. No lo es Deberías usar PBKDF2 o similar para la derivación de clave a partir de una contraseña usando un gran recuento de iteraciones. –

2

otra toma en este (en gran medida derivada de soluciones arriba) pero

  • utiliza nulo para relleno
  • no utiliza lambda (nunca sido un fan)

    #!/usr/bin/env python 
    
    import base64, re 
    from Crypto.Cipher import AES 
    from Crypto import Random 
    from django.conf import settings 
    
    class AESCipher: 
        """ 
         Usage: 
         aes = AESCipher(settings.SECRET_KEY[:16], 32) 
         encryp_msg = aes.encrypt('ppppppppppppppppppppppppppppppppppppppppppppppppppppppp') 
         msg = aes.decrypt(encryp_msg) 
         print("'{}'".format(msg)) 
        """ 
        def __init__(self, key, blk_sz): 
         self.key = key 
         self.blk_sz = blk_sz 
    
        def encrypt(self, raw): 
         if raw is None or len(raw) == 0: 
          raise NameError("No value given to encrypt") 
         raw = raw + '\0' * (self.blk_sz - len(raw) % self.blk_sz) 
         iv = Random.new().read(AES.block_size) 
         cipher = AES.new(self.key, AES.MODE_CBC, iv) 
         return base64.b64encode(iv + cipher.encrypt(raw)).decode('utf-8') 
    
        def decrypt(self, enc): 
         if enc is None or len(enc) == 0: 
          raise NameError("No value given to decrypt") 
         enc = base64.b64decode(enc) 
         iv = enc[:16] 
         cipher = AES.new(self.key, AES.MODE_CBC, iv) 
         return re.sub(b'\x00*$', b'', cipher.decrypt(enc[16:])).decode('utf-8') 
    
+0

Esto no funcionará si el byte de entrada [] tiene nulos finales porque en la función de descifrado() comerá los nulos de relleno MÁS los nulos finales. –

+0

Sí, como lo mencioné anteriormente, esta lógica rellena con nulos. Si los elementos que desea codificar/decodificar pueden tener nulos finales, mejor utilice una de las otras soluciones aquí – MIkee

5

Permítanme abordar pregunta sobre "modos". AES256 es un tipo de cifra de bloque. Toma como entrada un de 32 bytes clave y una cadena de 16 bytes, llamada bloque y emite un bloque. Usamos AES en un modo de operación para encriptar. Las soluciones anteriores sugieren el uso de CBC, que es un ejemplo. Otro se llama CTR, y es un poco más fácil de usar:

from Crypto.Cipher import AES 
from Crypto.Util import Counter 
from Crypto import Random 

# AES supports multiple key sizes: 16 (AES128), 24 (AES192), or 32 (AES256). 
key_bytes = 32 

# Takes as input a 32-byte key and an arbitrary-length plaintext and returns a 
# pair (iv, ciphtertext). "iv" stands for initialization vector. 
def encrypt(key, plaintext): 
    assert len(key) == key_bytes 

    # Choose a random, 16-byte IV. 
    iv = Random.new().read(AES.block_size) 

    # Convert the IV to a Python integer. 
    iv_int = int(binascii.hexlify(iv), 16) 

    # Create a new Counter object with IV = iv_int. 
    ctr = Counter.new(AES.block_size * 8, initial_value=iv_int) 

    # Create AES-CTR cipher. 
    aes = AES.new(key, AES.MODE_CTR, counter=ctr) 

    # Encrypt and return IV and ciphertext. 
    ciphertext = aes.encrypt(plaintext) 
    return (iv, ciphertext) 

# Takes as input a 32-byte key, a 16-byte IV, and a ciphertext, and outputs the 
# corresponding plaintext. 
def decrypt(key, iv, ciphertext): 
    assert len(key) == key_bytes 

    # Initialize counter for decryption. iv should be the same as the output of 
    # encrypt(). 
    iv_int = int(iv.encode('hex'), 16) 
    ctr = Counter.new(AES.block_size * 8, initial_value=iv_int) 

    # Create AES-CTR cipher. 
    aes = AES.new(key, AES.MODE_CTR, counter=ctr) 

    # Decrypt and return the plaintext. 
    plaintext = aes.decrypt(ciphertext) 
    return plaintext 

(iv, ciphertext) = encrypt(key, 'hella') 
print decrypt(key, iv, ciphertext) 

Esto se refiere a menudo como AES-CTR. Aconsejo precaución al usar AES-CBC con PyCrypto. La razón es que requiere que especifique el esquema de relleno , como se ejemplifica con las otras soluciones proporcionadas. En general, si no está muy teniendo cuidado con el relleno, hay attacks que rompen completamente el cifrado.

Ahora, es importante tener en cuenta que la clave debe ser un al azar, de 32 bytes cadena; una contraseña no es suficiente.Normalmente, la clave se genera de este modo:

# Nominal way to generate a fresh key. This calls the system's random number 
# generator (RNG). 
key1 = Random.new().read(key_bytes) 

Una clave puede ser deriva de una contraseña, también:

# It's also possible to derive a key from a password, but it's important that 
# the password have high entropy, meaning difficult to predict. 
password = "This is a rather weak password." 

# For added # security, we add a "salt", which increases the entropy. 
# 
# In this example, we use the same RNG to produce the salt that we used to 
# produce key1. 
salt_bytes = 8 
salt = Random.new().read(salt_bytes) 

# Stands for "Password-based key derivation function 2" 
key2 = PBKDF2(password, salt, key_bytes) 

Algunas soluciones anteriores sugieren utilizando SHA256 para derivar la clave, pero esto es generalmente considerado bad cryptographic practice. Consulte wikipedia para obtener más información sobre los modos de operación.

Cuestiones relacionadas