cirilo n
Estoy tratando de construir dos funciones usando PyCrypto que aceptan 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 fallas:
Este en codekoala usa os.urandom, que PyCrypto desaconseja.
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?
Además, hay varios modos, ¿cuál es el recomendado? no se que usar :/
Finalmente, ¿cuál es exactamente el IV? ¿Puedo proporcionar un IV diferente para cifrar y descifrar, o esto devolverá un resultado diferente?
mnótico
Aquí está mi implementación, y funciona para mí con algunas correcciones. Mejora la alineación de la clave y 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 = AES.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.encode()))
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:])]
-
¿Por qué hash hash la clave? Si espera que esto sea algo así como una contraseña, entonces no debería usar SHA256; es mejor usar una función de derivación de clave, como PBKDF2, que proporciona PyCrypto.
– ajustes
20 de junio de 2017 a las 18:43
-
@Chris: SHA256 proporciona un hash de 32 bytes, una clave de tamaño perfecto para AES256. Se supone que la generación/derivación de una clave es aleatoria/segura y debe estar fuera del alcance del código de cifrado/descifrado; el hashing es solo una garantía de que la clave se puede utilizar con el cifrado seleccionado.
– zwer
20 jun 2017 a las 20:35
-
en _pad se necesita acceso self.bs y en _unpad no se necesita
– mnótico
5 de julio de 2017 a las 13:56
-
@mnothic – pregunta tonta: por qué
s[:-ord(s[len(s)-1:])]
en lugar de solos[:-ord(s[-1])]
?– ryugie
30/10/2017 a las 19:15
-
Tuve que modificarlo para poder cifrar cadenas que no contienen caracteres ASCII como áàâãéèêíïóôõöúçñÁÀÂÃÉÈÍÏÓÔÕÖÚÇÑ. Para hacerlo, tuve que reemplazar len(s) en _pad() para len(s.codificar()) para obtener el relleno correcto. De lo contrario, obtendría el siguiente error: “los datos deben rellenarse hasta el límite de 16 bytes en el modo cbc”.
– Samuel OD
6 de mayo de 2021 a las 8:08
marcus
Es posible que necesite las dos funciones siguientes: pad
– para rellenar (al hacer el cifrado) y unpad
– para desbloquear (al realizar el descifrado) 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:])]
Entonces, ¿estás preguntando la longitud de la clave? Puedes usar el MD5 hash de la clave en lugar de usarla directamente.
Más, de acuerdo con mi poca experiencia con el uso de 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 se usa como parte de la salida del cifrado, y luego utilícelo para descifrar el mensaje.
Y aquí está mi implementación:
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:] ))
-
¿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
21 de octubre de 2013 a las 10:43
-
@Kjir, luego se agregará una secuencia de valor chr(BS) de longitud BLOCK_SIZE a los datos de origen.
– Marco
22 de octubre de 2013 a las 4:14
-
@Marcus el
pad
la función está rota (al menos en Py3), reemplace cons[:-ord(s[len(s)-1:])]
para que funcione en todas las versiones.– torcido
24 de febrero de 2014 a las 11:06
-
La función @Torxed pad está disponible en CryptoUtil.Padding.pad() con pycryptodome (seguimiento de pycrypto)
– conde
11 de abril de 2017 a las 15:46
-
¿Por qué no tener una constante de carácter como carácter de relleno?
– Inaimati
25 de abril de 2017 a las 14:48
ajustes
Permítame abordar su pregunta sobre “modos”. AES-256 Es un tipo de cifrado en bloque. Toma como entrada un 32 bytes llave y una cadena de 16 bytes, denominada bloquear y genera un bloque. Usamos AES en un modo de operación para cifrar. Las soluciones anteriores sugieren usar CBC, que es un ejemplo. otro se llama CTRy 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 a menudo se denomina AES-CTR. Aconsejaría precaución al usar AES-CBC con PyCrypto. La razón es que requiere que usted especifique el esquema de relleno, como lo ejemplifican las otras soluciones dadas. En general, si no eres muy cuidado con el relleno, existen ataques ¡que rompen completamente el cifrado!
Ahora, es importante tener en cuenta que la clave debe ser un cadena aleatoria de 32 bytes; una contraseña no es satisfacer. 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ñatambié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 SHA-256 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 funcionamiento.
-
iv_int = int(binascii.hexlify(iv), 16) no funciona, reemplácelo con iv_int = int(binascii.hexlify(iv), 16) más ‘importar binascii’ y debería funcionar (en Python 3.x ), por lo demás ¡buen trabajo!
– Valmond
20 de mayo de 2019 a las 15:04
-
Tenga en cuenta que es mejor utilizar los modos de cifrado autenticado como AES-GCM. GCM utiliza internamente el modo CTR.
– kelalaka
15 de septiembre de 2019 a las 10:02
-
Este código causa “TypeError: el tipo de objeto
no se puede pasar al código C” – Da Woon Jung
21 de noviembre de 2019 a las 2:49
-
@DaWoonJung Observé lo mismo con Python 3.9.7. texto cifrado = aes.encrypt(texto plano.encode(‘utf-8’)) y el texto cifrado necesitaría el decodificar(‘utf-8’) para completar el str, bytes de ida y vuelta.
– Stephan Scheller
1 de diciembre de 2021 a las 7:55
-
Recibí un error cuando traté de descifrar. el mensaje de error: el objeto ‘bytes’ no tiene atributo ‘codificar’. pero cuando verifico el tipo iv se dice
. como arreglar eso? – HelpMeInDjango
30 de noviembre a las 6:47
cenkarioz
Estoy agradecido por las otras respuestas que me inspiraron, pero no funcionó para mí.
Después de pasar horas tratando de averiguar cómo funciona, se me ocurrió la implementación a continuación con la última PyCryptodomex biblioteca (es otra historia cómo logré configurarlo detrás de proxy, en Windows, en un entorno virtual… uf)
Está trabajando en su implementación. Recuerde anotar los pasos de relleno, codificación y cifrado (y viceversa). Hay que hacer y deshacer las maletas teniendo en cuenta el orden.
import base64
import hashlib
from Cryptodome.Cipher import AES
from Cryptodome.Random import get_random_bytes
__key__ = hashlib.sha256(b'16-character key').digest()
def encrypt(raw):
BS = AES.block_size
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
raw = base64.b64encode(pad(raw).encode('utf8'))
iv = get_random_bytes(AES.block_size)
cipher = AES.new(key= __key__, mode= AES.MODE_CFB,iv= iv)
return base64.b64encode(iv + cipher.encrypt(raw))
def decrypt(enc):
unpad = lambda s: s[:-ord(s[-1:])]
enc = base64.b64decode(enc)
iv = enc[:AES.block_size]
cipher = AES.new(__key__, AES.MODE_CFB, iv)
return unpad(base64.b64decode(cipher.decrypt(enc[AES.block_size:])).decode('utf8'))
Para alguien a quien le gustaría usar urlsafe_b64encode y urlsafe_b64decode, esta es la versión que funciona para mí (después de pasar un 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:]))
neoneo
Puede obtener una frase de contraseña de una contraseña arbitraria mediante una función hash criptográfica (NO integrado de 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 duración que especifique.
Otra versión de esto (bastante derivada de las soluciones anteriores) pero
- usa nulo 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')
-
Esto no funcionará si el byte de entrada[] tiene valores nulos finales porque en la función descifrar () comerá sus valores nulos de relleno MÁS cualquier valor nulo final.
– Buzz Moschetti
31 de diciembre de 2017 a las 18:48
-
Sí, como dije anteriormente, esta lógica se rellena con nulos. Si los elementos que desea codificar/decodificar pueden tener nulos finales, mejor use una de las otras soluciones aquí
– Mikee
1 de enero de 2018 a las 22:12
-
** advertencia ** esta función no funciona con caracteres latinos y especiales.
– Elías Prado
18 oct 2021 a las 13:11
os.urandom es motivado sobre el PyCrypto sitio web. Utiliza Microsoft CryptGenRandom función que es una CSPRNG
– Joel Vroom
28 de noviembre de 2013 a las 15:43
o
/dev/urandom
en Unix– Joel Vroom
28 de noviembre de 2013 a las 16:19
Solo para aclarar, en este ejemplo frase de contraseña es el llave que puede ser de 128, 192 o 256 bits (16, 24 o 32 bytes)
– Marca
21/04/2016 a las 21:37
Podría valer la pena mencionar que PyCrypto es un proyecto muerto. El último compromiso es de 2014. PyCryptodome parece un buen reemplazo directo
– Sobrecarga
7 de agosto de 2018 a las 7:58
Esta pregunta es antigua, pero me gustaría señalar (a partir de 2020) que es probable que pycrypto esté desactualizado y ya no sea compatible. Mirando su página de github (github.com/pycrypto/pycrypto), parece que su último compromiso fue en 2014. Desconfiaría de usar software criptográfico que ya no está en desarrollo
– irritable_phd_syndrome
6 de junio de 2020 a las 3:40