Search code examples
c#.netsecuritycryptographyencryption-symmetric

Initializing SymmetricAlgorithm with valid values


I do not have a security background and the most I've dealt with cryptography is using snippets picked up from the net. After some reading up, it is clear that I need a better understanding of how these algorithms work. So please bear with any naivety.

I've tried to implement some generic/abstract methods that expect a fully initialized instance of SymmetricAlgorithm. As far as I can see, the transform methods are implemented fine while the calling code is not. I get exceptions as below among others:

The input data is not a complete block.
Length of the data to decrypt is invalid.

USAGE:

using System;
using System.Security.Cryptography;
using System.Text;

public class Program
{
    private static void Main (string [] args)
    {
        var cycled = "";
        Exception exception = null;
        var output = new byte [] { };
        var encoding = Encoding.UTF8;
        var source = "Dude, where's my car?!";

        var algorithm = RijndaelManaged.Create();
        algorithm.GenerateIV();
        algorithm.GenerateKey();
        algorithm.Mode = CipherMode.CBC; // CipherMode.OFB;

        // I've tried multiple combinations of algorithms and modes, which all result in exceptions.

        if (SecurityUtilities.Encrypt(source, encoding, algorithm, out output, out exception))
        {
            if (SecurityUtilities.Decrypt(output, encoding, algorithm, out cycled, out exception))
            {
                var r = cycled == source;
                Console.Write("{0} = {1} == {2}.", r, source, cycled);
            }
            else
            {
                Console.Write(exception);
            }
        }
        else
        {
            Console.Write(exception);
        }

        Console.ReadKey();
    }
}

GENERIC CODE:

using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

public static class SecurityUtilities
{
    public static bool Encrypt (string source, Encoding encoding, SymmetricAlgorithm algorithm, out string output, out Exception exception)
    {
        var result = false;

        output = "";
        exception = null;

        if (source == null) { throw (new ArgumentNullException("source")); }
        if (encoding == null) { throw (new ArgumentNullException("encoding")); }
        if (algorithm == null) { throw (new ArgumentNullException("algorithm")); }

        try
        {
            var bytesEncrypted = new byte [] { };
            var bytesSource = encoding.GetBytes(source);

            if (SecurityUtilities.Encrypt(bytesSource, algorithm, out bytesEncrypted, out exception))
            {
                output = encoding.GetString(bytesEncrypted);

                result = true;
            }
        }
        catch (Exception e)
        {
            exception = e;
        }

        return (result);
    }

    public static bool Encrypt (string source, Encoding encoding, SymmetricAlgorithm algorithm, out byte [] output, out Exception exception)
    {
        var result = false;

        output = null;
        exception = null;

        if (source == null) { throw (new ArgumentNullException("source")); }
        if (encoding == null) { throw (new ArgumentNullException("encoding")); }
        if (algorithm == null) { throw (new ArgumentNullException("algorithm")); }

        try
        {
            var bytesEncrypted = new byte [] { };
            var bytesSource = encoding.GetBytes(source);

            if (SecurityUtilities.Encrypt(bytesSource, algorithm, out bytesEncrypted, out exception))
            {
                output = bytesEncrypted;

                result = true;
            }
        }
        catch (Exception e)
        {
            exception = e;
        }

        return (result);
    }

    public static bool Encrypt (byte [] source, SymmetricAlgorithm algorithm, out byte [] output, out Exception exception)
    {
        var result = false;

        output = null;
        exception = null;

        if (source == null) { throw (new ArgumentNullException("source")); }
        if (algorithm == null) { throw (new ArgumentNullException("algorithm")); }

        try
        {
            using (var memoryStream = new MemoryStream())
            {
                using (var transform = algorithm.CreateEncryptor())
                {
                    using (var cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write))
                    {
                        cryptoStream.Write(source, 0, source.Length);
                    }
                }

                output = memoryStream.ToArray();
            }

            result = true;
        }
        catch (Exception e)
        {
            exception = e;
        }

