c# AES Decryption
Asked Answered
I

2

5

I am working with SagePay Forms and currently converting the VB examples they have to c#. I have made good progress so the encryption part of my project works fine (SagePay can decrypt it).

The issue I am having is that when I attempt to decrypt the string, it turns to garbage. If anyone has done this before I would really appreciate some help with my decryption code. I have included the encryption code which works and the first two lines are the setup and call from another method.

I haven't added the VB code but if this is required I could add it. Didn't want a huge post if not required.

Utility Methods:

public string byteArrayToHexString(byte[] ba)
    {
    return BitConverter.ToString(ba).Replace("-", "");
    }

public static byte[] StringToByteArray(string hex)
    {
        return Enumerable.Range(0, hex.Length)
                         .Where(x => x % 2 == 0)
                         .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
                         .ToArray();
    }

Main Encryption Method with first couple of lines being the calling of it extracted from a larger method.

string crypt = "blahblahblah"
string EncryptAndEncode = "@" + byteArrayToHexString(aesEncrypt(crypt));


        private byte[] aesEncrypt(string inputText)
    {

        RijndaelManaged AES = new RijndaelManaged();

        //set the mode, padding and block size for the key
        AES.Padding = PaddingMode.PKCS7;
        AES.Mode = CipherMode.CBC;
        AES.KeySize = 128;
        AES.BlockSize = 128;

        //convert key and plain text input into byte arrays
        Byte[] keyAndIvBytes = UTF8Encoding.UTF8.GetBytes("tR7nR6wZHGjYMCuV");
        Byte[] inputBytes = UTF8Encoding.UTF8.GetBytes(inputText);//AbHLlc5uLone0D1q

        //create streams and encryptor object
        MemoryStream memoryStream = new MemoryStream();
        CryptoStream cryptoStream = new CryptoStream(memoryStream, AES.CreateEncryptor(keyAndIvBytes, keyAndIvBytes), CryptoStreamMode.Write);

        //perform encryption
        cryptoStream.Write(inputBytes, 0, inputBytes.Length);
        cryptoStream.FlushFinalBlock();

        //get encrypted stream into byte array
        Byte[] outBytes = memoryStream.ToArray();

        //close streams
        memoryStream.Close();
        cryptoStream.Close();
        AES.Clear();

        return outBytes;
    }

Decoding and Decrypting methods

public string DecodeAndDecrypt(string strIn)
    {
        //** HEX decoding then AES decryption, CBC blocking with PKCS5 padding - DEFAULT **


        string DecodeAndDecrypt = aesDecrypt(StringToByteArray(strIn.Substring(1)));
        return (DecodeAndDecrypt);
    }

    private string aesDecrypt(Byte[] inputBytes)
    {
    RijndaelManaged AES = new RijndaelManaged();
    Byte[] keyAndIvBytes = UTF8Encoding.UTF8.GetBytes("tR7nR6wZHGjYMCuV");
    Byte[] outputBytes = inputBytes;//Convert.FromBase64String(inputBytes);

    //set the mode, padding and block size
    AES.Padding = PaddingMode.PKCS7;
    AES.Mode = CipherMode.CBC;
    AES.KeySize = 128;
    AES.BlockSize = 128;

    //create streams and decryptor object
    MemoryStream memoryStream = new MemoryStream(outputBytes);
    CryptoStream cryptoStream = new CryptoStream(memoryStream, AES.CreateEncryptor(keyAndIvBytes, keyAndIvBytes), CryptoStreamMode.Read);
    //perform decryption
    cryptoStream.Read(outputBytes, 0, outputBytes.Length);
    Trace.WriteLine(outputBytes);
    //close streams
    memoryStream.Close();
    cryptoStream.Close();
    AES.Clear();
    //return System.Text.Encoding.UTF8.GetString(outputBytes);

    string plainText = Encoding.UTF8.GetString(outputBytes,
                                   0,
                                   outputBytes.Length);

    return plainText;
    }
Incest answered 7/7, 2013 at 10:23 Comment(5)
Where did you find the specs? Perhaps you can include a link to them.Catchascatchcan
If you are doing decryption perhaps you should use AES.CreateDecryptor instead of AES.CreateEncryptorCatchascatchcan
Thanks Greg, your last comment has now resulted in a readable string albeit still some crap at the end for whatever reason. Kind of obvious now I think about it but couldn't see it for looking. I will give it another go to parse the string and see how I get on. Many Thanks, Steve.Incest
Of course you have "crap" at the end. You forget to take in that the plain text is smaller than the ciphertext if it has been padded using the PKCS#7 padding mechanism. You should not disregard the length that is returned by the Read method.Whitleather
You're jumping through unnecessary hoops. You can replace all that crypto stream code by a single call to TransformFinalBlock .Purify
F
10

There are actually multiple problems with your code. First in your decrypt method you're creating an encryptor, that should be a decryptor. Secondly you're reading the entire block including the padding of your algorithm into the buffer when you do the decryption. Below is a class with the items fixed and should be returning the proper result. I do however suggest you find a better way of storing the key, putting in your code and generating it the way you'r edoing it is a no no. You should generate your key with an RNG (RNGCryptoServiceProvider) then hash it with a secure hashing algorithm such as SHA512, use that output for your key. You then need to find a good place to store it, I would look into encrypting your web.config file.

public static class EncryptionHelper
{
    private static byte[] keyAndIvBytes;

    static EncryptionHelper()
    {
        // You'll need a more secure way of storing this, I hope this isn't
        // the real key
        keyAndIvBytes = UTF8Encoding.UTF8.GetBytes("tR7nR6wZHGjYMCuV");
    }

    public static string ByteArrayToHexString(byte[] ba)
    {
        return BitConverter.ToString(ba).Replace("-", "");
    }

