Cifrar y descifrar usando PyCrypto AES 256


Estoy tratando de construir dos funciones usando PyCrypto que acepten dos parámetros: el mensaje y la clave, y luego cifrar/descifrar el mensaje.

Encontré varios enlaces en la web para ayudarme, pero cada uno de ellos tiene defectos:

Este en codekoala usa os.urandom, que es desalentado por PyCrypto.

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

También, hay ¿hay varios modos, cuál se recomienda? No se que usar: /

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

Esto 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))
Author: Artjom B., 2012-09-21

9 answers

Aquí está mi implementación y funciona para mí con algunas correcciones y mejora la alineación de la clave y la frase secreta con 32 bytes y iv a 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:])]
 100
Author: mnothic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-11 15:44:05

Es posible que necesite las siguientes dos funciones para pad(cuando hacer cifrado) y unpad(cuando hacer descifrado) cuando la longitud de 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:])]

Entonces, ¿estás preguntando la longitud de la clave? Puede usar el md5sum de la clave en lugar de usarlo directamente.

Más, de acuerdo con mi poca experiencia de usar PyCrypto, el IV se usa para mezclar la salida de un cifrado cuando la entrada es la misma, por lo que el IV se elige como una cadena aleatoria, y lo usa como parte del cifrado salida, y luego usarlo para descifrar el mensaje.

Y aquí está mi implementació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:] ))
 136
Author: Marcus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-23 19:28:57

Puede obtener una frase de contraseña de una contraseña arbitraria usando una función hash criptográfica ( NO incorporada en Python hash) como SHA-1 o SHA-256. Python incluye soporte para ambos 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áfico simplemente usando [:16] o [:24] y conservará su seguridad hasta la longitud que especifique.

 6
Author: nneonneo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-21 06:08:30

Para alguien a quien le gustaría usar urlsafe_b64encode y urlsafe_b64decode, aquí están las versiones que están funcionando para mí (después de pasar algún tiempo con el problema de 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:]))
 6
Author: Hoang HUA,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-31 12:18:51

Para el beneficio de los demás, aquí está mi implementación de descifrado que llegué a la combinación de las respuestas de @Cyril y @Marcus. Esto asume que esto viene vía petición HTTP con el encryptedText entrecomillado 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()
 5
Author: scottmrogowski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-23 23:56:52

Permítanme abordar su pregunta sobre "modos."AES256 es una especie de cifrado por bloques . Toma como entrada una tecla de 32 bytes y una cadena de 16 bytes, llamada el bloque y genera un bloque. Usamos AES en un modo de operación para cifrar. Las soluciones anteriores sugieren el uso de CBC, que es un ejemplo. Otro se llama CTR, y es algo 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 conoce a menudo como AES-CTR. Aconsejaría precaución en el uso de AES-CBC con PyCrypto . La razón es que requiere que especifique el esquema de relleno , como se ejemplifica en las otras soluciones dadas. En general, si no eres muy cuidadoso con el relleno, hay ataques que rompen completamente el cifrado!

Ahora, es importante tener en cuenta que la clave debe ser una cadena aleatoria de 32 bytes; una contraseña no es suficiente. Normalmente, la clave se genera así:

# 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 derivado 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 usar SHA256 para derivar la clave, pero esto generalmente se considera mala práctica criptográfica. Echa un vistazo a wikipedia para obtener más información sobre los modos de operación.

 5
Author: tweaksp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-20 20:54:14

Es un poco tarde, pero creo que esto será muy útil. Nadie menciona sobre el esquema de uso como el relleno PKCS # 7. Se puede utilizar en su lugar las funciones anteriores para pad (cuando hacer cifrado) y unpad(cuando hacer descifrado).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())

 2
Author: xXxpRoGrAmmErxXx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-19 11:49:23

Otra toma de esto (muy derivada de las soluciones anteriores) pero

  • usa null para relleno
  • no usa lambda (nunca he sido fan)
  • Probado con python 2.7 y 3.6.5

    #!/usr/bin/python2.7
    # you'll have to adjust for your setup, e.g., #!/usr/bin/python3
    
    
    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)
            raw = raw.encode('utf-8')
            iv = Random.new().read( AES.block_size )
            cipher = AES.new( self.key.encode('utf-8'), 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.encode('utf-8'), AES.MODE_CBC, iv )
            return re.sub(b'\x00*$', b'', cipher.decrypt( enc[16:])).decode('utf-8')
    
 2
Author: MIkee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-15 22:22:34
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:])
 0
Author: yuen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-11 13:24:12