AES in ASP.NET with VB.NET

16,702

Solution 1

First

Don't do it! Writing your own crypto system can easily lead to making mistakes. It's best to use an existing system, or if not, get someone who knows cryptography to do it. If you have to do it yourself, read Practical Cryptography.

And please, remember: "We already have enough fast, insecure systems." (Bruce Schneier) -- Do things correct and worry about performance later.

That said, if you are stuck on using AES to roll your own, here are a few pointers.

Initialization Vector

AES is a block cipher. Given a key and a block of plaintext, it converts it to a specific ciphertext. The problem with this is that the same blocks of data will generate the same ciphertext with the same key, every time. So suppose you send data like this:

user=Encrypt(Username)&roles=Encrypt(UserRoles)

They're two separate blocks, and the UserRoles encryption will have the same ciphertext each time, regardless of the name. All I need is the ciphertext for an admin, and I can drop it right in with my cipher'd username. Oops.

So, there are cipher operation modes. The main idea is that you'll take the ciphertext of one block, and XOR it into the ciphertext of the next block. That way we'll do Encrypt(UserRoles, Username), and the Username ciphertext is affected by the UserRoles.

The problem is that the first block is still vulnerable - just by seeing someone's ciphertext, I might know their roles. Enter the initialization vector. The IV "starts up" the cipher and ensures it has random data to encrypt the rest of the stream. So now the UserRoles ciphertext has the ciphertext of the random IV XOR'd in. Problem solved.

So, make sure you generate a random IV for each message. The IV is not sensitive and can be sent plaintext with the ciphertext. Use an IV large enough -- the size of the block should be fine for many cases.

Integrity

AES doesn't provide integrity features. Anyone can modify your ciphertext, and the decrypt will still work. It's unlikely it'll be valid data in general, but it might be hard to know what valid data is. For instance, if you're transmitting a GUID encrypted, it'd be easy to modify some bits and generate a completely different one. That could lead to application errors and so on.

The fix there is to run a hash algorithm (use SHA256 or SHA512) on the plaintext, and include that in the data you transmit. So if my message is (UserName, Roles), you'll send (UserName, Roles, Hash(UserName, Roles)). Now if someone tampers with the ciphertext by flipping a bit, the hash will no longer compute and you can reject the message.

Key derivation

If you need to generate a key from a password, use the built-in class: System.Security.Cryptography.PasswordDeriveBytes. This provides salting and iterations, which can improve the strength of derived keys and reduce the chance of discovering the password if the key is compromised.

Timing/replay

Edit: Sorry for not mentioning this earlier :P. You also need to make sure you have an anti-replay system. If you simply encrypt the message and pass it around, anyone who gets the message can just resend it. To avoid this, you should add a timestamp to the message. If the timestamp is different by a certain threshold, reject the message. You may also want to include a one-time ID with it (this could be the IV) and reject time-valid messages that come from other IPs using the same ID.

It's important to make sure you do the hash verification when you include the timing information. Otherwise, someone could tamper with a bit of the ciphertext and potentially generate a valid timestamp if you don't detect such brute force attempts.

Sample code

Since apparently using an IV correctly is controversial for some folks, here's some code that'll generate random IVs and add them to your output for you. It'll also perform the authentication step, making sure the encrypted data wasn't modified.

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

class AesDemo {

    const int HASH_SIZE = 32; //SHA256

    /// <summary>Performs encryption with random IV (prepended to output), and includes hash of plaintext for verification.</summary>
    public static byte[] Encrypt(string password, byte[] passwordSalt, byte[] plainText) {
        // Construct message with hash
        var msg = new byte[HASH_SIZE + plainText.Length];
        var hash = computeHash(plainText, 0, plainText.Length);
        Buffer.BlockCopy(hash, 0, msg, 0, HASH_SIZE);
        Buffer.BlockCopy(plainText, 0, msg, HASH_SIZE, plainText.Length);

        // Encrypt
        using (var aes = createAes(password, passwordSalt)) {
            aes.GenerateIV();
            using (var enc = aes.CreateEncryptor()) {

                var encBytes = enc.TransformFinalBlock(msg, 0, msg.Length);
                // Prepend IV to result
                var res = new byte[aes.IV.Length + encBytes.Length];
                Buffer.BlockCopy(aes.IV, 0, res, 0, aes.IV.Length);
                Buffer.BlockCopy(encBytes, 0, res, aes.IV.Length, encBytes.Length);
                return res;
            }
        }
    }