    public static byte[] StringToByteArray(string hex)
    {
        return Enumerable.Range(0, hex.Length)
                         .Where(x => x % 2 == 0)
                         .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
                         .ToArray();
    }

    public static string DecodeAndDecrypt(string cipherText)
    {
        string DecodeAndDecrypt = AesDecrypt(StringToByteArray(cipherText));
        return (DecodeAndDecrypt);
    }

    public static string EncryptAndEncode(string plaintext)
    {
        return ByteArrayToHexString(AesEncrypt(plaintext));
    }

    public static string AesDecrypt(Byte[] inputBytes)
    {
        Byte[] outputBytes = inputBytes;

        string plaintext = string.Empty;

        using (MemoryStream memoryStream = new MemoryStream(outputBytes))
        {
            using (CryptoStream cryptoStream = new CryptoStream(memoryStream, GetCryptoAlgorithm().CreateDecryptor(keyAndIvBytes, keyAndIvBytes), CryptoStreamMode.Read))
            {
                using (StreamReader srDecrypt = new StreamReader(cryptoStream))
                {
                    plaintext = srDecrypt.ReadToEnd();
                }
            }
        }

        return plaintext;
    }

    public static byte[] AesEncrypt(string inputText)
    {
        byte[] inputBytes = UTF8Encoding.UTF8.GetBytes(inputText);//AbHLlc5uLone0D1q

        byte[] result = null;
        using (MemoryStream memoryStream = new MemoryStream())
        {
            using (CryptoStream cryptoStream = new CryptoStream(memoryStream, GetCryptoAlgorithm().CreateEncryptor(keyAndIvBytes, keyAndIvBytes), CryptoStreamMode.Write))
            {
                cryptoStream.Write(inputBytes, 0, inputBytes.Length);
                cryptoStream.FlushFinalBlock();

                result = memoryStream.ToArray();
            }
        }

        return result;
    }


    private static RijndaelManaged GetCryptoAlgorithm()
    {
        RijndaelManaged algorithm = new RijndaelManaged();
        //set the mode, padding and block size
        algorithm.Padding = PaddingMode.PKCS7;
        algorithm.Mode = CipherMode.CBC;
        algorithm.KeySize = 128;
        algorithm.BlockSize = 128;
        return algorithm;
    }
}

Calling it is easy:

string crypt = "blahblahblah";
string EncryptAndEncode = EncryptionHelper.EncryptAndEncode(crypt);            
Console.WriteLine(EncryptAndEncode);

Console.WriteLine(EncryptionHelper.DecodeAndDecrypt(EncryptAndEncode));

Console.ReadLine();
Flippant answered 13/8, 2013 at 4:3 Comment(3)
Thanks Moe, the key is in the web.config and will get encrypted at a later stage, I just hardcoded for posting sake. I have it working now and will review against your post to look at improvements.Incest
Using the same IV for each encrypted string is a REALLY bad idea. Especially with smaller block size. If you encrypt two strings longer than 16 bytes but their first 16 bytes are going to be identical the encrypted text is also going to have first 16 bytes identical. That's why people came up with initialization vector (IV) - if you use a random one for each string (and store it alongside the encrypted text) this won't happen.Plummer
@JanHudecek I totally agree. I'm not sure what SagePay does however in terms of the way it decrypts the cipher text, it may not be an option in their protocol. So this example is just to fix the code that was posted. But I definitely agree that you should not use the same IV for the encrypted strings if at all possible.Flippant
S
-1

Key-passing encryption and decryption

using System;

using System.Text;

using System.Security.Cryptography;

public static class Aes256Cbc
{
    private static readonly Encoding encoding = Encoding.UTF8;

    public static string Encrypt(string text, string key)
    {
        try
        {
            var aes = new RijndaelManaged();
            aes.KeySize = 256;
            aes.BlockSize = 128;
            aes.Padding = PaddingMode.PKCS7;
            aes.Mode = CipherMode.CBC;
            aes.Key = SHA256.Create().ComputeHash(encoding.GetBytes(key));
            aes.IV = encoding.GetBytes(key);

            var encrypt = aes.CreateEncryptor(aes.Key, aes.IV);

            var bufferText = encoding.GetBytes(text);
            var encryptedText =
                Convert.ToBase64String(encrypt.TransformFinalBlock(bufferText, 0, bufferText.Length));

            return encryptedText;
        }
        catch (Exception e)
        {
            throw new Exception("Error encrypting: " + e.Message);
        }
    }

    public static string Decrypt(string text, string key)
    {
        try
        {
            var aes = new RijndaelManaged();
            aes.KeySize = 256;
            aes.BlockSize = 128;
            aes.Padding = PaddingMode.PKCS7;
            aes.Mode = CipherMode.CBC;
            aes.Key = SHA256.Create().ComputeHash(encoding.GetBytes(key));
            aes.IV = encoding.GetBytes(key);

            var decrypt = aes.CreateDecryptor(aes.Key, aes.IV);
            var bufferText = Convert.FromBase64String(text);

            return encoding.GetString(decrypt.TransformFinalBlock(bufferText, 0, bufferText.Length));
        }
        catch (Exception e)
        {
            throw new Exception("Error decrypting: " + e.Message);
        }
    }
}

Sherwood answered 19/9 at 6:57 Comment(1)
Thank you for contributing to the Stack Overflow community. This may be a correct answer, but it’d be really useful to provide additional explanation of your code so developers can understand your reasoning. This is especially useful for new developers who aren’t as familiar with the syntax or struggling to understand the concepts. Would you kindly edit your answer to include additional details for the benefit of the community?Munn

© 2022 - 2024 — McMap. All rights reserved.