password - Python 3.2-Symétric AES Crypto en modo CBC-consejos solicitados
python cipher password (3)
Mi objetivo es configurar el cifrado simple con Python 3.x, así que busqué en la web este fin de semana para obtener información sobre RSA / AES, etc. En realidad, cosas que pueden parecer una posibilidad de cifrar datos de texto para su transmisión en un forma segura y razonable ... sin paranoia tampoco, no soy un experto solo quiero asegurarme de que el material sea bastante difícil de leer sin la clave.
Honestamente, no sé mucho sobre criptografía. Después de varias horas de búsqueda y recopilación de información y código fuente, mis intentos fracasaron debido a problemas de longitud no válida u otros errores de conversión debido a los ejemplos proporcionados en Python 2.7. Encontré muy pocos ejemplos en Python 3 y los métodos de encriptación utilizados parecían no ser realmente apropiados o serios.
Finalmente pude ejecutar el siguiente código que acepta caracteres codificados ISO 8859-1. De hecho, encapsula todo en codificación UTF-8 para evitar problemas de idioma. Eso espero ...
Me gustaría saber si estoy en el camino correcto del diseño y especialmente si la seguridad de los datos es aceptable, de nuevo no estoy buscando la gran solución de seguridad, solo quiero proteger mis datos personales y no proteger una defensa militar. secreto jaja!
¡No dude en enviarme sus comentarios o sugerencias y especialmente las cosas que podría haber perdido!
Muchas gracias por adelantado.
Emmanuel (Francia)
Nota: el próximo paso intentaré enviar una contraseña AES encriptada RSA al destinatario junto con la secuencia de texto. Como la contraseña de AES es diferente para cada mensaje, el cliente debe traducirla automáticamente para poder decodificar el mensaje de cifrado. La contraseña de AES se transmitirá en encriptación asimétrica RSA con la clave más fuerte posible sin desglosar el rendimiento. El objetivo es transmitir mensajes simples (sin codificación base64) o grandes volúmenes de datos en un marco de tiempo razonable.
@ + nos vemos
Para ejecutar el siguiente código, debe tener PyCrypto (python 3.2) instalado
import os, base64, hashlib
from Crypto.Cipher import AES
class Aes(object):
# Crypte / décrypte un texte donné en AES mode CBC. Accepte l''encodage base64.
# Encrypts input text string & decrypts bytes encoded string with or without base64 encoding
# Author: [email protected] - 12/2013
SALT_LENGTH = 64
DERIVATION_ROUNDS=10000
BLOCK_SIZE = 16
KEY_SIZE = 256
MODE = AES.MODE_CBC
def encrypt(self, source, aes_key, outfile=None, base64_encode=False):
''''''
Crypte l''entrée source en AES mode CBC avec sortie encodage base64 / fichier facultative
@param str source: text to encode or text file path
@param bytes aes_key: password
@parm str outfile: disk file to write encoded text to. defaults to None
@param bool base64_encode: returns base64 encoded string if True (for emails) or bytes if False
@return bytes ciphertext: the bytes encoded string.
''''''
''''''
----------------------------
Inputs management
----------------------------
''''''
if os.path.exists(source):
fp = open(source, ''rb'')
input_text = fp.read()
fp.close()
else:
input_text = bytes(source, ''UTF-8'')
if input_text == b'''':
print(''No data to encrypt'')
return
padding_len = 16 - (len(input_text) % 16)
padded_text = str(input_text, ''UTF-8'') + chr(padding_len) * padding_len
''''''
---------------------------------------------------------
Computes the derived key (derived_key).
---------------------------------------------------------
Elle permet d''utiliser la clé initiale (aes_key) plusieurs
fois, une pour chaque bloc à encrypter.
---------------------------------------------------------
''''''
salt = os.urandom(self.SALT_LENGTH)
derived_key = bytes(aes_key, ''UTF-8'')
for unused in range(0,self.DERIVATION_ROUNDS):
derived_key = hashlib.sha256(derived_key + salt).digest()
derived_key = derived_key[:self.KEY_SIZE]
''''''
----------------
Encrypt
----------------
''''''
# The initialization vector should be random
iv = os.urandom(self.BLOCK_SIZE)
cipherSpec = AES.new(derived_key, self.MODE, iv)
cipher_text = cipherSpec.encrypt(padded_text)
cipher_text = cipher_text + iv + salt
''''''
-------------------------
Output management
-------------------------
''''''
if outfile is None:
''''''
Returns cipher in base64 encoding. Useful for email management for instance
''''''
if base64_encode:
return(base64.b64encode(cipher_text))
else:
return(cipher_text)
else:
''''''
Writes result to disk
''''''
fp = open(outfile, ''w'')
if base64_encode:
fp.write(base64.b64encode(cipher_text))
else:
fp.write(cipher_text)
fp.close()
print(''Cipher text saved in'', outfile)
def decrypt(self, source, aes_key, outfile=None, base64_encode=False):
''''''
Decrypts encoded string or data file
@param bytes or str source: encrypted bytes string to decode or file path
@param bytes aes_key: password
@parm str outfile: disk file to write encoded text to. defaults to None
@param bool base64_encode: cipher text is given base64 encoded (for mails content for examples)
@returns str secret_text: the decoding text string or None if invalid key given
''''''
''''''
---------------------------
Input management
---------------------------
''''''
if type(source) == str and os.path.exists(source):
fp = open(source, ''rb'')
ciphertext = fp.read()
fp.close()
elif type(source) == bytes:
ciphertext = source
else:
print(''Invalid data source'')
return
if base64_encode:
encoded_text = base64.b64decode(ciphertext)
else:
# decodedCiphertext = ciphertext.decode("hex")
encoded_text = ciphertext
''''''
-------------------------
Computes derived key
-------------------------
''''''
iv_start = len(encoded_text) - self.BLOCK_SIZE - self.SALT_LENGTH
salt_start = len(encoded_text) - self.SALT_LENGTH
data, iv, salt = encoded_text[:iv_start], encoded_text[iv_start:salt_start], encoded_text[salt_start:]
derived_key = bytes(aes_key, ''utf-8'')
for unused in range(0, self.DERIVATION_ROUNDS):
derived_key = hashlib.sha256(derived_key + salt).digest()
derived_key = derived_key[:self.KEY_SIZE]
''''''
-------------------------
Decrypt
-------------------------
''''''
Cipher = AES.new(derived_key, self.MODE, iv)
padded_text = Cipher.decrypt(data)
padding_length = padded_text[-1]
secret_text = padded_text[:-padding_length]
''''''
Si le flux n''est pas décodé (mot de passe invalide), la conversion UTF-8 plante ou au mieux on obtient un texte illisible
''''''
try:
secret_text = str(secret_text, ''utf-8'')
except:
return
if outfile is None:
return(secret_text)
else:
''''''
Writes result to disk
''''''
fp = open(outfile, ''w'')
fp.write(secret_text)
fp.close()
cosas finales
Hice los siguientes cambios:
- usa PBKDF2 como KDF con HMAC-sha512
- Solucionado el problema constante
- Los paquetes obligatorios son ahora: PyCypto y pbkdf2-1.3
He intentado durante mucho tiempo insertar el nuevo bloque de código ... pero no funciona. Comportamiento muy extraño del editor de texto.
Faust ya hizo algunas observaciones interesantes, pero tengo algunas más. Como ya dijo Faust, parece que va en la dirección correcta.
- Utilice PBKDF2 para derivación de clave en lugar de su KDF propietario;
- Agregue un HMAC al final de su texto cifrado, controle su sal y cualquier información sobre el algoritmo, revise el HMAC antes de confiar en el texto plano y el relleno;
- Tenga en cuenta que la codificación UTF-8 no es compatible con la codificación ISO 8859-1 para caracteres de 127 o más, sin duda podrá codificar todos los caracteres definidos para ISO 8859-1 ;
- La sal debe colocarse frente al texto cifrado, de lo contrario no se puede descifrar de manera eficiente (necesitará todo el texto cifrado antes de poder comenzar a descifrarlo);
- Puede establecer el IV en cero (y posiblemente no enviarlo) si genera una sal aleatoria cada vez (solo se requiere IV aleatoria si se vuelve a utilizar la clave);
- Durante el descifrado, no confíe
padding_length = padded_text[-1]
enpadding_length = padded_text[-1]
(consulte la parte sobre HMAC); - Use constantes cuando se proporcionen, por ejemplo,
AES.block_size
lugar de 16; - Utilice el generador de números aleatorios provisto por la biblioteca, puede aceptar cualquier cosa desde la base de clase
BaseRNG
pero use elOSRNG
por defecto
Tenga en cuenta que las clases RNG de Python crypto son extremadamente difíciles de entender, consérvelas en os.urandom
si no puede encontrar una buena forma de utilizar las que están en la biblioteca.
Lo estás haciendo mejor de lo que esperaba: P. Solo un par de sugerencias para mejorar un poco su código:
- Sería mejor si usa una función de derivación de clave agradable, famosa y fuerte como PBKDF2 con HMAC-sha256. Su KDF se ve fuerte, pero cuando habla de criptografía es mejor confiar en algoritmos ampliamente revisados.
- Puede considerar la posibilidad de usar os.random en lugar de os.urandom (o al menos hacer que sea simple cambiar de uno a otro) para obtener más entropía.
- Podría agregar algunos "encabezados" al resultado encriptado que le permitiría descifrarlo sin saber antes del tamaño de las claves y otras cosas variables, que ahora están codificadas.
- Proporcione al usuario una manera más fácil de cambiar esas configuraciones que ahora están codificadas.
Además, para su próximo paso, le sugiero que eche un vistazo al intercambio de claves DH . Esto te dará perfecto secreto hacia delante.
La última versión de la fuente 0.3. Espero que ayude a alguien.
# -*- coding: utf-8 -*-
from Crypto.Cipher import AES
from Crypto.Hash import HMAC, SHA512
from pbkdf2 import PBKDF2
import os, base64, bz2, binascii
class Aes(object):
''''''
Crypte / décrypte un texte donné en AES mode CBC. Accepte l''encodage base64.
Encrypts input text string & decrypts bytes encoded string with or without base64 encoding
PyCrypto and pbkdf2-1.3 packages are mandatory
Author: [email protected] - 12/2013
''''''
SALT_LENGTH = 32 # 32 bytes = 256 bits salt
DERIVATION_ROUNDS=7000
KEY_SIZE = 32 # 256 bits key
MODE = AES.MODE_CBC
def encrypt(self, source, aes_key, outfile=None, base64_encode=False):
''''''
Crypte l''entrée source en AES mode CBC avec sortie encodage base64 / fichier facultative
@param str source: text to encode or text file path
@param bytes aes_key: password in byte
@parm str outfile: disk file to write encoded text to. defaults to None
@param bool base64_encode: returns base64 encoded string if True (for emails) or bytes if False
@return bytes ciphertext: the bytes encoded string.
''''''
''''''
----------------------------
Inputs management
----------------------------
''''''
if os.path.exists(source):
fp = open(source, ''rb'')
input_text = fp.read()
fp.close()
else:
input_text = bytes(source, ''UTF-8'')
if input_text == b'''':
print(''No data to encrypt'')
return
''''''
# padding_len = AES.block_size - (len(input_text) % AES.block_size)
# padded_text = str(input_text, ''UTF-8'') + chr(padding_len) * padding_len
''''''
''''''
-------------------
Compress
------------------
''''''
cmp_text = bz2.compress(input_text)
b64_bin = base64.b64encode(cmp_text)
b64_str = str(b64_bin, ''UTF-8'')
padding_len = AES.block_size - (len(b64_str) % AES.block_size)
padded_text = b64_str + chr(padding_len) * padding_len
''''''
---------------------------------------------------------
Derived key computing PBKDF2 / specs RSA PKCS#5 V2.0
---------------------------------------------------------
''''''
salt = os.urandom(self.SALT_LENGTH)
derived_key = PBKDF2(bytes(aes_key, ''UTF-8''), salt, iterations=self.DERIVATION_ROUNDS, digestmodule=SHA512, macmodule=HMAC).read(self.KEY_SIZE)
''''''
----------------
Encrypt
----------------
''''''
# le vecteur d''initialisation doit être aléatoire
iv = os.urandom(AES.block_size)
Cipher = AES.new(derived_key, self.MODE, iv)
cipher_text = Cipher.encrypt(padded_text)
cipher_text = cipher_text + iv + salt
# cipher_text = salt + cipher_text
''''''
-------------------------
Output management
-------------------------
''''''
if outfile is None:
''''''
Returns cipher in base64 encoding. Useful for email management for instance
''''''
if base64_encode:
return(base64.b64encode(cipher_text))
else:
return(cipher_text)
else:
''''''
Writes result to disk
''''''
fp = open(outfile, ''w'')
if base64_encode:
fp.write(base64.b64encode(cipher_text))
else:
fp.write(cipher_text)
fp.close()
print(''Cipher text saved in'', outfile)
def decrypt(self, source, aes_key, outfile=None, base64_encode=False):
''''''
@param bytes or str source: encrypted bytes string to decode or file path
@param bytes aes_key: password
@parm str outfile: disk file to write encoded text to. defaults to None
@param bool base64_encode: cipher text is given base64 encoded (for mails content for examples)
@returns str secret_text: the decoding text string or None if invalid key given
''''''
''''''
---------------------------
Input management
---------------------------
''''''
if type(source) == str and os.path.exists(source):
fp = open(source, ''rb'')
ciphertext = fp.read()
fp.close()
elif type(source) == bytes:
ciphertext = source
else:
print(''Invalid data source'')
return
if base64_encode:
encoded_text = base64.b64decode(ciphertext)
else:
encoded_text = ciphertext
salt_start = len(encoded_text) - self.SALT_LENGTH
iv_start = len(encoded_text) - AES.block_size - self.SALT_LENGTH
data, iv, salt = encoded_text[:iv_start], encoded_text[iv_start:salt_start], encoded_text[salt_start:]
''''''
-------------------------
Derived key computing
-------------------------
''''''
# derived_key = PBKDF2(bytes(aes_key, ''UTF-8''), salt).read(self.KEY_SIZE)
derived_key = PBKDF2(bytes(aes_key, ''UTF-8''), salt, iterations=self.DERIVATION_ROUNDS, digestmodule=SHA512, macmodule=HMAC).read(self.KEY_SIZE)
''''''
-------------------------
Decrypt
-------------------------
''''''
Cipher = AES.new(derived_key, self.MODE, iv)
padded_text = Cipher.decrypt(data)
padding_length = padded_text[-1]
secret_text = padded_text[:-padding_length]
''''''
--------------------------
Decompress
--------------------------
''''''
cmp_text = base64.b64decode(secret_text)
secret_text = bz2.decompress(cmp_text)
''''''
Si le flux n''est pas décodé (mot de passe invalide), la conversion UTF-8 plante ou au mieux on obtient un texte illisible
''''''
try:
secret_text = str(secret_text, ''utf-8'')
except:
return
if outfile is None:
return(secret_text)
else:
''''''
Writes result to disk
''''''
fp = open(outfile, ''w'')
fp.write(secret_text)
fp.close()