AES 256 block size only part of the string is decrypted

Viewed 141

I am trying to implement a .net core version of our existing encryption mechanism. my existing encryption is implemented in the below way which is not compatible with .net core

 byte[] bytesToBeDecrypted = Convert.FromBase64String(dataToDecrypt);
        byte[] saltBytes = new byte[16];

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

                var key = new Rfc2898DeriveBytes(GetHashKeyWithSHA256(), saltBytes, 1000);
                AES.Key = key.GetBytes(AES.KeySize / 8);
                AES.IV = key.GetBytes(AES.BlockSize / 8);

                AES.Mode = CipherMode.CBC;
                AES.Padding = PaddingMode.PKCS7;

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

But there is no direct method to implement this into .net core so i have used AES but it is giving always a portion of expected output

 var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
       
        byte[] saltBytes = new byte[16];
       
        var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
        
        var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray();

        using (var password = new Rfc2898DeriveBytes(GetHashKeyWithSHA256(), saltBytes, DerivationIterations))
        {
            var keyBytes = password.GetBytes(Keysize / 8);
            var engine = new RijndaelEngine(256);
            var blockCipher = new CbcBlockCipher(engine);
            var cipher = new PaddedBufferedBlockCipher(blockCipher, new Pkcs7Padding());
            var keyParam = new KeyParameter(keyBytes);
            var keyParamWithIV = new ParametersWithIV(keyParam, ivStringBytes, 0, 32);

            cipher.Init(false, keyParamWithIV);
            var comparisonBytes = new byte[cipher.GetOutputSize(cipherTextBytes.Length)];
            var length = cipher.ProcessBytes(cipherTextBytes, comparisonBytes, 0);

            cipher.DoFinal(comparisonBytes, length);
             var nullIndex = comparisonBytes.Length - 1;
            while (comparisonBytes[nullIndex] == (byte)0)
                nullIndex--;
            comparisonBytes = comparisonBytes.Take(nullIndex + 1).ToArray();

            var result = Encoding.UTF8.GetString(comparisonBytes, 0, comparisonBytes.Length);

            return result;
        }


 private static byte[] GetHashKeyWithSHA256()
    {
        string key = "2893562938q562bdx3whegujfgwehjgfewygfr3287t4238rb2332r7y234723byr54h";
        byte[] passwordBytes = Encoding.UTF8.GetBytes(key);

        return SHA256.Create().ComputeHash(passwordBytes);
    }

Test Input : SxXrRvUEqYvRATVswgrJVQ9EtS6AHJVo6wV+mhQpW+t9a0GPDxvatC9JUYEJ5z/vrPD0MBxyW3dBfrlgec2LqA==

Appreciate if somebody helps me to identify the issue

3 Answers

In the 2nd snippet there are the following issues:

  • dataToDecrypt contains only the ciphertext but not salt and IV. Therefore no separation is necessary.
  • Key and (especially) IV must be derived using the key derivation as in the 1st snippet.
  • The returned length in the DoFinal() call must be taken into account and used in the later GetString() call.

A possible implementation is:

using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Paddings;
using Org.BouncyCastle.Crypto.Parameters;
using System;
using System.Security.Cryptography;
using System.Text;

...

string dataToDecrypt = "SxXrRvUEqYvRATVswgrJVQ9EtS6AHJVo6wV+mhQpW+t9a0GPDxvatC9JUYEJ5z/vrPD0MBxyW3dBfrlgec2LqA==";

var cipherTextBytes = Convert.FromBase64String(dataToDecrypt);
byte[] saltBytes = new byte[16];
int DerivationIterations = 1000;