    public static byte[] Decrypt(string password, byte[] passwordSalt, byte[] cipherText) {
        using (var aes = createAes(password, passwordSalt)) {
            var iv = new byte[aes.IV.Length];
            Buffer.BlockCopy(cipherText, 0, iv, 0, iv.Length);
            aes.IV = iv; // Probably could copy right to the byte array, but that's not guaranteed

            using (var dec = aes.CreateDecryptor()) {
                var decBytes = dec.TransformFinalBlock(cipherText, iv.Length, cipherText.Length - iv.Length);

                // Verify hash
                var hash = computeHash(decBytes, HASH_SIZE, decBytes.Length - HASH_SIZE);
                var existingHash = new byte[HASH_SIZE];
                Buffer.BlockCopy(decBytes, 0, existingHash, 0, HASH_SIZE);
                if (!compareBytes(existingHash, hash)){
                    throw new CryptographicException("Message hash incorrect.");
                }

                // Hash is valid, we're done
                var res = new byte[decBytes.Length - HASH_SIZE];
                Buffer.BlockCopy(decBytes, HASH_SIZE, res, 0, res.Length);
                return res;
            }
        }
    }

    static bool compareBytes(byte[] a1, byte[] a2) {
        if (a1.Length != a2.Length) return false;
        for (int i = 0; i < a1.Length; i++) {
            if (a1[i] != a2[i]) return false;
        }
        return true;
    }

    static Aes createAes(string password, byte[] salt) {
        // Salt may not be needed if password is safe
        if (password.Length < 8) throw new ArgumentException("Password must be at least 8 characters.", "password");
        if (salt.Length < 8) throw new ArgumentException("Salt must be at least 8 bytes.", "salt");
        var pdb = new PasswordDeriveBytes(password, salt, "SHA512", 129);
        var key = pdb.GetBytes(16);

        var aes = Aes.Create();
        aes.Mode = CipherMode.CBC;
        aes.Key = pdb.GetBytes(aes.KeySize / 8);
        return aes;
    }

    static byte[] computeHash(byte[] data, int offset, int count) {
        using (var sha = SHA256.Create()) {
            return sha.ComputeHash(data, offset, count);
        }
    }

    public static void Main() {
        var password = "1234567890!";
        var salt = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
        var ct1 = Encrypt(password, salt, Encoding.UTF8.GetBytes("Alice; Bob; Eve;: PerformAct1"));
        Console.WriteLine(Convert.ToBase64String(ct1));
        var ct2 = Encrypt(password, salt, Encoding.UTF8.GetBytes("Alice; Bob; Eve;: PerformAct2"));
        Console.WriteLine(Convert.ToBase64String(ct2));

        var pt1 = Decrypt(password, salt, ct1);
        Console.WriteLine(Encoding.UTF8.GetString(pt1));
        var pt2 = Decrypt(password, salt, ct2);
        Console.WriteLine(Encoding.UTF8.GetString(pt2));

        // Now check tampering
        try {
            ct1[30]++;
            Decrypt(password, salt, ct1);
            Console.WriteLine("Error: tamper detection failed.");
        } catch (Exception ex) {
            Console.WriteLine("Success: tampering detected.");
            Console.WriteLine(ex.ToString());
        }
    }
}

Output:

JZVaD327sDmCmdzY0PsysnRgHbbC3eHb7YXALb0qxFVlr7Lkj8WaOZWc1ayWCvfhTUz/y0QMz+uv0PwmuG8VBVEQThaNTD02JlhIs1DjJtg= QQvDujNJ31qTu/foDFUiVMeWTU0jKL/UJJfFAvmFtz361o3KSUlk/zH+4701mlFEU4Ce6VuAAuaiP1EENBJ74Wc8mE/QTofkUMHoa65/5e4= Alice; Bob; Eve;: PerformAct1 Alice; Bob; Eve;: PerformAct2 Success: tampering detected. System.Security.Cryptography.CryptographicException: Message hash incorrect. at AesDemo.Decrypt(String password, Byte[] passwordSalt, Byte[] cipherText) in C:\Program.cs:line 46 at AesDemo.Main() in C:\Program.cs:line 100

After removing the random IV and the hash, here's the type of output:

tZfHJSFTXYX8V38AqEfYVXU5Dl/meUVAond70yIKGHY= tZfHJSFTXYX8V38AqEfYVcf9a3U8vIEk1LuqGEyRZXM=

Notice how the first block, corresponding to "Alice; Bob; Eve;" is the same. "Corner case" indeed.

Example without hashing

Here's a simple example of passing a 64-bit integer. Just encrypt and you're open to attack. In fact, the attack is easily done, even with CBC padding.

public static void Main() {
    var buff = new byte[8];
    new Random().NextBytes(buff);
    var v = BitConverter.ToUInt64(buff, 0);
    Console.WriteLine("Value: " + v.ToString());
    Console.WriteLine("Value (bytes): " + BitConverter.ToString(BitConverter.GetBytes(v)));
    var aes = Aes.Create();
    aes.GenerateIV();
    aes.GenerateKey();
    var encBytes = aes.CreateEncryptor().TransformFinalBlock(BitConverter.GetBytes(v), 0, 8);
    Console.WriteLine("Encrypted: " + BitConverter.ToString(encBytes));
    var dec = aes.CreateDecryptor();
    Console.WriteLine("Decrypted: " + BitConverter.ToUInt64(dec.TransformFinalBlock(encBytes, 0, encBytes.Length), 0));
    for (int i = 0; i < 8; i++) {
        for (int x = 0; x < 250; x++) {
            encBytes[i]++;
            try {
                Console.WriteLine("Attacked: " + BitConverter.ToUInt64(dec.TransformFinalBlock(encBytes, 0, encBytes.Length), 0));
                return;
            } catch { }
        }
    }
}

Output:

Value: 6598637501946607785 Value

(bytes): A9-38-19-D1-D8-11-93-5B

Encrypted:

31-59-B0-25-FD-C5-13-D7-81-D8-F5-8A-33-2A-57-DD

Decrypted: 6598637501946607785

Attacked: 14174658352338201502

So, if that's the kind of ID you're sending, it could quite easily be changed to another value. You need to authenticate outside of your message. Sometimes, the message structure is unlikely to fall into place and can sorta act as a safeguard, but why rely on something that could possibly change? You need to be able to rely on your crypto working correctly regardless of the application.

Solution 2

I wrote a blog post which has a sample project that you can download here (C# though): http://www.codestrider.com/blog/read/AESFileEncryptorWithRSAEncryptedKeys.aspx

The code basically uses AES for encryption of binary data and then RSA encrypts the Key and the IV using an X509Certificate. So, as long as the private key certificate is available, the Key and IV can be decrypted, and then in turn the AES encrypted data can be decrypted ..

You could set up your certificate stores so that the 'encryptor' only has access to the public key certificate, while the 'decryptor' has access to the private key.

This allows you to encrypt using different Key and IV each time and avoid hardcoding anything.. which I believe is more secure. There should be nothing in your source code that would easily allow someone to decrypt your data - and if your system was ever compromised, you would only need to swap out the certificates with new ones. No need to recompile the application with new hardcoded values.. :)

The sample code may be slightly different from your intended use, but I think the technique and some of the code might be useful to you.

Solution 3

Below you'll find a class that provides AES Encryption/Decryption methods that explicitly provide URL-friendly strings for use in applications like yours. It also has the methods that work with byte arrays.

NOTE: you should use different values in the Key and Vector arrays! You wouldn't want someone to figure out your keys by just assuming that you used this code as-is! All you have to do is change some of the numbers (must be <= 255) in the Key and Vector arrays.

Using it is easy: just instantiate the class and then call (usually) EncryptToString(string StringToEncrypt) and DecryptString(string StringToDecrypt) as methods. It couldn't be any easier (or more secure) once you have this class in place.


using System;
using System.Data;
using System.Security.Cryptography;
using System.IO;


public class SimpleAES
{
    // Change these keys
    private byte[] Key = { 123, 217, 19, 11, 24, 26, 85, 45, 114, 184, 27, 162, 37, 112, 222, 209, 241, 24, 175, 144, 173, 53, 196, 29, 24, 26, 17, 218, 131, 236, 53, 209 };
    private byte[] Vector = { 146, 64, 191, 111, 23, 3, 113, 119, 231, 121, 2521, 112, 79, 32, 114, 156 };


