Search code examples
c#vectorinitializationaesrijndael

C# 2-way Encryption Class - Rijndael possible initialization vector issue (jibberish output)


I am having problems with an encryption class that I made:

public static class Encryption {

    public static string EncryptToString(string TextToEncrypt, byte[] Key, byte[] IV = null)
    {
        return ByteArrToString(EncryptStringToBytes(StrToByteArray(TextToEncrypt), Key, IV));        
    }

    public static string EncryptToString(byte[] BytesToEncrypt, byte[] Key, byte[] IV = null)
    {
        return ByteArrToString(EncryptStringToBytes(BytesToEncrypt, Key, IV));  
    }

    public static byte[] EncryptToBytes(string TextToEncrypt, byte[] Key, byte[] IV = null)
    {
        return EncryptStringToBytes(StrToByteArray(TextToEncrypt), Key, IV);
    }

    public static byte[] EncryptToBytes(byte[] BytesToEncrypt, byte[] Key, byte[] IV = null)
    {
        return EncryptStringToBytes(BytesToEncrypt, Key, IV);
    }
    
    public static string DecryptToString(string EncryptedText, byte[] Key,byte[] IV=null) 
    {
        return ByteArrToString(DecryptStringFromBytes(StrToByteArray(EncryptedText), Key, IV));            
    }

    public static string DecryptToString(byte[] EncryptedBytes, byte[] Key,byte[] IV=null) 
    {
        return ByteArrToString(DecryptStringFromBytes(EncryptedBytes, Key, IV));            
    }

    public static byte[] DecryptToBytes(string EncryptedText, byte[] Key,byte[] IV=null) 
    {
        return DecryptStringFromBytes(StrToByteArray(EncryptedText), Key, IV);            
    }

    public static byte[] DecryptToBytes(byte[] EncryptedBytes, byte[] Key,byte[] IV=null) 
    {
        return DecryptStringFromBytes(EncryptedBytes, Key, IV);            
    }
            
    private static byte[] EncryptStringToBytes(byte[] TextToEncrypt, byte[] Key, byte[] IV=null)
    {
        Debug.WriteLine("Password: " + ByteArrToString(Key));
        Debug.WriteLine("IV: " + ByteArrToString(IV));                        

        byte[] encrypted;
        // Create an Rijndael object 
        // with the specified key and IV. 
        using (Rijndael rijAlg = Rijndael.Create())
        {
            rijAlg.Key = Key;
            rijAlg.IV = IV;

            // Create a decrytor to perform the stream transform.
            ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);

            // Create the streams used for encryption. 
            using (MemoryStream msEncrypt = new MemoryStream())
            {
                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                    {
                        //Write all data to the stream.
                        swEncrypt.Write(TextToEncrypt);
                    }
                    encrypted = msEncrypt.ToArray();
                }
            }
        }
        
        // Return the encrypted bytes from the memory stream. 
        return encrypted;
    }

    private static byte[] DecryptStringFromBytes(byte[] EncryptedText, byte[] Key, byte[] IV)
    {
        Debug.WriteLine("Password: " + ByteArrToString(Key));
        Debug.WriteLine("IV: " + ByteArrToString(IV));

        byte[] fromEncrypt = new byte[EncryptedText.Length];

        // Create a Rijndael object with the specified key and IV. 
        using (Rijndael rijAlg = Rijndael.Create())
        {
            rijAlg.Key = Key;
            rijAlg.IV = IV;

            // Create a decrytor to perform the stream transform.
            ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);

            // Create the streams used for decryption. 
            using (MemoryStream msDecrypt = new MemoryStream(EncryptedText))
            {
                using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                {
                    //read stream into byte array
                    csDecrypt.Read(fromEncrypt,0,fromEncrypt.Length);                        
                }
            }
        }

        return fromEncrypt;
    }
    
    public static byte[] StrToByteArray(string str)
    {
        if (str.Length == 0)
            throw new Exception("Invalid string value in StrToByteArray");

        byte[] bytes = new byte[str.Length * sizeof(char)];
        System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
        return bytes;
    }

    public static string ByteArrToString(byte[] bytes)
    {
        char[] chars = new char[bytes.Length / sizeof(char)];
        System.Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
        return new string(chars);
    }

    public static byte[] GetIV()
    {
        byte[] randomArray = new byte[16];
        RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
        rng.GetBytes(randomArray);
        return randomArray;
    }
}

I test it with the following:

byte[] iv =  Encryption.GetIV();
byte[] password = Encryption.StrToByteArray("password");

string encrypted = Encryption.EncryptToString("Hello", password, iv);
Debug.WriteLine("Result: " + Encryption.DecryptToString(encrypted, password, iv));

This is the result I get in the debug window:

Password: password

IV: 䴞ㆫ튾꛽輔

Password: password

IV: 䴞ㆫ튾꛽輔

Result: 祓瑳浥䈮瑹孥]

I don't get any errors; just a jibberish result.

I don't know if it's a problem with the initialization vector, the stream, or something else that I'm missing.


