Seguridad de la clave bcrypt / sha256 utilizada con AES para cifrar un archivo

6

Estoy buscando cifrar archivos usando algoritmos de cifrado y hash seguro en Python. Habiendo usado bcrypt en el pasado, decidí usarlo para mi calculadora de frase de contraseña, luego pasar la salida a través de SHA256 para obtener mis 32 bytes de datos, luego usar eso con AES para cifrar / descifrar un archivo:

#!/usr/bin/env python

from argparse import ArgumentParser
from bcrypt import gensalt, hashpw
from Crypto.Cipher import AES
from hashlib import sha256

import os, struct, sys

def main():
    parser = ArgumentParser(description = "Encrypts or decrypts a file using " +
            "bcrypt for the password and triple AES for file encryption.")
    parser.add_argument('-p', '--passphrase', required = True, 
            help = "The passphrase to use for encryption.")
    parser.add_argument('-i', '--input', required = True,
            help = "The input file for encryption / decryption.")
    parser.add_argument('-o', '--output', required = True,
            help = "The output file for encryption / decryption.")
    parser.add_argument('-r', '--rounds', default = 10, 
            help = "The number of bcrypt rounds to use.")
    parser.add_argument('-s', '--salt', default = None,
            help = "The salt to use with bcrypt in decryption.")
    parser.add_argument('operation', choices = ('encrypt', 'decrypt'),
            help = "The operation to apply, whether to encrypt or decrypt data.")

    parameters = parser.parse_args()

    if parameters.operation == 'encrypt':
        encrypt(parameters.input, parameters.output, parameters.passphrase,
                parameters.rounds)
    elif parameters.operation == 'decrypt':
        decrypt(parameters.input, parameters.output, parameters.passphrase,
                parameters.salt)

def encrypt(input_file, output_file, passphrase, rounds):
    bcrypt_salt = gensalt(rounds)
    bcrypt_passphrase = hashpw(passphrase, bcrypt_salt)
    passphrase_hash = sha256(bcrypt_passphrase).digest()

    print "Salt: %s" % (bcrypt_salt, )

    iv = os.urandom(16)

    cipher = AES.new(passphrase_hash, AES.MODE_CBC, iv)

    with open(input_file, 'rb') as infile:
        infile.seek(0, 2)
        input_size = infile.tell()

        infile.seek(0)  

        with open(output_file, 'wb') as outfile:
            outfile.write(struct.pack('<Q', input_size))
            outfile.write(iv)

            while True:
                chunk = infile.read(64 * 1024)
                if len(chunk) == 0:
                    break
                elif len(chunk) % 16 != 0:
                    chunk += ' ' * (16 - len(chunk) % 16)

                outfile.write(cipher.encrypt(chunk))

    return bcrypt_salt

def decrypt(input_file, output_file, passphrase, salt):
    print "Salt: %s" % (salt,)

    bcrypt_passphrase = hashpw(passphrase, salt)
    passphrase_hash = sha256(bcrypt_passphrase).digest()

    with open(input_file, 'rb') as infile:
        input_size = struct.unpack('<Q', infile.read(struct.calcsize('Q')))[0]
        iv = infile.read(16)

        cipher = AES.new(passphrase_hash, AES.MODE_CBC, iv)

        with open(output_file, 'wb') as outfile:
            while True:
                chunk = infile.read(64 * 1024)
                if len(chunk) == 0:
                    break

                outfile.write(cipher.decrypt(chunk))

            outfile.truncate(input_size)

if __name__ == "__main__":
    main()

¿Cuáles son los posibles puntos débiles de una implementación como esta?

Lo que he determinado es que sería fácil para un atacante determinar el tamaño del archivo original, sin embargo eso no revela mucho sobre el archivo. SHA-256 no es el mejor algoritmo de hash del mundo, pero ajustar una contraseña de bcrypt me haría creer que todas las amenazas se mitigarían allí. Debido a la capacidad de bcrypt para aumentar la seguridad con el tiempo agregando más rondas al algoritmo, parece una apuesta bastante segura para usar bcrypt por ahora.