    private ICryptoTransform EncryptorTransform, DecryptorTransform;
    private System.Text.UTF8Encoding UTFEncoder;

    public SimpleAES()
    {
        //This is our encryption method
        RijndaelManaged rm = new RijndaelManaged();

        //Create an encryptor and a decryptor using our encryption method, key, and vector.
        EncryptorTransform = rm.CreateEncryptor(this.Key, this.Vector);
        DecryptorTransform = rm.CreateDecryptor(this.Key, this.Vector);

        //Used to translate bytes to text and vice versa
        UTFEncoder = new System.Text.UTF8Encoding();
    }

    /// -------------- Two Utility Methods (not used but may be useful) -----------
    /// Generates an encryption key.
    static public byte[] GenerateEncryptionKey()
    {
        //Generate a Key.
        RijndaelManaged rm = new RijndaelManaged();
        rm.GenerateKey();
        return rm.Key;
    }

    /// Generates a unique encryption vector
    static public byte[] GenerateEncryptionVector()
    {
        //Generate a Vector
        RijndaelManaged rm = new RijndaelManaged();
        rm.GenerateIV();
        return rm.IV;
    }


    /// ----------- The commonly used methods ------------------------------    
    /// Encrypt some text and return a string suitable for passing in a URL.
    public string EncryptToString(string TextValue)
    {
        return ByteArrToString(Encrypt(TextValue));
    }

    /// Encrypt some text and return an encrypted byte array.
    public byte[] Encrypt(string TextValue)
    {
        //Translates our text value into a byte array.
        Byte[] bytes = UTFEncoder.GetBytes(TextValue);

        //Used to stream the data in and out of the CryptoStream.
        MemoryStream memoryStream = new MemoryStream();

        /*
         * We will have to write the unencrypted bytes to the stream,
         * then read the encrypted result back from the stream.
         */
        #region Write the decrypted value to the encryption stream
        CryptoStream cs = new CryptoStream(memoryStream, EncryptorTransform, CryptoStreamMode.Write);
        cs.Write(bytes, 0, bytes.Length);
        cs.FlushFinalBlock();
        #endregion

        #region Read encrypted value back out of the stream
        memoryStream.Position = 0;
        byte[] encrypted = new byte[memoryStream.Length];
        memoryStream.Read(encrypted, 0, encrypted.Length);
        #endregion

        //Clean up.
        cs.Close();
        memoryStream.Close();

        return encrypted;
    }

    /// The other side: Decryption methods
    public string DecryptString(string EncryptedString)
    {
        return Decrypt(StrToByteArray(EncryptedString));
    }

    /// Decryption when working with byte arrays.    
    public string Decrypt(byte[] EncryptedValue)
    {
        #region Write the encrypted value to the decryption stream
        MemoryStream encryptedStream = new MemoryStream();
        CryptoStream decryptStream = new CryptoStream(encryptedStream, DecryptorTransform, CryptoStreamMode.Write);
        decryptStream.Write(EncryptedValue, 0, EncryptedValue.Length);
        decryptStream.FlushFinalBlock();
        #endregion

        #region Read the decrypted value from the stream.
        encryptedStream.Position = 0;
        Byte[] decryptedBytes = new Byte[encryptedStream.Length];
        encryptedStream.Read(decryptedBytes, 0, decryptedBytes.Length);
        encryptedStream.Close();
        #endregion
        return UTFEncoder.GetString(decryptedBytes);
    }

    /// Convert a string to a byte array.  NOTE: Normally we'd create a Byte Array from a string using an ASCII encoding (like so).
    //      System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
    //      return encoding.GetBytes(str);
    // However, this results in character values that cannot be passed in a URL.  So, instead, I just
    // lay out all of the byte values in a long string of numbers (three per - must pad numbers less than 100).
    public byte[] StrToByteArray(string str)
    {
        if (str.Length == 0)
            throw new Exception("Invalid string value in StrToByteArray");

        byte val;
        byte[] byteArr = new byte[str.Length / 3];
        int i = 0;
        int j = 0;
        do
        {
            val = byte.Parse(str.Substring(i, 3));
            byteArr[j++] = val;
            i += 3;
        }
        while (i < str.Length);
        return byteArr;
    }

