2

I was told not to use RSA to encrypt simple text but to use AES. I found a simple piece of code to implement AES:

public static class Crypto
    {
        #region Settings

        private static int _iterations = 2;
        private static int _keySize = 256;

        private static string _hash = "SHA1";
        private static string _salt = "aselrias38490a32"; // Random
        private static string _vector = "8947az34awl34kjq"; // Random

        #endregion

        public static string Encrypt(string value, string password)
        {
            return Encrypt<AesManaged>(value, password);
        }

        public static string Encrypt<T>(string value, string password)
            where T : SymmetricAlgorithm, new()
        {

            byte[] vectorBytes = Encoding.ASCII.GetBytes(_vector);
            byte[] saltBytes = Encoding.ASCII.GetBytes(_salt);
            byte[] valueBytes = Encoding.UTF8.GetBytes(value);

            byte[] encrypted;
            using (T cipher = new T())
            {
                PasswordDeriveBytes _passwordBytes =
                    new PasswordDeriveBytes(password, saltBytes, _hash, _iterations);
                byte[] keyBytes = _passwordBytes.GetBytes(_keySize/8);

                cipher.Mode = CipherMode.CBC;

                using (ICryptoTransform encryptor = cipher.CreateEncryptor(keyBytes, vectorBytes))
                {
                    using (MemoryStream to = new MemoryStream())
                    {
                        using (CryptoStream writer = new CryptoStream(to, encryptor, CryptoStreamMode.Write))
                        {
                            writer.Write(valueBytes, 0, valueBytes.Length);
                            writer.FlushFinalBlock();
                            encrypted = to.ToArray();
                        }
                    }
                }
                cipher.Clear();
            }
            return Convert.ToBase64String(encrypted);
        }

        public static string Decrypt(string value, string password)
        {
            return Decrypt<AesManaged>(value, password);
        }

        public static string Decrypt<T>(string value, string password) where T : SymmetricAlgorithm, new()
        {
            byte[] vectorBytes = Encoding.ASCII.GetBytes(_vector);
            byte[] saltBytes = Encoding.ASCII.GetBytes(_salt);
            byte[] valueBytes = Convert.FromBase64String(value);

            byte[] decrypted;
            int decryptedByteCount = 0;

            using (T cipher = new T())
            {
                PasswordDeriveBytes _passwordBytes = new PasswordDeriveBytes(password, saltBytes, _hash, _iterations);
                byte[] keyBytes = _passwordBytes.GetBytes(_keySize/8);

                cipher.Mode = CipherMode.CBC;

                try
                {
                    using (ICryptoTransform decryptor = cipher.CreateDecryptor(keyBytes, vectorBytes))
                    {
                        using (MemoryStream from = new MemoryStream(valueBytes))
                        {
                            using (CryptoStream reader = new CryptoStream(from, decryptor, CryptoStreamMode.Read))
                            {
                                decrypted = new byte[valueBytes.Length];
                                decryptedByteCount = reader.Read(decrypted, 0, decrypted.Length);
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    return String.Empty;
                }

                cipher.Clear();
            }
            return Encoding.UTF8.GetString(decrypted, 0, decryptedByteCount);
        }

    }

However, this is based on a string coming back and then used to decrypt in the same program. I need to encrypt the following data in a WinForms program and the decrypt in a whole separate Windows Service program:

string fileName = System.IO.Path.Combine(Application.StartupPath, "alphaService.xml");
                XDocument doc = new XDocument();
                XElement xml = new XElement("Info",
                    new XElement("DatabaseServerName", txtServerName.Text),
                    new XElement("DatabaseUserName", txtDatabaseUserName.Text),
                    new XElement("DatabasePassword", txtDatabasePassword.Text),
                    new XElement("ServiceAccount", txtAccount.Text),
                    new XElement("ServicePassword", txtServicePassword.Text),
                    new XElement("RegistrationCode", txtRegistrationCode.Text));

                doc.Add(xml);
                doc.Save(fileName);

                // Convert XML doc to byte stream
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.Load(fileName);

                // byte[] fileBytes = Encoding.Default.GetBytes(xmlDoc.OuterXml);

                string encrypted = Crypto.Encrypt(xmlDoc.OuterXml, "testpass");

How can I do it? Please show sample code.

EDIT: Kevin, I have implemented your algorithm but the problem is I want to generate the key once and save it for use in the other program to decrypt but I need to pass the byte[] to the encrypt function. So I tried converting using System.Text.Encoding.ASCII.GetBytes(key); and it doesn't do it correctly. I have the wrong number of bytes for byte[] for the key.

string fileName = System.IO.Path.Combine(Application.StartupPath, "alphaService.xml");
                XDocument doc = new XDocument();
                XElement xml = new XElement("Info",
                    new XElement("DatabaseServerName", txtServerName.Text),
                    new XElement("DatabaseUserName", txtDatabaseUserName.Text),
                    new XElement("DatabasePassword", txtDatabasePassword.Text),
                    new XElement("ServiceAccount", txtAccount.Text),
                    new XElement("ServicePassword", txtServicePassword.Text),
                    new XElement("RegistrationCode", txtRegistrationCode.Text));

                doc.Add(xml);
                doc.Save(fileName);

                // Read file to a string
                string contents = File.ReadAllText(fileName);

                string key = String.Empty;
                byte[] aesKey;
                using (var aes = Aes.Create())
                {
                    // aesKey = aes.Key;
                    key = Convert.ToBase64String(aes.Key);
                }

                string sKey = "LvtZELDrB394hbSOi3SurLWAvC8adNpZiJmQDJHdfJU=";
                aesKey = System.Text.Encoding.UTF8.GetBytes(sKey);

                string encyptedText = EncryptDecrpt.EncryptStringToBase64String(contents, aesKey);
                File.WriteAllText(fileName, encyptedText);

EDIT2: Here's both parts as they stand now. The encrypting side:

private void SaveForm()
        {
            try
            {
                string fileName = System.IO.Path.Combine(Application.StartupPath, "alphaService.xml");
                XDocument doc = new XDocument();
                XElement xml = new XElement("Info",
                    new XElement("DatabaseServerName", txtServerName.Text),
                    new XElement("DatabaseUserName", txtDatabaseUserName.Text),
                    new XElement("DatabasePassword", txtDatabasePassword.Text),
                    new XElement("ServiceAccount", txtAccount.Text),
                    new XElement("ServicePassword", txtServicePassword.Text),
                    new XElement("RegistrationCode", txtRegistrationCode.Text));

                doc.Add(xml);
                // doc.Save(fileName);

                // Read file to a string
                // string contents = File.ReadAllText(fileName);

                string key = String.Empty;
                byte[] aesKey;
                //using (var aes = Aes.Create())
                //{
                //    aesKey = aes.Key;
                //    key = Convert.ToBase64String(aes.Key);
                //}

                string sKey = "LvtZELDrB394hbSOi3SurLWAvC8adNpZiJmQDJHdfJU=";
                aesKey = Convert.FromBase64String(sKey);

                string encyptedText = EncryptDecrpt.EncryptStringToBase64String(doc.ToString(), aesKey);
                File.WriteAllText(fileName, encyptedText);
                //doc.Save(fileName);

The Windows Service side that tries to decrypt:

try
        {
            string path = AppDomain.CurrentDomain.BaseDirectory;
            eventLog1.WriteEntry(path);
            string fileName = System.IO.Path.Combine(path, "alphaService.xml");

            string sKey = "LvtZELDrB394hbSOi3SurLWAvC8adNpZiJmQDJHdfJU=";
            Byte[] keyBytes = Convert.FromBase64String(sKey);

            var encryptedText = File.ReadAllText(fileName, new ASCIIEncoding());
            string xmlStr = DecryptStringFromBase64String(encryptedText, keyBytes);

            eventLog1.WriteEntry(xmlStr);

            using (XmlReader reader = XmlReader.Create(new StringReader(xmlStr)))
            {
                reader.ReadToFollowing("DatabaseServerName");
                DatabaseServerName = reader.ReadElementContentAsString();
                reader.ReadToFollowing("DatabaseUserName");
                DatabaseUserName = reader.ReadElementContentAsString();
                reader.ReadToFollowing("DatabasePassword");
                DatabasePassword = reader.ReadElementContentAsString();
                reader.ReadToFollowing("RegistrationCode");
                RegistrationCode = reader.ReadElementContentAsString();
            }
            eventLog1.WriteEntry("Configuration data loaded successfully");
        }
        catch (Exception ex)
        {
            eventLog1.WriteEntry("Unable to load configuration data.  " + ex.Message);
        }
Community
  • 1
  • 1
user2471435
  • 1,644
  • 7
  • 35
  • 62
  • You need to store the salt and initialization vector so that you can use it on both applications. How you do that it's up to you, e.g. file, config file, database, hard code it. A better idea would be to use the key of a certificate to encrypt and decrypt. That way you don't have to worry about key storage – ManyRootsofAllEvil Apr 07 '14 at 19:20
  • Can you show an example with code? I just don't get this stuff and am so confused! – user2471435 Apr 07 '14 at 19:24
  • My suggestion does imply usage of RSA, simply because that solves the issue of key storage, so probably not going to work for you, sorry, i missed the link at the top. Have a look at this for storage of key and iv (http://stackoverflow.com/questions/18324149/how-to-securely-handle-aes-key-and-iv-values) In essence, you need to store the key and IV somewhere so that they are readable by both the winform app and the windows service – ManyRootsofAllEvil Apr 07 '14 at 19:45
  • The choice of the type of encryption - symmetric or asymmetric (or, practically speaking, hybrid) - depends more on key management than ease of use. Just choosing some function because it is *simple* will result in an insecure solution. You need to have at least a hint on what you are doing and choose a well vetted solution instead. – Maarten Bodewes Apr 07 '14 at 23:52
  • @user2471435 NEVER NEVER NEVER convert bytes to string using a TextEncoding unless you KNOW it was Encoded into bytes the same way. You must use Convert.FromBase64String(sKey). I'll edit my answer. – Kevin Apr 08 '14 at 15:56
  • @user2471435 Instead of doing doc.Save... just get your xml from your xdoc by doing `doc.ToString()` then encrypt that and save it to the file. – Kevin Apr 08 '14 at 18:03
  • So: string encyptedText = EncryptDecrpt.EncryptStringToBase64String(doc.ToString(), aesKey); File.WriteAllText(fileName, encyptedText); doc.Save(fileName); – user2471435 Apr 08 '14 at 18:12
  • Kevin, still same when comes time to decrpt. Unable to load configuration data. Access to the path 'c:\worl\Project Alpha\Code\AlphaBackendService\AlphaBackendService\bin\Debug\alphaService.xml' is denied. – user2471435 Apr 08 '14 at 18:20
  • I'll post both parts of the code as Edit2. – user2471435 Apr 08 '14 at 18:20
  • @user2471435 I've edited your code in Edit2 to work. You were still doing a `doc.Save()` which I think was locking the file. – Kevin Apr 08 '14 at 18:34
  • It looks the same. There is sill a doc.Save – user2471435 Apr 08 '14 at 18:38
  • Ok, commented out the doc.Save but I am still getting the exception – user2471435 Apr 08 '14 at 18:45
  • @user2471435 I've posted an attempted test of your code that works on my machine so if you are doing something differently than my 4th edit below I don't know what to tell you. – Kevin Apr 09 '14 at 14:18
  • Kevin, you're doing it from the same process (the console app). I am doing the first part in the WinForms app, then manually copying the file to the bin\debug directory of the Windows Service and I get the access denied exception. Is it the file copy? – user2471435 Apr 09 '14 at 14:24
  • Do you have the file up in notepad maybe? It must be related to the file copy. – Kevin Apr 09 '14 at 14:43
  • It works if I use a shared file on the c drive!! – user2471435 Apr 09 '14 at 15:10
  • The problem is going to be that our Installer program picks up the file as output out of the C:\worl\Project Alpha\Code\AlphaConfigurationUtility\AlphaConfigurationUtility\bin\Debug directory and installs it under the c:Program Files\etc... – user2471435 Apr 09 '14 at 15:33

1 Answers1

5

The algorithm I wrote below uses a random Initialization Vector that it puts at the beginning of the encrypted value so you can encrypt the same value twice and not get the same encrypted output. This is fairly normal and lets you only pass a single "secret" back and forth.

You will need to share your secret key by some out of bounds process because both encryption and decryption need to know the key. That is a seperate topic of key exchange that is documented in other places. Here is an SO link to get you started if you need some help on it.

Also if you are "making up" random values I recommend that you don't. Use something to help you like the following which generates random bytes and then converts them into a base64 string which is easier for human usage or some types of key exchange. Note that this is just an example of how you could generate random key's... in practice this may be based on some user input that is recreatable or you use the users hash value to lookup your random key that you generate. In any event here is the code for the key...

byte[] key;
string base64Key;
using (var aes = Aes.Create())
{
    // key as byte[]
    key = aes.Key;  
    // key as base64string - which one you use depends on how you store your keys
    base64Key= Convert.ToBase64String(aes.Key);
}

Usage is as follows...

    // you get the base64 encoded key from somewhere
    var base64Key = "+CffHxKmykUvCrrCILd4rZDBcrIoe3w89jnPNXYi0rU="; 
    // convert it to byte[] or alternatively you could store your key as a byte[] 
    //   but that depends on how you set things up.
    var key = Convert.FromBase64String(base64Key);
    var plainText = "EncryptThis";
    var encryptedText = EncryptStringToBase64String(plainText, key);
    var decryptedText = DecryptStringFromBase64String(encryptedText, key);

Here are the encryption methods... EncryptStringToBase64String and DecryptStringFromBase64String.

EDIT: Great point owlstead about using Aes.BlockSize for the IV size. I've also cleaned up the arguement checks.

    private const int KeySize = 256; // in bits
    static string EncryptStringToBase64String(string plainText, byte[] Key)
    {
        // Check arguments. 
        if (Key == null || Key.Length <= 0)
            throw new ArgumentNullException("Key");
        byte[] returnValue;
        using (var aes = Aes.Create())
        {
            aes.KeySize = KeySize;
            aes.GenerateIV();
            aes.Mode = CipherMode.CBC;
            var iv = aes.IV;
            if (string.IsNullOrEmpty(plainText))
                return Convert.ToBase64String(iv);
            var encryptor = aes.CreateEncryptor(Key, 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(plainText);
                    }
                    // this is just our encrypted data
                    var encrypted = msEncrypt.ToArray();
                    returnValue = new byte[encrypted.Length + iv.Length];
                    // append our IV so our decrypt can get it
                    Array.Copy(iv, returnValue, iv.Length);
                    // append our encrypted data
                    Array.Copy(encrypted, 0, returnValue, iv.Length, encrypted.Length);
                }
            }
        }

        // return encrypted bytes converted to Base64String
        return Convert.ToBase64String(returnValue);
    }

    static string DecryptStringFromBase64String(string cipherText, byte[] Key)
    {
        // Check arguments. 
        if (string.IsNullOrEmpty(cipherText))
            return string.Empty;
        if (Key == null || Key.Length <= 0)
            throw new ArgumentNullException("Key");

        string plaintext = null;
        // this is all of the bytes
        var allBytes = Convert.FromBase64String(cipherText);

        using (var aes = Aes.Create())
        {
            aes.KeySize = KeySize;
            aes.Mode = CipherMode.CBC;

            // get our IV that we pre-pended to the data
            byte[] iv = new byte[aes.BlockSize/8];
            if (allBytes.Length < iv.Length)
                throw new ArgumentException("Message was less than IV size.");
            Array.Copy(allBytes, iv, iv.Length);
            // get the data we need to decrypt
            byte[] cipherBytes = new byte[allBytes.Length - iv.Length];
            Array.Copy(allBytes, iv.Length, cipherBytes, 0, cipherBytes.Length);

            // Create a decrytor to perform the stream transform.
            var decryptor = aes.CreateDecryptor(Key, iv);

            // Create the streams used for decryption. 
            using (MemoryStream msDecrypt = new MemoryStream(cipherBytes))
            {
                using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                {
                    using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                    {
                        // Read the decrypted bytes from the decrypting stream 
                        // and place them in a string.
                        plaintext = srDecrypt.ReadToEnd();
                    }
                }
            }
        }

        return plaintext;
    }

EDIT 2: Never convert actual binary data (like a random key) into a string using a TextEncoding. If data starts life as a string and you convert into binary using an encoding then and ONLY then can you convert it from binary into a string using the proper encoding. Otherwise you will have code that works sometimes which is a recipe for torturing yourself.

        // This is base64 not UTF8, unicode, ASCII or anything else!!!
        string sKey = "LvtZELDrB394hbSOi3SurLWAvC8adNpZiJmQDJHdfJU=";
        aesKey = Convert.FromBase64String(sKey);

Edit 3:

Why use File.WriteAllText to write the file but use File.ReadAllBytes when you read it? You can write it and read it as text and use ASCII encoding since base64 is guaranteed to be ASCII. Also Decrypt returns a decrypted string which you are not storing or using. The decrypted string is what you need to parse because it's your xml.

You can use this for saving the file...

    var encryptedText = File.ReadAllText(fileName, new ASCIIEncoding());

In your decrypt you should do this...

    var encryptedText = File.ReadAllText(fileName, new ASCIIEncoding());
    string xmlStr = DecryptStringFromBase64String(encryptedStr , keyBytes);

EDIT 4: I've attempted to duplicate your exception and I can't make it happen... here is my test code that I'm running in a console app and it works.

public static void EncryptMethod()
{
    var fileName = @"c:/text.xml";
    XDocument doc = new XDocument();
    XElement xml = new XElement("Info",
        new XElement("DatabaseServerName", "txtServerName.Text"),
        new XElement("DatabaseUserName", "txtDatabaseUserName.Text"),
        new XElement("DatabasePassword", "txtDatabasePassword.Text"),
        new XElement("ServiceAccount", "txtAccount.Text"),
        new XElement("ServicePassword", "txtServicePassword.Text"),
        new XElement("RegistrationCode", "txtRegistrationCode.Text"));
    doc.Add(xml);

    var sKey = "LvtZELDrB394hbSOi3SurLWAvC8adNpZiJmQDJHdfJU=";
    var aesKey = Convert.FromBase64String(sKey);

    string encyptedText = EncryptStringToBase64String(doc.ToString(), aesKey);
    File.WriteAllText(fileName, encyptedText);
}


public static void DecryptMethod()
{
    var fileName = @"c:/text.xml";
    string sKey = "LvtZELDrB394hbSOi3SurLWAvC8adNpZiJmQDJHdfJU=";
    Byte[] keyBytes = Convert.FromBase64String(sKey);

    var encryptedText = File.ReadAllText(fileName, new ASCIIEncoding());
    string xmlStr = DecryptStringFromBase64String(encryptedText, keyBytes);

    using (XmlReader reader = XmlReader.Create(new StringReader(xmlStr)))
    {
        reader.ReadToFollowing("DatabaseServerName");
        Console.WriteLine(reader.ReadElementContentAsString());
        reader.ReadToFollowing("DatabaseUserName");
        Console.WriteLine(reader.ReadElementContentAsString());
        reader.ReadToFollowing("DatabasePassword");
        Console.WriteLine(reader.ReadElementContentAsString());
        reader.ReadToFollowing("RegistrationCode");
        Console.WriteLine(reader.ReadElementContentAsString());
    }
}

Usage from the console app...

    EncryptMethod();
    DecryptMethod();
Community
  • 1
  • 1
Kevin
  • 4,586
  • 23
  • 35
  • Relatively OK answer. Some remarks though: - the IV should always be `Aes.Blocksize` in size (and blocksize is not a function of the key size). The plaintext may be empty, so checking for `== 0` does not make sense, (let alone `< 0` of course). Assigning plaintext to null does not make much of a difference either. – Maarten Bodewes Apr 08 '14 at 00:01
  • You call Convert.ToBase64String(aes.Key) but the Encrypt and Decrypt take a Byte[] for a key. How on earth do I get that and store it? – user2471435 Apr 08 '14 at 13:57
  • @user2471435 aes.Key is a byte[]. Convert it to base64 if you need to read it... but just pass it in as the key if not. I'll edit my answer to reflect usage. – Kevin Apr 08 '14 at 14:37
  • Kevin, I'll show you in an edit what I am trying to do. I want to do the encryption once and store the string and then get the byte[] from that but I have the wrong code. I'll post it. – user2471435 Apr 08 '14 at 15:12
  • @owlstead My code was using arrays before which is where some of those checks came from. I've corrected it and use Aes.BlockSize now as well excellent point! – Kevin Apr 08 '14 at 15:51
  • Kevin, now I can't access the file from the other program where I want to decrypt when I do a File.ReadAllBytes which I think I need in order to pass to decrypt function. I'll make another edit. – user2471435 Apr 08 '14 at 17:19
  • @user2471435 Decrypt returns a string which you aren't doing anything with. Then you are trying to read the encrypted xml as if it were xml. – Kevin Apr 08 '14 at 17:45
  • But I get an Access denied exception when I call File.ReadAllBytes. I am in a catch-22. I can't decrypt the file without having the contents and I can't get the contents without the decrypt. Can you add to your post how I could do this? – user2471435 Apr 08 '14 at 17:53
  • Unable to load configuration data. Access to the path 'c:\worl\Project Alpha\Code\AlphaBackendService\AlphaBackendService\bin\Debug\alphaService.xml' is denied. – user2471435 Apr 08 '14 at 17:56
  • You aren't closing the file... actually you are writing the xml to it.. then reading it.. then writing the encrypted data to the same file. Just create the XDocument in memory and write it once. – Kevin Apr 08 '14 at 17:58