Solution

  • I believe there are several issues with this code involving string to byte conversions, total crypto length, etc.

    here is a piece of code I have which does effectively the same thing and may get you on your way. I have tested it and it does work as expected.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Diagnostics;
    using System.Security.Cryptography;
    using System.IO;
    
    
    namespace Encryption_test_app
    {
        class Program
        {
            static void Main(string[] args)
            {
                var encoding = new UTF8Encoding(false, true);
                var cryptor = new RijndaelEncryptor();
    
                var plainText = "Hello World!";
                Debug.Print("Plain Text: [{0}]", plainText);
    
                byte[] cypherBytes = cryptor.Encrypt(encoding.GetBytes(plainText));
                string decryptedText = encoding.GetString(cryptor.Decrypt(cypherBytes));
    
                Debug.Print("Decrypted Text: [{0}]", decryptedText);
                Debug.Print("PlainText == Decrypted Text: [{0}]", plainText == decryptedText);
            }
        }
    
    
    
        /// <summary>
        /// Simple class to encrypt/decrypt a byte array using the <see cref="RijndaelManaged"/> cryptographic algorithm.
        /// </summary>
        public class RijndaelEncryptor : IDisposable
        {
    
            private RijndaelManaged _crypt = new RijndaelManaged();
    
            #region Constructors
    
            /// <summary>
            /// Initializes a new instance of the <see cref="RijndaelEncryptor"/> class using a default key and initial vector (IV).
            /// </summary>
            public RijndaelEncryptor() : this("0nce @pon a time...", "There lived a princess who 1iked frogs...") { }
    
            /// <summary>
            /// Initializes a new instance of the <see cref="RijndaelEncryptor"/> class using the plain text key and initial vector
            /// which are used to construct encrypted key and IV values using the maximum allowed key and iv sizes for 
            /// the <see cref="RijndaelEncryptor"/> cryptographic algorithm.
            /// </summary>
            /// <param name="keyPassword"></param>
            /// <param name="ivPassword"></param>
            public RijndaelEncryptor(string keyPassword, string ivPassword) 
            {
                if (string.IsNullOrEmpty(keyPassword)) throw new ArgumentOutOfRangeException("keyPassword", "Cannot be null or empty");
                if (string.IsNullOrEmpty(ivPassword)) throw new ArgumentOutOfRangeException("ivPassword", "Cannot be null or empty");
    
                KeyPassword = keyPassword;
                IVPassword = ivPassword;
    
                _crypt.KeySize = _crypt.LegalKeySizes[0].MaxSize;
    
                EncryptKey = _stringToBytes(KeyPassword, _crypt.KeySize >> 3);
                EncryptIV = _stringToBytes(IVPassword, _crypt.BlockSize >> 3);
    
            }
    
            /// <summary>
            /// Initializes a new instance of the <see cref="RijndaelEncryptor"/> class using the user supplied key and initial vector arrays.
            /// NOTE: these arrays will be validated for use with the <see cref="RijndaelManaged"/> cypher.
            /// </summary>
            /// <param name="encryptedKey"></param>
            /// <param name="encryptedIV"></param>
            public RijndaelEncryptor(byte[] encryptedKey, byte[] encryptedIV)
            {
                if (encryptedKey == null) throw new ArgumentNullException("encryptedKey");
                if (encryptedIV == null) throw new ArgumentNullException("encryptedIV");
    
                //Verify encrypted key length is valid for this cryptor algo.
                int keylen = encryptedKey.Length << 3;
                if (!_crypt.ValidKeySize(keylen))
                {
                    string errmsg = "Encryption key length(" + keylen.ToString() + ") is not for this algorithm:" + _crypt.GetType().Name;
                    throw new ApplicationException(errmsg);
                }
    
                //Verify encrypted iv length is valid for this cryptor algo.
                int len = encryptedIV.Length << 3;
                if (len != _crypt.BlockSize)
                {
                    string errmsg = "Encryption key length(" + len.ToString() + ") is not for this algorithm:" + _crypt.GetType().Name;
                    throw new ApplicationException(errmsg);
                }
    
                EncryptKey = encryptedKey;
                EncryptIV = encryptedIV;
            }
    
            #endregion
    
            /// <summary>
            /// Plain text encryption key. Is used to generate a encrypted key <see cref="EncryptKey"/>
            /// </summary>
            public string KeyPassword { get; private set; }
    
            /// <summary>
            /// Plain text encryption initial vector. Is used to generate a encrypted IV <see cref="EncryptIV"/>
            /// </summary>
            public string IVPassword { get; private set; }
    
            /// <summary>
            /// Encrypted encryption key. (Size must match one of the allowed sizes for this encryption method).
            /// </summary>
            public byte[] EncryptKey { get; private set; }
    
            /// <summary>
            /// Encrypted encryption IV. (Size must match one of the allowed sizes for this encryption method).
            /// </summary>
            public byte[] EncryptIV { get; private set; }
    
    
            /// <summary>
            /// Encrypts the given byte array using the defined <see cref="EncryptKey"/> and <see cref="EncryptIV"/> values.
            /// </summary>
            /// <param name="plaintext"></param>
            /// <returns></returns>
            public byte[] Encrypt(byte[] plaintext)
            {
                return(_encrypt(plaintext, EncryptKey, EncryptIV));
            }
    
            /// <summary>
            /// Decrypts the given byte array using the defined <see cref="EncryptKey"/> and <see cref="EncryptIV"/> values.
            /// </summary>
            /// <param name="cypherBytes"></param>
            /// <returns></returns>
            public byte[] Decrypt(byte[] cypherBytes)
            {
                return (_decrypt(cypherBytes, EncryptKey, EncryptIV));
            }
    
    
            #region Private Encryption methods
    
    
            /// <summary>
            /// Used to encrypt the plain-text key and iv values to not so easy to ready byte arrays of the given size.
            /// </summary>
            /// <param name="password"></param>
            /// <param name="KeyByteSize"></param>
            /// <returns></returns>
            private byte[] _stringToBytes(string password, int KeyByteSize)
            {
                byte[] salt = new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0xfe, 0x00, 0xa7, 0xd3, 0x02, 0x02, 0x97, 0xc4, 0xa5, 0x32 };
                PasswordDeriveBytes b = new PasswordDeriveBytes(password, salt);
                return (b.GetBytes(KeyByteSize));
            }
    
            /// <summary>
            /// Encrypts the <paramref name="plainBytes"/> array using the given key and initial vector.
            /// </summary>
            /// <remarks>
            /// This routine embeds the length of the plain data at the beginning of the encrypted record. This would be 
            /// frowed apon by crypto experts. However, if you dont do this you may get extraneous data (extra null bytes)
            /// at the end of the decrypted byte array. This embedded length is used to trim the final decrypted array to size.
            /// </remarks>
            /// <param name="plainBytes"></param>
            /// <param name="key"></param>
            /// <param name="iv"></param>
            /// <returns></returns>
            private byte[] _encrypt(byte[] plainBytes, byte[] key, byte[] iv)
            {
                try
                {
                    // Create a MemoryStream.
                    using (MemoryStream mStream = new MemoryStream())
                    {
                        // Create a CryptoStream using the MemoryStream 
                        // and the passed key and initialization vector (IV).
                        using (CryptoStream cStream = new CryptoStream(mStream, _crypt.CreateEncryptor(key, iv), CryptoStreamMode.Write))
                        {
    
                            // Write the byte array to the crypto stream and flush it.
                            byte[] recordLen = BitConverter.GetBytes(plainBytes.Length);
                            cStream.Write(recordLen, 0, recordLen.Length);
                            cStream.Write(plainBytes, 0, plainBytes.Length);
    
                            if (!cStream.HasFlushedFinalBlock)
                            {
                                cStream.FlushFinalBlock();
                            }
    
                            // Get an array of bytes from the 
                            // MemoryStream that holds the 
                            // encrypted data.
                            return(mStream.ToArray());
    
                        }
                    }
                }
                catch (CryptographicException ex)
                {
                    throw new ApplicationException("**ERROR** occurred during Encryption", ex);
                }
    
            }
    
            /// <summary>
            /// Decrypts the <paramref name="cryptBytes"/> array using the given key and initial vector.
            /// </summary>
            /// <param name="plainBytes"></param>
            /// <param name="key"></param>
            /// <param name="iv"></param>
            /// <returns></returns>
            private byte[] _decrypt(byte[] cryptBytes, byte[] key, byte[] iv)
            {
                try
                {
                    // Create a new MemoryStream using the passed 
                    // array of encrypted data.
                    using (MemoryStream msDecrypt = new MemoryStream(cryptBytes))
                    {
                        // Create a CryptoStream using the MemoryStream 
                        // and the passed key and initialization vector (IV).
                        using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, _crypt.CreateDecryptor(key, iv), CryptoStreamMode.Read))
                        {
                            byte[] recordLen = BitConverter.GetBytes((int)0);
                            csDecrypt.Read(recordLen, 0, recordLen.Length);
                            int length = BitConverter.ToInt32(recordLen, 0);
    
                            // Create buffer to hold the decrypted data.
                            byte[] fromEncrypt = new byte[cryptBytes.Length - recordLen.Length];
    
                            // Read the decrypted data out of the crypto stream
                            // and place it into the temporary buffer.
                            csDecrypt.Read(fromEncrypt, 0, fromEncrypt.Length);
    
                            byte[] plainBytes = new byte[length];
                            Array.Copy(fromEncrypt, plainBytes, length);
    
                            return (plainBytes);
                        }
                    }
                }
                catch (CryptographicException ex)
                {
                    throw new ApplicationException("**ERROR** occurred during Decryption", ex);
                }
            }
    
            #endregion
    
            #region IDisposable Members
    
            private bool disposed = false;  //indicates if this instance has been disposed.
    
            private void Dispose(bool disposing)
            {
                if (!this.disposed)
                {
                    //Dispose managed objects
                    if (disposing)
                    {
                        if (_crypt != null)
                        {
                            try { _crypt.Clear(); }
                            finally { _crypt = null; }
                        }
                    }
    
                    //Dispose Unmanaged objects
                }
                this.disposed = true;
            }
    
            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }
    
            ~RijndaelEncryptor() { Dispose(false); }
    
            #endregion
    
    
        }
    
    }