Search code examples
c#encryptioncryptographyaes

AES Encryption using C#


I'm a newbie in cryptography and to learn it I tried to encrypt/decrypt with AES in C#.

Sadly I realized, that it isn't as easy as I thought. So I was looking for a simpler solution.

Later I found a couple of code snippets including some explanation.

I copied the code and tried to implement it into a small application.

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

namespace aes
{
    class Program
    {

        public static string passwd = null;
        public static string content = null;
        public static string encryptedcontent = null;

        public static byte[] IV = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
        public static int BlockSize = 128;


        static void Encrypt()
        {

            if (passwd == "") return;
            //Content to Byte Array
            byte[] bytes = Encoding.Unicode.GetBytes(content);
            //Encrypt
            //Init AES
            SymmetricAlgorithm crypt = Aes.Create();
            //Init md5 hash
            HashAlgorithm hash = MD5.Create();
            //AES blocksize (AES 192 etc.) (min 128)
            crypt.BlockSize = BlockSize;
            //Generating Key
            crypt.Key = hash.ComputeHash(Encoding.Unicode.GetBytes(passwd));
            //Initialize Vectors
            crypt.IV = IV;

            //CryptoStram is used for encryption
            //The required Encryptor is based on the algorithm above
            //Cryptostream sends data of the encrypted byte array to Memorystream
            //The memory stream is then converted into a Base64 string and made readable
            using (MemoryStream memoryStream = new MemoryStream())
            {
                using (CryptoStream cryptoStream =
                   new CryptoStream(memoryStream, crypt.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    cryptoStream.Write(bytes, 0, bytes.Length);
                }

                encryptedcontent = Convert.ToBase64String(memoryStream.ToArray());
            }
        }


        static void Main(string[] args)
        {
//Set Password
            Console.WriteLine("Passwort angeben");
            Console.Write("> ");
                 passwd = Console.ReadLine();

//Set content to encrypt (String)
            Console.WriteLine("Zu verschlüsselner Text angeben");
            Console.Write("> ");
                 content = Console.ReadLine();


            Encrypt();

            Console.WriteLine(encryptedcontent);

            Console.ReadLine();
        }
    }
}

Subsequently I wanted to try the programm with some testdata. I actually got a seemingly encrypted string.

PW: supersecretpassword Content: I like to keep my secrets Result: SEEc1sLMIyfVFsoHPFRIcl437+yjUC5uFMgco3iO+oWSgJWQOwKhoDhUbFJREeqiIvaY2DBR+Ih4OJeGAc6JZQ==

I tried to use some online tools to decrypt and check my result. Sadly most of the Webtools were not able to decrypt my result.

And if I encrypt the sentence I like to keep my secrets with that online tools I get results like: 7IWuebm0T8HdrGdtkBjt5zgjbdEqYfidNZVvfgtOjH4=

My result SEEc1sLMIyfVFsoHPFRIcl437+yjUC5uFMgco3iO+oWSgJWQOwKhoDhUbFJREeqiIvaY2DBR+Ih4OJeGAc6JZQ==

As you can see, the two results are different. Unfortunately I have no idea why this could be the case.

Thanks for you help

Jonas

P.S Somehow I deleted some of rows written in this question. I hope the new words can clarify what my problem is.


Solution

  • You don't say what online tools did, or did not, succeed in replicating your results, so this is a general answer, instead of specific.

    //AES blocksize (AES 192 etc.) (min 128)
    
    crypt.BlockSize = BlockSize;
    

    The BlockSize of AES is 128. Always (contrast with the original algorithm, Rijndael, which allows the BlockSize to change).

    AES-128/AES-192/AES-256 are about the KeySize, not the BlockSize.

    crypt.Key = hash.ComputeHash(Encoding.Unicode.GetBytes(passwd));
    

    You're using MD5(UTF16(password)) as your Key Deriviation Function (KDF). Maybe you can find an online sample that is using this, but they're more likely to be using MD5(UTF8(password)) (which would come from Encoding.UTF8, vs Encoding.Unicode). A better answer would be to use a proper password-based Key Derivation Function, like PBKDF2 (which is called Rfc2898DeriveBytes in .NET for... reasons).

    [When I encrypt I like to keep my secrets I get an answer that is twice as long as online tools.]

    You're encrypting the UTF-16 representation of that string. The string is comprised of 25 Unicode codepoint values, all from the US-ASCII range. Therefore the UTF-16 representation is just the codepoint length * 2 (50 bytes).

    50 bytes breaks down into 3 16-byte (128-bit) blocks, plus 2 bytes left over. Add padding, that becomes 4 blocks of AES-CBC-PKCS#7 output (64 bytes). 64 bytes converts to Base64 as 21 full values (of 3 bytes -> 4 chars) with 1 byte remaining, so the Base64 value ends in 2 = padding characters with a total length of 88 characters. This matches your description, hooray :).

    If, on the other hand, you used the UTF-8 encoding, you'd have 25 bytes into encryption, which becomes 2 blocks of output (32 bytes) which turns into 10 full base64 conversions with 2 bytes remaining, so one = at a total of 44 characters... which looks a lot like what the online tools are using.

    You also should produce a new IV for every time you encrypt with the same key. The IV isn't a key, but changing the IV causes the same secret input to get encrypted differently, so someone who can see your encrypted data can't tell that you sent the same message that you just sent. (At least, that's the purpose in CBC block mode, in other block modes it has sometimes more important purposes). The IV can be transmitted with the message... in fact it should be, unless you have some other way of both sides agreeing (without hard-coding it).

    And, of course, you should dispose all of your disposable objects. Changing your encoding to UTF-8, but not changing your KDF, would better be

    private static string Encrypt(string content, string password)
    {
        byte[] bytes = Encoding.UTF8.GetBytes(content);
    
        using (SymmetricAlgorithm crypt = Aes.Create())
        using (HashAlgorithm hash = MD5.Create())
        using (MemoryStream memoryStream = new MemoryStream())
        {
            crypt.Key = hash.ComputeHash(Encoding.UTF8.GetBytes(password));
            // This is really only needed before you call CreateEncryptor the second time,
            // since it starts out random.  But it's here just to show it exists.
            crypt.GenerateIV();
        
            using (CryptoStream cryptoStream = new CryptoStream(
                memoryStream, crypt.CreateEncryptor(), CryptoStreamMode.Write))
            {
                cryptoStream.Write(bytes, 0, bytes.Length);
            }
        
            string base64IV = Convert.ToBase64String(crypt.IV);
            string base64Ciphertext = Convert.ToBase64String(memoryStream.ToArray());
    
            return base64IV + "!" + base64Ciphertext;
        }
    }