using (var key = new Rfc2898DeriveBytes(GetHashKeyWithSHA256(), saltBytes, DerivationIterations))
{
    var keyBytes = key.GetBytes(256 / 8);
    var ivBytes = key.GetBytes(256 / 8);
    var engine = new RijndaelEngine(256);
    var blockCipher = new CbcBlockCipher(engine);
    var cipher = new PaddedBufferedBlockCipher(blockCipher, new Pkcs7Padding());
    var keyParam = new KeyParameter(keyBytes);
    var keyParamWithIV = new ParametersWithIV(keyParam, ivBytes, 0, 32);

    cipher.Init(false, keyParamWithIV);
    var comparisonBytes = new byte[cipher.GetOutputSize(cipherTextBytes.Length)];
    var length = cipher.ProcessBytes(cipherTextBytes, comparisonBytes, 0);
    length += cipher.DoFinal(comparisonBytes, length);

    Console.WriteLine(Encoding.UTF8.GetString(comparisonBytes, 0, length)); // hello .thanks for helping me to resolve the issue
}

With this a decryption is possible with the posted test data: hello .thanks for helping me to resolve the issue


Keep in mind that a static salt is insecure. Instead, a random salt should be generated for each encryption. The salt is not secret and is passed along with the ciphertext to the decrypting side, typically concatenated.


Edit:

As requested in the comment, the related encryption is:

string dataToEncrypt = "hello .thanks for helping me to resolve the issue ";

var plainTextBytes = Encoding.UTF8.GetBytes(dataToEncrypt);
byte[] saltBytes = new byte[16];
int DerivationIterations = 1000;

using (var key = new Rfc2898DeriveBytes(GetHashKeyWithSHA256(), saltBytes, DerivationIterations))
{
    var keyBytes = key.GetBytes(256 / 8);
    var ivBytes = key.GetBytes(256 / 8);
    var engine = new RijndaelEngine(256);
    var blockCipher = new CbcBlockCipher(engine);
    var cipher = new PaddedBufferedBlockCipher(blockCipher, new Pkcs7Padding());
    var keyParam = new KeyParameter(keyBytes);
    var keyParamWithIV = new ParametersWithIV(keyParam, ivBytes, 0, 32);

    cipher.Init(true, keyParamWithIV);
    var ciphertextBytes = new byte[cipher.GetOutputSize(plainTextBytes.Length)];
    var length = cipher.ProcessBytes(plainTextBytes, ciphertextBytes, 0);
    cipher.DoFinal(ciphertextBytes, length);

    Console.WriteLine(Convert.ToBase64String(ciphertextBytes)); // SxXrRvUEqYvRATVswgrJVQ9EtS6AHJVo6wV+mhQpW+t9a0GPDxvatC9JUYEJ5z/vrPD0MBxyW3dBfrlgec2LqA==
}

You can try this:

private const string Key = "ffs-Jks*ca@cyY<xHR][Ycx{dDL,B_nJeOgFa'G_q^Hv.yDWq.dsbE'1af^xdeP";

private static readonly byte[] IV = { 11, 123, 1, 44, 78, 22, 54, 12 };

#endregion

#region methods

/// <summary>
/// Creates a symmetric decryptor object with the current Key property and initialization vector (IV).
/// </summary>
/// <param name="cipherText">The encrypted string.</param>
/// <returns>The Plaintext of an encrypted string.</returns>
public static string Decrypt(string cipherText)
{
    try
    {
        // Check arguments.
        if (cipherText == null || cipherText.Length <= 0)
        {
            //TODO:LogErrors.Error(new ArgumentNullException("cipherText"));
            return null;
        }
        if (Key == null || Key.Length <= 0)
        {
            //TODO:LogErrors.Error(new ArgumentNullException("Key"));
            return null;
        }
        if (IV == null || IV.Length <= 0)
        {
            //TODO:LogErrors.Error(new ArgumentNullException("IV"));
            return null;
        }
        cipherText = cipherText.Replace(" ", "+");
        var cipherBytes = Convert.FromBase64String(cipherText);
        using var encryptor = Aes.Create();
        var pdb = new Rfc2898DeriveBytes(Key, IV);
        encryptor.Mode = CipherMode.CBC;
        encryptor.KeySize = 128;
        encryptor.BlockSize = 128;
        encryptor.FeedbackSize = 128;
        encryptor.Padding = PaddingMode.PKCS7;
        encryptor.Key = pdb.GetBytes(32);
        encryptor.IV = pdb.GetBytes(16);
        using var ms = new MemoryStream();
        using (var cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write))
        {
            cs.Write(cipherBytes, 0, cipherBytes.Length);
            cs.Close();
        }
        cipherText = Encoding.Unicode.GetString(ms.ToArray());
        cipherText = cipherText.TrimEnd(char.Parse("\0"));
        return cipherText;
    }
    catch (ArgumentNullException ex)
    {
        //TODO:LogErrors.Error(ex);
    }
    catch (ArgumentException ex)
    {
        //TODO:LogErrors.Error(ex);
    }
    catch (FormatException ex)
    {
        //TODO:LogErrors.Error(ex);
    }
    catch (CryptographicException ex)
    {
        //TODO:LogErrors.Error(ex);
    }
    catch (NotSupportedException ex)
    {
        //TODO:LogErrors.Error(ex);
    }
    return null;
}

