AES Encriptado IV aleatorio por archivo

3

Saltar a actualización a continuación < no estoy seguro de cómo hacer un enlace :(

He hecho algunas mejoras en el código de: Csharp- AES-bits-Encryption-Library-with-Salt

  • saltBytes ahora es el SHA512 de la contraseña.
  • Random IV para cada llamada de cifrado. (La longitud 16 de IV se agrega al archivo encriptado, se elimina del archivo antes del descifrado)

¿Ves algún defecto, algo que necesite optimización? En particular, mis preguntas son:

  • ¿Está mi método generateIV() a continuación en el "Código de cifrado" seguro y protegido? Tenga en cuenta que su única dependencia es en .NET RNGCryptoServiceProvider Class .

  • ¿Es seguro usar el hash de contraseña como un salt? o debería ser aleatorio como el IV y almacenado junto con el texto cifrado?

Solo para referencia, aquí está mi código.

El siguiente código está funcionando. He realizado una prueba para cifrar dos archivos de texto con exactamente el mismo texto dentro de cada uno. El resultado es que ambos tienen datos diferentes, y descifran con éxito.

También verifiqué antes de que el IV aleatorio, ambos archivos tuvieran el mismo texto cifrado y obtuvieran los mismos datos.

Código de cifrado:

    private static int IV_LENGTH = 16;

    public static byte[] AES_Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes)
    {

        byte[] encryptedBytes = null;
        byte[] encryptedBytesAndIV = null;
        byte[] saltBytes = SHA512.Create().ComputeHash(passwordBytes);
        using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
        {
            using (AesCryptoServiceProvider AES = new AesCryptoServiceProvider())
            {
                AES.KeySize = 256;
                //AES.BlockSize = 128;

                var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 100);
                AES.Key = key.GetBytes(AES.KeySize / 8);
                AES.IV = generateIV();

                AES.Mode = CipherMode.CBC;

                using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
                    cs.Close();
                }
                encryptedBytes = ms.ToArray();
                encryptedBytesAndIV = new byte[encryptedBytes.Length + AES.IV.Length];
                AES.IV.CopyTo(encryptedBytesAndIV, 0);
                encryptedBytes.CopyTo(encryptedBytesAndIV, IV_LENGTH);
            }
        }

        return encryptedBytesAndIV;
    }

    private static byte[] generateIV()
    {
        using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
        {
            byte[] nonce = new byte[IV_LENGTH];
            rng.GetBytes(nonce);
            return nonce;
        }
    }

Código de descifrado:

    public static byte[] AES_Decrypt(byte[] bytesToBeDecrypted, byte[] passwordBytes)
    {
        byte[] decryptedBytes = null;


        byte[] saltBytes = SHA512.Create().ComputeHash(passwordBytes);

        using (MemoryStream ms = new MemoryStream())
        {
            using (AesCryptoServiceProvider AES = new AesCryptoServiceProvider())
            {
                AES.KeySize = 256;
                //AES.BlockSize = 128;

                var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 100);
                AES.Key = key.GetBytes(AES.KeySize / 8);
                AES.IV = getIV(bytesToBeDecrypted);
                bytesToBeDecrypted = removeTagAndIV(bytesToBeDecrypted);
                AES.Mode = CipherMode.CBC;

                using (var cs = new CryptoStream(ms, AES.CreateDecryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(bytesToBeDecrypted, 0, bytesToBeDecrypted.Length);
                    cs.Close();
                }
                decryptedBytes = ms.ToArray();


            }
        }

        return decryptedBytes;
    }

    private static byte[] removeTagAndIV(byte[] arr)
    {
        byte[] enc = new byte[arr.Length - IV_LENGTH];
        Array.Copy(arr, IV_LENGTH, enc, 0, arr.Length - IV_LENGTH);
        return enc;
    }

    private static byte[] getIV(byte[] arr)
    {
        byte[] IV = new byte[IV_LENGTH];
        Array.Copy(arr, 0, IV, 0, IV_LENGTH);
        return IV;
    }

Actualizar:

Aquí hay un código actualizado basado en comentarios / recomendaciones / consejos

  • Sal aleatoria Verdadera
  • iteraciones 100000

