Search code examples
c#encryptionaes

C# - Problem with AES Decryption - always get null


I am trying to implement image steganography with LSB and everything works except decrypting.

There is my class responsible for encryption and decryption of strings below. Encrypting works fine but Decrypt method always returns null:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace WindowsFormsApp1
{
   class Encryptor {
    //text to encrypt or already decrypted
    private String decryptedText = "";
    //text to decrypt or already encrypted
    private String encryptedText = "";
    private String key = "";

    public Encryptor setDecryptedText(String text)
    {
        decryptedText = text;

        return this;
    }

    public Encryptor setEncryptedText(String text)
    {
        encryptedText = text;

        return this;
    }
    public Encryptor setKey(String text)
    {
        key = text;

        return this;
    }

    Byte[] getHash(Byte[] hash)
    {
        Byte[] newHash = new Byte[32];
        for (int i = 0; i < 32; i++)
        {
            newHash[i] = hash[i];
        }

        return newHash;
    }

    Byte[] getIV(Byte[] hash)
    {
        Byte[] newHash = new Byte[16];
        int j = 0;
        for (int i = 32; i < 48; i++)
        {
            newHash[j++] = hash[i];
        }

        return newHash;
    }

    String EncryptAesManaged()
    {
        SHA512 shaM = new SHA512Managed();

        Byte[] data = Encoding.UTF8.GetBytes(key);
        Byte[] hash = shaM.ComputeHash(data);

        try
        {
            return Encrypt(decryptedText, getHash(hash), getIV(hash));
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }

        return null;
    }

    String DecryptAesManaged()
    {
        SHA512 shaM = new SHA512Managed();
        var data = Encoding.UTF8.GetBytes(key);
        Byte[] hash = shaM.ComputeHash(data);
        try
        {
            return Decrypt(Convert.FromBase64String(encryptedText), getHash(hash), getIV(hash));
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        return "";
    }

    String Encrypt(string plainText, byte[] Key, byte[] IV)
    {
        Byte[] encrypted;
        using (RijndaelManaged aes = new RijndaelManaged())
        {
            aes.Mode = CipherMode.CBC;
            aes.BlockSize = 128;
            aes.KeySize = 256;
            ICryptoTransform encryptor = aes.CreateEncryptor(Key, IV);
            using (MemoryStream ms = new MemoryStream())
            {
                using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                {
                    using (StreamWriter sw = new StreamWriter(cs)) { 
                        sw.Write(Encoding.UTF8.GetBytes(plainText));
                        cs.FlushFinalBlock();
                        encrypted = ms.ToArray();
                    }
                }
            }
            aes.Clear();
        }

        return  Convert.ToBase64String(encrypted);
    }
    string Decrypt(byte[] cipherText, byte[] Key, byte[] IV)
    {
        string plaintext = null;
        using (RijndaelManaged aes = new RijndaelManaged())
        {
            aes.Mode = CipherMode.CBC;
            aes.BlockSize = 128;
            aes.KeySize = 256;
            ICryptoTransform decryptor = aes.CreateDecryptor(Key, IV);
            try
            {
                using (MemoryStream ms = new MemoryStream(cipherText))
                using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
                using (StreamReader reader = new StreamReader(cs))
                {
                    plaintext = reader.ReadToEnd(); //Here get null
                }

                aes.Clear();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        return plaintext;
    }

    public String getEncrypted()
    {
         return EncryptAesManaged();

    }

    public String getDecrypted()
    {
         return DecryptAesManaged();

    }
  }
}

Why is Decrypt() returning null rather than the originally encrypted string?


Solution

  • You don't show how you use your Encryptor class, so your question doesn't quite include a Minimal, Complete, and Verifiable example. I was able to reproduce the problem with the following test harness:

    public static void Test()
    {
        var key = "my key";
        var plainText = "hello";
    
        var encryptor = new Encryptor();
    
        encryptor.setDecryptedText(plainText);
        encryptor.setKey(key);
    
        var encrypted = encryptor.getEncrypted();
    
        Console.WriteLine(encrypted);
    
        var deecryptor = new Encryptor();
    
        deecryptor.setEncryptedText(encrypted);
        deecryptor.setKey(key);
    
        var decrypted = deecryptor.getDecrypted();
    
        Console.WriteLine(decrypted);
    
        Assert.IsTrue(plainText == decrypted);
    }
    

    Demo fiddle #1 here.

    Given that, your code has 2 problems, both of which are actually in encryption rather than decryption.

    Firstly, in Encrypt(string plainText, byte[] Key, byte[] IV), you are writing to the StreamWriter sw, then flushing the CryptoStream and returning the MemoryStream contents -- but you never flush or dispose sw, so its buffered contents are never forwarded to the underlying stream(s).

    To fix this, your code should looks something like:

    using (MemoryStream ms = new MemoryStream())
    {
        using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
        {
            using (StreamWriter sw = new StreamWriter(cs)) 
            { 
                sw.Write(Encoding.UTF8.GetBytes(plainText));
            }
        }
        encrypted = ms.ToArray();
    }
    

    Now getDecrypted() no longer returns a null result -- but instead returns a wrong result of "System.Byte[]", as shown in demo fiddle #2 here.

    Secondly, again in Encrypt(...), you are effectively encoding your plainText twice at this line:

    sw.Write(Encoding.UTF8.GetBytes(plainText));
    

    Encoding.UTF8.GetBytes(plainText) converts the plain text to a byte array, but the StreamWriter is also intended to do this job, converting strings to bytes and passing them to the underlying stream. So, since you are not passing a string to Write(), the overload that gets called is StreamWriter.Write(Object):

    Writes the text representation of an object to the text string or stream by calling the ToString() method on that object.

    Thus what actually gets encrypted is the ToString() value of a byte array, which is "System.Byte[]".

    To fix this, simply remove the call to Encoding.UTF8.GetBytes(plainText) and write the string directly. Thus your Encrypt() method should now look like:

    static String Encrypt(string plainText, byte[] Key, byte[] IV)
    {
        string encrypted;
        using (var aes = new RijndaelManaged())
        {
            aes.Mode = CipherMode.CBC;
            aes.BlockSize = 128;
            aes.KeySize = 256;
            ICryptoTransform encryptor = aes.CreateEncryptor(Key, IV);
            using (var ms = new MemoryStream())
            {
                using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write, true))
                {
                    using (var sw = new StreamWriter(cs)) 
                    { 
                        sw.Write(plainText);
                    }
                }                   
                // Calling GetBuffer() avoids the extra allocation of ToArray().
                encrypted = Convert.ToBase64String(ms.GetBuffer(), 0, checked((int)ms.Length)); 
            }
            aes.Clear();
        }
    
        return encrypted;
    }
    

    Demo fiddle #3 here that now passes successfully.

    Disclaimer: this answer does not attempt to to review your code for security best practices such as secure setup of salt and IV.