0

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.

Community
  • 1
  • 1
Victor Stoddard
  • 3,582
  • 2
  • 27
  • 27

1 Answers1

1

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


    }

}
  • Thanks. I upvoted this because I can probably re-work it into a static class. Please correct me if I'm wrong, but it looks as if the ivPassword passed to the RijndaelEncryptor() method may be of any length because the IV it helps create will be only as long as the accepted Rijndael key length. – Victor Stoddard Jul 17 '13 at 22:06
  • 1
    Yes, the [ivPassword] parameter passed in is hashed to a byte array in _stringToBytes() which accepts the [keyByteSize] for the crypto algorithm you are using. The use of PasswordDeriveBytes() here is not ideal but shows how the "plain" password can be hashed to a key for any of the crypto algorithms in NET. Please see the documentation for a more proper use. – rtl_airfoil Jul 28 '13 at 08:59