/// <summary>
/// Creates a symmetric encryptor object with the current Key property and initialization vector (IV).
/// </summary>
/// <param name="clearText">The plaintext or string to be encrypted.</param>
/// <returns>The encrypted string.</returns>
public static string Encrypt(string clearText)
{
    try
    {
        // Check arguments.
        if (clearText == null || clearText.Length <= 0)
        {
            //TODO:LogErrors.Error(new ArgumentNullException("plainText"));
            return null;
        }
        if (Key == null || Key.Length <= 0)
        {
            //TODO:LogErrors.Error(new ArgumentNullException("Key"));
            return null;
        }
        if (IV == null || IV.Length <= 0)
        {
            //TODO:LogErrors.Error(new ArgumentNullException("IV"));
            return null;
        }
        var clearBytes = Encoding.Unicode.GetBytes(clearText);
        using var encryptor = Aes.Create();
        var pdb = new Rfc2898DeriveBytes(Key, IV);
        encryptor.Mode = CipherMode.CBC;
        encryptor.KeySize = 128;
        encryptor.BlockSize = 128;
        encryptor.FeedbackSize = 128;
        encryptor.Padding = PaddingMode.PKCS7;
        encryptor.Key = pdb.GetBytes(32);
        encryptor.IV = pdb.GetBytes(16);
        using var ms = new MemoryStream();
        using (var cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write))
        {
            cs.Write(clearBytes, 0, clearBytes.Length);
            cs.Close();
        }
        clearText = Convert.ToBase64String(ms.ToArray());
        return clearText;
    }
    catch (EncoderFallbackException ex)
    {
        //TODO:LogErrors.Error(ex);
    }
    catch (ArgumentNullException ex)
    {
        //TODO:LogErrors.Error(ex);
    }
    catch (ArgumentException ex)
    {
        //TODO:LogErrors.Error(ex);
    }
    catch (CryptographicException ex)
    {
        //TODO:LogErrors.Error(ex);
    }
    catch (NotSupportedException ex)
    {
        //TODO:LogErrors.Error(ex);
    }
    catch (OverflowException ex)
    {
       //TODO:LogErrors.Error(ex);
    }
    return null;
}

I'd start much closer to your original code with,

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
                    
public static class Crypto
{
    public static string Decrypt(string dataToDecrypt)
    {
        byte[] bytesToBeDecrypted = Convert.FromBase64String(dataToDecrypt);
        byte[] saltBytes = new byte[16];

        using (MemoryStream ms = new MemoryStream())
        {
            using (Aes AES = Aes.Create())
            {
                AES.KeySize = 256;
                AES.BlockSize = 256;

                var key = new Rfc2898DeriveBytes(GetHashKeyWithSHA256(), saltBytes, 1000);
                AES.Key = key.GetBytes(AES.KeySize / 8);
                AES.IV = key.GetBytes(AES.BlockSize / 8);

                AES.Mode = CipherMode.CBC;
                AES.Padding = PaddingMode.PKCS7;

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

First get it work, then improve it.

Related