¿Hay agujeros vacíos en esta implementación? No soy criptógrafo, pero sí conozco los conceptos básicos y los propósitos de cada uno de los tres algoritmos que se utilizan aquí.

    
pregunta Naftuli Kay 22.01.2013 - 05:03
fuente

1 respuesta

9

De haberlo mirado durante dos minutos:

  • Usar bcrypt es sonido. SHA-256 es una de las funciones hash más conocidas (porque ha estado presente por más de 10 años, se ha implementado ampliamente y, sin embargo, no se ha visto afectada).

  • Ya que genera un nuevo salt para cada archivo cifrado (¡y eso es bueno!), tendría sentido almacenarlo en un encabezado de archivo. De esa manera, no tendría que proporcionarlo como parámetro a la rutina de descifrado. Una sal no debe ser secreta (que es una sal , no una clave ), sino que se debe generar una nueva sal para cada cifrado, por lo tanto, el manejo externo puede ser engorroso. Ya tiene un encabezado personalizado para el IV, podría hacer lo mismo para el salt.

    Alternativa: almacene salt como encabezado y genere tanto clave de cifrado y el IV de la salida bcrypt (dividir el SHA-256 salida en dos trozos de 128 bits; La primera es la clave, la segunda es la clave. el IV). Esto mantendría el tamaño del encabezado a 16 bytes. Pero un IV al azar (de os.urandom() , como usted lo hace) tampoco es malo.

  • Usted usa el cifrado pero no hay MAC . No hay muchos modelos de ataque en los que el atacante pueda observar datos (es decir, necesita confidencialidad ) pero no alterar los datos (es decir, no necesita integridad ). En la práctica, sería mejor agregar un MAC. Lo ideal es reemplazar el CBC con un modo que maneje tanto el cifrado como la integridad ( GCM , EAX ).

    Si su biblioteca de soporte no proporciona tal modo, tendrá que hacerlo "estilo antiguo" combinando el cifrado con HMAC . Esto es no es totalmente trivial de hacer . La mejor práctica sería generar una clave MAC adicional a partir de la salida de bcrypt (si produce la clave de cifrado, la IV y la clave MAC, necesitará una función hash más grande, por ejemplo, SHA-512) y compute el MAC sobre la concatenación de los datos IV y encriptados (puede omitir el IV solo si lo genera a partir de la frase de contraseña, pero es más seguro incluirlo en la entrada MAC). Tras el descifrado, verifique primero el MAC, luego proceda al descifrado solo si el MAC coincide.

  • Es posible que desee incluir alguna provisión para la agilidad del algoritmo, es decir, agregar en el encabezado un valor de byte que identifique la combinación de algoritmos que usa (bcrypt, SHA-256, AES + CBC , HMAC / SHA-256). Esto le permitiría cambiar posteriormente a otro conjunto de algoritmos sin romper la compatibilidad del código con los archivos existentes. En ese caso, no olvide agregar este "byte identificador de algoritmo" a la entrada para el MAC.

  • Su relleno es ambiguo: al descifrar, encontrará algunos caracteres de espacio al final, pero no sabrá si los espacios adicionales formaron parte del archivo de entrada o si fueron agregados por la función de cifrado. Y, efectivamente, al descifrar se dejan los espacios. Esto podría no ser un problema para su escenario de uso específico, pero, como herramienta general de cifrado / descifrado, generalmente se considera un problema si los archivos no sobreviven a un viaje de cifrado / descifrado sin modificaciones. Comúnmente se utiliza PKCS # 7 padding (también conocido como PKCS # 5 padding): agrega k bytes (al menos 1, como máximo 16, por lo que la longitud total es un múltiplo de 16) y todos los bytes tienen un valor k . Luego de descifrar, mire el último byte, que le dirá cuántos bytes de relleno se agregaron.

respondido por el Thomas Pornin 22.01.2013 - 12:44
fuente

Lea otras preguntas en las etiquetas