python - mensaje - libreria pycrypto
Encriptar y descifrar usando PyCrypto AES 256 (9)
Intento crear dos funciones usando PyCrypto que acepten dos parámetros: el mensaje y la clave, y luego encriptar / 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 es desanimado 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 se recomienda? 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?
Esto es lo que hice 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))
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 de 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:])]
Déjame abordar tu pregunta sobre "modos". AES256 es una especie de cifrado de bloques . Toma como entrada una clave de 32 bytes y una cadena de 16 bytes, llamada bloque y genera 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 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 conoce como AES-CTR. Aconsejaría 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 eres muy cuidadoso con el relleno, ¡ hay attacks 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 también puede derivarse de una contraseña :
# 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 una mala práctica criptográfica . Consulte la wikipedia para obtener más información sobre los modos de operación.
Es posible que necesite las siguientes dos funciones: 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:])]
¿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 en el uso de PyCrypto, el IV se usa para mezclar la salida de un cifrado cuando la entrada es igual, por lo que el IV se elige como una cadena aleatoria y se usa como parte de la salida de cifrado, y luego Úselo para descifrar el mensaje.
Y aquí está mi implementación, espero que sea útil para ti:
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:] ))
Es un poco tarde pero creo que esto será de mucha ayuda. 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())
Otra opinión sobre esto (derivada en gran medida de las soluciones anteriores) pero
- utiliza nulo para relleno
no usa lambda (nunca ha sido 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'')
Para alguien a quien le gustaría usar urlsafe_b64encode y urlsafe_b64decode, aquí está la versión que está 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:]))
Para el beneficio de otros, aquí está mi implementación de descifrado que obtuve combinando 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()
Puede obtener una frase de contraseña de una contraseña arbitraria mediante una función de hash criptográfica ( NO hash
incorporado de Python) 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 de hash criptográfico simplemente usando [:16]
o [:24]
y conservará su seguridad hasta la longitud especificada.
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:])