    // Same comment as above.  Normally the conversion would use an ASCII encoding in the other direction:
    //      System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
    //      return enc.GetString(byteArr);    
    public string ByteArrToString(byte[] byteArr)
    {
        byte val;
        string tempStr = "";
        for (int i = 0; i <= byteArr.GetUpperBound(0); i++)
        {
            val = byteArr[i];
            if (val < (byte)10)
                tempStr += "00" + val.ToString();
            else if (val < (byte)100)
                tempStr += "0" + val.ToString();
            else
                tempStr += val.ToString();
        }
        return tempStr;
    }
}

Share:
16,702
Admin
Author by

Admin

Updated on June 04, 2022

Comments

  • Admin
    Admin almost 2 years

    What is a good link or article on encrypting a URL link with AES to pass username to another web site in ASP.NET using VB.NET 2005? FYI: The receiving web site will have access to the private KEY to decrypt.

  • andleer
    andleer about 15 years
    Michael, please revisit the comment you left on my code. I think you are mistaken. I am an wrong, more power to you but I an not sure that is the case.
  • MichaelGG
    MichaelGG about 15 years
    The IV should be generated for each message, not set once.
  • Dineshkumar
    Dineshkumar about 15 years
    Michael - you seem to have all of the flaws well documented but they are either unlikely or very specific corner cases that may well not apply. Your contrived example (UserName/UserRoles) does not establish the need to generate Unique IVs for each encryption and makes all of AES unwieldy.
  • Dineshkumar
    Dineshkumar about 15 years
    To cover your case, I would simply provide unique IVs for the user name and for user roles so that the receiver could decrypt each without any chance of decrypting the other. In essence, your criticism of my code is "if someone breaks your cipher then they can break it again." And with that little
  • Dineshkumar
    Dineshkumar about 15 years
    nugget of wisdom, you downvote me as "unhelpful" despite the fact that I actually provide all the source code needed to solve this problem in the general case? I really do appreciate your comments above but you could have just noted the corner case or, better yet, offered your own code example.
  • Dineshkumar
    Dineshkumar about 15 years
    I disagree entirely! This would mean that I'd also have to send the IV with each message - with obvious drawbacks. There are very easy ways around your corner cases (e.g. separate IVs for each class of message, adding a delimiter and some "salt" to your message (which is what I do), etc.
  • Dineshkumar
    Dineshkumar about 15 years
    Your downvote was completely inappropriate here - especially since, despite all of your criticisms, you provide no source code that actually backs up any of your points.
  • MichaelGG
    MichaelGG about 15 years
    Salt at the beginning of the message? That's exactly what an IV is. I'd normally not downvote for a simple bug, except it's a bit of a larger problem when doing crypto. But since it apparently upset you so much I'll take it off...
  • MichaelGG
    MichaelGG about 15 years
    I'd hardly consider IVs to be a "corner case" of a crypto system.
  • Dineshkumar
    Dineshkumar about 15 years
    The "Salt" is actually a time-stamp appended to the end of the message. The thing is, the message always changes (due to the time stamp), I can pull it apart easily due to the delimiter, and the timestamp itself acts like your hashing suggestion since out-of-band results will fail.
  • MichaelGG
    MichaelGG about 15 years
    A timestamp at the end of the message won't modify the beginning of the ciphertext, which means you can leak information. Maybe your particular app isn't affected by this, but it's definately not an overall secure approach.
  • MichaelGG
    MichaelGG about 15 years
    Anyways, I added some sample code to show how trivial my suggestions are. Doesn't hurt to do it right, especially when giving code to someone without full app requirements.
  • MichaelGG
    MichaelGG about 15 years
    Oh and I can't remove the downvote until the post is edited -- I didn't see the message when I clicked to remove it and commented last time.
  • MichaelGG
    MichaelGG about 15 years
    There, sample code and a very simple demonstration that this is not "contrived". But hey, glad you're out there making AES less unwieldy than those darned cryptographers making it so hard for no reason :).
  • Shea
    Shea about 15 years
    You're absolutely right. I didn't realize that Rijndael used the AES algorithm.
  • Funka
    Funka almost 14 years
    Just a note, I was reading up on the System.Security.Cryptography.PasswordDeriveBytes that you mentioned, but see the recommendation that "Rfc2898DeriveBytes replaces PasswordDeriveBytes for deriving key material from a password and is preferred in new applications."
  • CodingBarfield
    CodingBarfield almost 12 years
    You're link is death: New link that does work codestrider.com/…