Estoy tratando de aprender sobre seguridad cibernética y esto es lo primero que hice al respecto. Estoy usando este documento de MSDN (https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.rfc2898derivebytes?redirectedfrom=MSDN&view=netcore-3.1) y funciona parcialmente. Supongo que lo está cifrando bien, ya que cuando se trata del descifrado, algunos de los datos originales están ahí, pero algunos se pierden. Los datos que se cifran son una clase que se ha formateado en una cadena JSON (no creo que esto sea relevante, ya que todavía se está cifrando una cadena). datos originales

Pero una vez que se ha cifrado y descifrado, resulta así: Datos cifrados y luego descifrados

Ejecuté este código y comparé los resultados más de 5 veces y siempre es: el inicio es incorrecto, el nombre de usuario es en parte correcto, la contraseña siempre es correcta y la clave de inicio de sesión es en parte correcta. Entonces el error es recurrente y siempre en el mismo lugar.

La información que debe conocer, los datos se cifran y se guardan en un archivo .txt. El programa se ejecutará de nuevo e intentará descifrarlo. El Salt y la contraseña se guardan en otro archivo y se leen y se utilizan en el descifrado.

Hay una pregunta similar en stackoverflow, pero la respuesta solo dice que use Rijndael (por lo que no es realmente una respuesta), este código es para que yo lo aprenda y quiera una respuesta que no tenga 4 líneas.

Codifique si es curioso (pero es básicamente lo mismo que el documento de MSDN):

Cifrada:

    static void EncryptFile()
    {
        string pwd1 = SteamID;//steamID is referring to account ID on Valve Steam           
        using (RNGCryptoServiceProvider rngCsp = new
        RNGCryptoServiceProvider())
        {
            rngCsp.GetBytes(salt1); //salt1 is a programme variable and will get saved to a file
        }
        SecureData File = new SecureData(_UserName,_PassWord,_LoginKey);
        string JsonFile = JsonConvert.SerializeObject(File); //puts the class into Json format
        int myIterations = 1000; //not needed
        try
        {
            Rfc2898DeriveBytes k1 = new Rfc2898DeriveBytes(pwd1, salt1,
            myIterations);
            Aes encAlg = Aes.Create(); // This might be the issue as AES will be different when you decrypt 
            encAlg.Key = k1.GetBytes(16);
            MemoryStream encryptionStream = new MemoryStream();
            CryptoStream encrypt = new CryptoStream(encryptionStream,
            encAlg.CreateEncryptor(), CryptoStreamMode.Write);
            byte[] utfD1 = new System.Text.UTF8Encoding(false).GetBytes(
            JsonFile); //encrypt Data 

            encrypt.Write(utfD1, 0, utfD1.Length);
            encrypt.FlushFinalBlock();
            encrypt.Close();
            byte[] edata1 = encryptionStream.ToArray();
            k1.Reset();

            System.IO.File.WriteAllBytes(SecureFile, edata1); //writes encrypted data to file
        }
        catch (Exception e)
        {
            Console.WriteLine("Error: ", e);
        }
    }

Descifrada:

    static void DecryptFile()
    {
        string pwd1 = SteamID;
        byte[] edata1;
        try
        {
            edata1 = System.IO.File.ReadAllBytes(SecureFile); //reads the file with encrypted data on it
            Aes encAlg = Aes.Create(); //I think this is the problem as the keyvalue changes when you create a new programme
            Rfc2898DeriveBytes k2 = new Rfc2898DeriveBytes(pwd1, salt1); //inputs from last time carry over
            Aes decAlg = Aes.Create();
            decAlg.Key = k2.GetBytes(16);
            decAlg.IV = encAlg.IV;
            MemoryStream decryptionStreamBacking = new MemoryStream();
            CryptoStream decrypt = new CryptoStream(
            decryptionStreamBacking, decAlg.CreateDecryptor(), CryptoStreamMode.Write);
            decrypt.Write(edata1, 0, edata1.Length);
            decrypt.Flush();
            decrypt.Close();
            k2.Reset();
            string data2 = new UTF8Encoding(false).GetString(
            decryptionStreamBacking.ToArray());//decrypted data  
            SecureData items = JsonConvert.DeserializeObject<SecureData>(data2); //reformat it out of JSon(Crashes as format isn't accepted)
            _UserName = items.S_UserName;
            _PassWord = items.S_Password;
            _LoginKey = items.S_LoginKey;
        }
        catch (Exception e)
        {
            Console.WriteLine("Error: ", e);
            NewLogin();
        }
    }

Estructura de clase:

    class SecureData
    {
        public string S_UserName { get; set; } //These are variables that are apart of Valve steam Login process 
        public string S_Password { get; set; }
        public string S_LoginKey { get; set; }

        public SecureData(string z, string x, string y)
        {
            S_UserName = z;
            S_Password = x;
            S_LoginKey = y;
        }
    }
1
Andrew Foot 31 jul. 2020 a las 02:40

1 respuesta

La mejor respuesta

El problema es causado por diferentes IV para el cifrado y descifrado. Para un descifrado exitoso, se debe utilizar el IV del cifrado.

¿Por qué se aplican diferentes IV? Cuando se crea una instancia AES, se genera implícitamente un IV aleatorio. Por lo tanto, dos instancias de AES diferentes significan dos IV diferentes. En el código publicado, se utilizan diferentes instancias de AES para el cifrado y el descifrado. Aunque la referencia encAlg utilizada en el descifrado tiene el mismo nombre que el del cifrado, la instancia a la que se hace referencia es diferente (es decir, una instancia recién creada durante el descifrado). Esto es diferente en el ejemplo de Microsoft. Allí, el IV del cifrado se utiliza en el descifrado: decAlg.IV = encAlg.IV, donde encAlg es la instancia AES con la que se realizó el cifrado.

La solución es almacenar el IV del cifrado en el archivo para que pueda utilizarse en el descifrado. El IV no es secreto y generalmente se coloca antes del texto cifrado:

Cambios necesarios en EncryptFile:

...
byte[] utfD1 = new System.Text.UTF8Encoding(false).GetBytes(JsonFile); 

encryptionStream.Write(encAlg.IV, 0, encAlg.IV.Length);           // Write the IV
encryptionStream.Flush();
            
encrypt.Write(utfD1, 0, utfD1.Length);
...
            

Cambios necesarios en DecryptFile:

...
edata1 = System.IO.File.ReadAllBytes(SecureFile); 
            
byte[] iv = new byte[16];                                         // Separate IV and ciphertext
byte[] ciphertext = new byte[edata1.Length - iv.Length];
Array.Copy(edata1, 0, iv, 0, iv.Length);
Array.Copy(edata1, iv.Length, ciphertext, 0, ciphertext.Length);
...     
Aes encAlg = Aes.Create();                                        // Remove this line
...
decAlg.IV = iv;                                                   // Use the separated IV
...
decrypt.Write(ciphertext, 0, ciphertext.Length);                  // Use the separated ciphertext

Algunas observaciones:

  • Para cada cifrado, se debe generar una nueva sal aleatoria y concatenarla con el texto cifrado análogo al IV. Durante el descifrado, la sal se puede determinar de forma análoga a IV. Considere además RFC8018, sección 4.1.
  • El recuento de iteraciones ralentiza la derivación de la clave, lo que debería dificultar un ataque por intentos repetidos. Por lo tanto, el valor debe ser lo más grande posible. Considere además RFC8018, sección 4.2.
  • Los datos de autenticación (es decir, las contraseñas) no están cifrados, sino hash, aquí.
2
Topaco 31 jul. 2020 a las 18:19