Encriptación:

        public static byte[] AES_Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes)
    {

        byte[] encryptedBytes = null;
        byte[] encryptedBytesFinal = null;
        byte[] saltBytes = generateIVandSalt(16);
        using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
        {
            using (AesCryptoServiceProvider AES = new AesCryptoServiceProvider())
            {
                AES.KeySize = 256;

                var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 100000);

                AES.Key = key.GetBytes(AES.KeySize / 8);

                AES.IV = generateIVandSalt(16);

                AES.Mode = CipherMode.CBC;

                using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
                    cs.Close();
                }
                encryptedBytes = ms.ToArray();
                encryptedBytesFinal = new byte[encryptedBytes.Length + AES.IV.Length + saltBytes.Length];

                AES.IV.CopyTo(encryptedBytesFinal, 0);
                saltBytes.CopyTo(encryptedBytesFinal, 16);
                encryptedBytes.CopyTo(encryptedBytesFinal, 16 + 16);

            }
        }

        return encryptedBytesFinal;
    }
    private static byte[] generateIVandSalt(int len)
    {
        using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
        {
            byte[] nonce = new byte[len];
            rng.GetBytes(nonce);
            return nonce;
        }
    }

Descifrado:

        public static byte[] AES_Decrypt(byte[] bytesToBeDecrypted, byte[] passwordBytes)
    {
        byte[] decryptedBytes = null;


        byte[] saltBytes = getSalt(bytesToBeDecrypted);

        using (MemoryStream ms = new MemoryStream())
        {
            using (AesCryptoServiceProvider AES = new AesCryptoServiceProvider())
            {
                AES.KeySize = 256;


                var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 100000);
                AES.Key = key.GetBytes(AES.KeySize / 8);
                AES.IV = getIV(bytesToBeDecrypted);
                bytesToBeDecrypted = removeIVandSalt(bytesToBeDecrypted);
                AES.Mode = CipherMode.CBC;

                using (var cs = new CryptoStream(ms, AES.CreateDecryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(bytesToBeDecrypted, 0, bytesToBeDecrypted.Length);
                    cs.Close();
                }
                decryptedBytes = ms.ToArray();


            }
        }

        return decryptedBytes;
    }

    private static byte[] removeIVandSalt(byte[] arr)
    {
        byte[] enc = new byte[arr.Length - 16 - IV_LENGTH];
        Array.Copy(arr, IV_LENGTH + 16, enc, 0, arr.Length - IV_LENGTH - 16);
        return enc;
    }
    private static byte[] getIV(byte[] arr)
    {
        byte[] IV = new byte[IV_LENGTH];
        Array.Copy(arr, 0, IV, 0, IV_LENGTH);
        return IV;
    }

    private static byte[] getSalt(byte[] arr)
    {
        byte[] salt = new byte[16];
        Array.Copy(arr, IV_LENGTH, salt, 0, 16);
        return salt;
    }
    
pregunta xhxx 21.11.2016 - 18:27
fuente

2 respuestas

6

Desde una perspectiva de seguridad, el método generateIV() está bien, si un poco redundante como .NET generará un IV para usted exactamente de la misma manera si no se proporciona uno.

Los tres problemas más importantes son que 100 iteraciones es manera demasiado poco para PBKDF2, la falta de autenticación, que es un problema si el texto cifrado está siempre fuera de su control (aparte de eso, tiene una método llamado removeTagAndIV() pero ninguna etiqueta implementada en primer lugar) y el uso del hash de contraseña como un salt. El punto de una sal es ser globalmente único. El hash de una contraseña en bruto no lo es. Ahora ha debilitado significativamente su PBKFD, ha limitado enormemente el tamaño potencial del espacio de claves práctico y ha creado la posibilidad de usar tablas de arco iris para identificar claves. La sal debe ser globalmente única. La forma más fácil de lograr esto sería simplemente generar una sal utilizando el método generateIV() que ya tiene, y almacenarlo junto con la IV y el texto cifrado.

    
respondido por el Xander 21.11.2016 - 19:07
fuente
3
  1. No soy un programador de .NET y no entiendo completamente parte del código aquí.

  2. ¿Cuánta entropía hay en passwordBytes ? Si la entropía está por debajo de 72 bits, deberá usar un Hash (lento) de contraseñas , o más específicamente una función de derivación de claves.

  3. generateIV debería estar bien siempre que sea aleatorio. Ni siquiera tiene que ser un número aleatorio seguro criptográficamente, siempre que sea globalmente único.

  4. Si desea estar seguro de que CBC es un buen modo de encadenamiento, puede hacer una pregunta por separado. Tendremos respuestas sobre las ventajas y desventajas de cada modo de encadenamiento.

  5. No debe pedirnos que verifiquemos el código de descifrado por seguridad. Puede examinarlo usted mismo simplemente probando.

  6. La sal debe ser aleatoria, no determinista. También agrega Pepper.

  7. Vea # 5 y luego haga que su cifrado sea lo más breve y conciso posible. Haga preguntas sobre los puntos individuales que le gusten, pero el hecho de que nosotros revisemos todo esto es un tema un tanto apartado.

  8. AES es una buena opción.

respondido por el George Bailey 21.11.2016 - 19:02
fuente

Lea otras preguntas en las etiquetas