[SOLUCIONADO] AES 256 y el método de fuerza bruta

Iniciado por Lafleur212, Diciembre 07, 2020, 11:15:31 AM

Tema anterior - Siguiente tema

0 Miembros y 1 Visitante están viendo este tema.

Diciembre 07, 2020, 11:15:31 AM Ultima modificación: Diciembre 12, 2020, 06:35:37 PM por AXCESS
Estoy estudiando el algoritmo AES con sus variantes (ECB, CBC, OFB, CFB, etc). También sé que por fuerza bruta, probando todas las contraseñas posibles, se llegaría a descifrar cualquier archivo.
El problema que me encuento es cómo se ataca al fichero cifrado. Suponiendo que dispongo del fichero cifado, una cadena de bytes, y no dispongo de ningún tipo de información más, y quiero probar la contraseña '000001', cómo se comprueba que esa clave es o no la correcta?
Dispongo de una función de encriptado en C#, y he cifrado un archivo de texto con la contraseña 'hola', que me devuelve una cadena de bytes la cual es el fichero cifrado. Bien, pues con la función de descifrado, con la contraseña "adios", me devuelve otra cadena de bytes, sin dar error, aunque evidentemente el texto no es legible.
Sin dar ningún tipo de error, cómo podría comprobar si la contraseña que pruebo es la correcta? Si el cifrado te permite cifrar y descifrar con una contraseña distinta, aunque no te devuelva el archivo corecto, cómo sabe la máquina que esa clave no es la correcta?
NOTA: he empleado el mismo IV tanto para el cifrado como para el descifrado.

Citar...cómo se comprueba que esa clave es o no la correcta?...

Dependiendo del modo que use, y si éste verifica que el mensaje no se haya modificado en transmisión, por lo que le será más sencillo (aunque igual sigue siendo realmente complejo e inviable).

Supongamos que tenemos el siguiente script:

aes_gcm.py:
Código: python
#!/usr/bin/env python

from Crypto.Cipher import AES

def _convert2bytes(s):
return s.encode() if not (isinstance(s, bytes)) else s

def encrypt(plaintext, password):
password = _convert2bytes(password)
plaintext = _convert2bytes(plaintext)

cipher = AES.new(password, AES.MODE_GCM)
nonce = cipher.nonce
ciphertext, tag = cipher.encrypt_and_digest(plaintext)

return nonce+tag+ciphertext

def decrypt(ciphertext, password):
password = _convert2bytes(password)

nonce = ciphertext[:16]
tag = ciphertext[16:32]
ciphertext = ciphertext[32:]

descipher = AES.new(password, AES.MODE_GCM, nonce=nonce)

return descipher.decrypt_and_verify(ciphertext, tag)


Ahora ciframos:

Código: text
>>> import aes_gcm
>>> key = ("1"*32).encode()
>>> plaintext = "Hello world!"
>>> ciphertext = aes_gcm.encrypt(plaintext.encode(), key)
>>> ciphertext
b"{\xf5$'\x08\xc9\xa1\xdb\x0e\xc9D\x1cm#\xf6\x912g@+\xbe\xe1\xce\xc4\xe6\xb9=Q\t\xe5\xbbV\xb6s\xf4\x9c8\xd34\xee\xee\xecP\xed"
>>> decrypted = aes_gcm.decrypt(ciphertext, key)
>>> decrypted
b'Hello world!'
>>> decrypted.decode() == plaintext
True
>>>


Muy bien. Este modo, con el diligente uso, te permite verificar si el mensaje ha sido corrompido o si ha habido un cambio incongruente. Como por ejemplo:

Código: php
>>> key = b'0' + key[1:]
>>> key
b'01111111111111111111111111111111'
>>> decrypted = aes_gcm.decrypt(ciphertext, key)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/tmp/aes_gcm.py", line 27, in decrypt
    return descipher.decrypt_and_verify(ciphertext, tag)
  File "/home/dtxdf/.local/lib/python3.8/site-packages/Crypto/Cipher/_mode_gcm.py", line 567, in decrypt_and_verify
    self.verify(received_mac_tag)
  File "/home/dtxdf/.local/lib/python3.8/site-packages/Crypto/Cipher/_mode_gcm.py", line 508, in verify
    raise ValueError("MAC check failed")
ValueError: MAC check failed
>>>


Si se nota, simplemente cambié una cifra en la contraseña y generó un error; por supuesto que esto depende de la implantación, pero por lo general, generaría una excepción sin importar la librería.

En caso de que los datos cifrados no sean parte del No tienes permitido ver los links. Registrarse o Entrar a mi cuenta se complicaría un poco más. Incluso de podría decir que ya no es inviable, sino que está llegando a lo imposible, pero podría hacer lo siguiente:

aes_cbc.py:

Código: python
#!/usr/bin/env python

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

def _convert2bytes(s):
return s.encode() if not (isinstance(s, bytes)) else s

def encrypt(plaintext, password):
password = _convert2bytes(password)
plaintext = _convert2bytes(plaintext)

cipher = AES.new(password, AES.MODE_CBC)
ciphertext = cipher.encrypt(pad(plaintext, AES.block_size))

return cipher.iv + ciphertext

def decrypt(ciphertext, password):
password = _convert2bytes(password)

iv = ciphertext[:16]
ciphertext = ciphertext[16:]

descipher = AES.new(password, AES.MODE_CBC, iv=iv)

return unpad(descipher.decrypt(ciphertext), AES.block_size)