        return (result);
    }

    public static bool Decrypt (string source, Encoding encoding, SymmetricAlgorithm algorithm, out string output, out Exception exception)
    {
        var result = false;

        output = "";
        exception = null;

        if (source == null) { throw (new ArgumentNullException("source")); }
        if (encoding == null) { throw (new ArgumentNullException("encoding")); }
        if (algorithm == null) { throw (new ArgumentNullException("algorithm")); }

        try
        {
            var bytesDecrypted = new byte [] { };
            var bytesSource = encoding.GetBytes(source);

            if (SecurityUtilities.Decrypt(bytesSource, algorithm, out bytesDecrypted, out exception))
            {
                output = encoding.GetString(bytesDecrypted);

                result = true;
            }
        }
        catch (Exception e)
        {
            exception = e;
        }

        return (result);
    }

    public static bool Decrypt (byte [] source, Encoding encoding, SymmetricAlgorithm algorithm, out string output, out Exception exception)
    {
        var result = false;

        output = "";
        exception = null;

        if (source == null) { throw (new ArgumentNullException("source")); }
        if (encoding == null) { throw (new ArgumentNullException("encoding")); }
        if (algorithm == null) { throw (new ArgumentNullException("algorithm")); }

        try
        {
            var bytesSource = source;
            var bytesDecrypted = new byte [] { };

            if (SecurityUtilities.Decrypt(bytesSource, algorithm, out bytesDecrypted, out exception))
            {
                output = encoding.GetString(bytesDecrypted);

                result = true;
            }
        }
        catch (Exception e)
        {
            exception = e;
        }

        return (result);
    }

    public static bool Decrypt (byte [] source, SymmetricAlgorithm algorithm, out byte [] output, out Exception exception)
    {
        var result = false;

        output = null;
        exception = null;

        if (source == null) { throw (new ArgumentNullException("source")); }
        if (algorithm == null) { throw (new ArgumentNullException("algorithm")); }

        try
        {
            using (var memoryStream = new MemoryStream(source))
            {
                using (var transform = algorithm.CreateDecryptor())
                {
                    using (var cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Read))
                    {
                        cryptoStream.Read(source, 0, source.Length);
                    }
                }

                output = memoryStream.ToArray();
            }

            result = true;
        }
        catch (Exception e)
        {
            exception = e;
        }

        return (result);
    }
}

The core question is, are there mistakes in the Encrypt/Decrypt methods and if not, what is the bare minimum change required in the calling code to get it to work?


Solution

  • The two errors you mention are the result of the same issue - many symmetric algorithms are "block ciphers", i.e. they work on one or more blocks of a specific size. When encrypting arbitrary length data with a block cipher, it must be padded so that its length is a multiple of the block size, similarly when decrypting, the padding must be removed to give the original, un-padded plaintext.

    There are a number of different ways to pad the plaintext, and the method used is determined by the Padding property of the SymmetricAlgorithm. If this is set to PaddingMode.None, then the length of the plaintext must be a multiple of BlockSize.

    If we assume the SymmetricAlgorithm instance is set to use a padding mode other than None, then it's necessary to tell the algorithm that you're done encrypting and it should add the padding (or when decrypting that you've read all the ciphertext and it should remove the padding). When using the CryptoStream this is achieved using the FlushFinalBlock() method, and should be performed immediately after all of the data has been written to the stream:

    using (var memoryStream = new MemoryStream())
    {
        using (var transform = algorithm.CreateEncryptor())
        {
            using (var cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write))
            {
                cryptoStream.Write(source, 0, source.Length);
                // Tell the CryptoStream we've written all the data and padding should be applied
                cryptoStream.FlushFinalBlock();
            }
        }
        output = memoryStream.ToArray();
    }
    

    In your Decrypt(byte[], SymmetricAlgorithm, ...) you use memoryStream.ToArray() as your output, however memoryStream contains ciphertext, not the resulting plaintext. You should instead be able to use the same construct as for encryption, except using transform.CreateDecryptor() instead of transform.CreateEncryptor():

    using (var memoryStream = new MemoryStream())
    {
        using (var transform = algorithm.CreateDecryptor())
        {
            using (var cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write))
            {
                cryptoStream.Write(source, 0, source.Length);
                // Tell the CryptoStream we've written all the data and padding should be removed
                cryptoStream.FlushFinalBlock();
            }
        }
        output = memoryStream.ToArray();
    }
    

    Finally, in your Encrypt (string, Encoding, ...) you're taking the binary ciphertext and converting it to a string using encoding.GetString(), which is not valid. If you wish to represent binary data as a string, you should use something like Convert.ToBase64String() instead. Similarly in Decrypt(string, Encoding, ...), you should use Convert.FromBase64String() to convert the incoming Base64 encoded ciphertext string into the byte array required for decryption.