Código: text
>>> import aes_cbc
>>> key = ("1"*32).encode()
>>> plaintext = "Hello world!"
>>> ciphertext = aes_cbc.encrypt(plaintext, key)
>>> ciphertext
b';\xa4\xd5\xffgd\xe5\x98\x06i\xab\xbal\x19\xb2\xeai\xa8\x07\xca\xbe\x17U\\)\xc1\x1d\xfc\x03\xb9\xc7\x06'
>>> aes_cbc.decrypt(ciphertext, key)
b'Hello world!'
>>> key = b'0' + key[1:]
>>> aes_cbc.decrypt(ciphertext, key)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/tmp/aes_cbc.py", line 26, in decrypt
    return unpad(descipher.decrypt(ciphertext), AES.block_size)
  File "/home/dtxdf/.local/lib/python3.8/site-packages/Crypto/Util/Padding.py", line 93, in unpad
    raise ValueError("PKCS#7 padding is incorrect.")
ValueError: PKCS#7 padding is incorrect.
>>>


Esta vez, el caballero que nos ayudó fue el No tienes permitido ver los links. Registrarse o Entrar a mi cuenta, que por su naturaleza detectó una anomalía. Pero no siempre éste será el que nos ayudará y como mencioné, depende de la implantación. Entones lo que sí podría aliviar un poco la tensión es detectar si los caracteres son o no, imprimibles, pero claro, esto es con caracteres que mayormente usamos los humanos y no son procesados de una forma especial (como \n, \r, \t, etc.) y también puede ser que desencriptemos el mensaje, pero sea un archivo, y como un archivo puede ser cualquier cosa (incluyendo caracteres no imprimibles) puede que lo que le digo no sea del todo correcto, pero puede probar el siguiente script para ver si le guía por un camino correcto: No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Manualmente el script puede posarse de la siguiente manera (eliminando el relleno):

aes_cbc.py (sin relleno):
Código: python
#!/usr/bin/env python

from Crypto.Cipher import AES

def _convert2bytes(s):
return s.encode() if not (isinstance(s, bytes)) else s

def encrypt(plaintext, password):
password = _convert2bytes(password)
plaintext = _convert2bytes(plaintext)

cipher = AES.new(password, AES.MODE_CBC)
ciphertext = cipher.encrypt(plaintext)

return cipher.iv + ciphertext

def decrypt(ciphertext, password):
password = _convert2bytes(password)

iv = ciphertext[:16]
ciphertext = ciphertext[16:]

descipher = AES.new(password, AES.MODE_CBC, iv=iv)

return descipher.decrypt(ciphertext)


Código: text
>>> import aes_cbc
>>> plaintext = "Buen día mundo.".encode()
>>> len(plaintext)
16
>>> key = ("1"*32).encode()
>>> ciphertext = aes_cbc.encrypt(plaintext, key)
>>> ciphertext
b'\xbb\xf4\x016\xd4\xea\xde\x9f\x92\xc7*\xb2]\xc2\xe8\xb4\xeegt\x9dy\xd9\x14\xa8\xb1\xee\x87\x93{&`\xfa'
>>> aes_cbc.decrypt(ciphertext, key)
b'Buen d\xc3\xada mundo.'
>>> key = b'0' + key[1:]
>>> decrypted = aes_cbc.decrypt(ciphertext, key)
>>> decrypted
b'\x05\xa0?IL!\x8dW\xf1<\x97\x85\x7f\xe7\xa98'
>>> decrypted.decode()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa0 in position 1: invalid start byte
>>>



También es realmente útil indagar sobre algunos modos, que se podrían decir como clásicos, y que están en desuso, como:

* No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
* No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Puede que lo siguiente también sea igual de útil:

* No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

~ DtxdF
PGP :: <D82F366940155CB043147178C4E075FC4403BDDC>

~ DtxdF

Lo primero muchísimas gracias por la respuesta, muy bien afinada de verdad.
Entiendo lo que planteas, pero eso serviría para texto, detectando la anomalía de que no es un byte imprimible.
Aunque la verdad, en el caso de un archivo como por ejemplo un vídeo o una ddl, siempre van a a aparecer, con lo cual, pudieran ser muchas contraseñas. Y tratandose de texto, nos encontraríamos ante el mismo problema, serían muchisimas las combinaciones de texto imprimible como para determinar cual de ellas es la clave correcta.
Es por eso que, podemos decir, que en un archivo  de una longitud digamos, de apartir de 20 megas, es imposible hallar la contraseña extacta, aún disponiendo de la eternidad y con una máquina con velocidad infinita?



Diciembre 11, 2020, 06:41:21 PM #3 Ultima modificación: Diciembre 11, 2020, 06:47:10 PM por DtxdF
@No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

La fuerza bruta es desde lejos el ataque más viable contra AES. Le dejo estas respuestas a su cuestión, y además le agrego un libro que recomendó nuestro compañero que trata sobre criptografía.

Breaking AES with ChipWhisperer - Piece of scake (Side Channel Analysis 100)

No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Vídeo y canal recomendable para aprender, aunque este vídeo que se presenta en este momento es muy bueno, hallará de sobra del mismo sujeto y del mismo tema.

No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Una sencilla pregunta con excelentes respuestas, enlazadas en el mismo post. Lealo, le dotará de más respuestas.

No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

~ DtxdF
PGP :: <D82F366940155CB043147178C4E075FC4403BDDC>

~ DtxdF