Is this wrapper around AesManaged ok?

I need to encrypt/decrypt some strings. I've build my wrapper class according to the msdn documentation but with some changes.

Since I want to encrypt/decrypt data with a given string/passphrase, I don't use AesManaged for creating a key. (The user should be able to encrypt/decrypt with a key he enters, and therefore I cannot use the key from AesManaged and I cannot save the key).

I instead create the key by using Rfc2898DeriveBytes (PBKDF2) with a given salt. The given salt is used since I do not store the key and I think because of this, the salt must be always the same.

I then create an IV, encrypt the given string and concatenate the IV and the encrypted string. This will then eventually got saved in a file. This means the IV gets save together with the encrypted data.

Questions:

  1. Is it ok to store the IV together with the encrypted data?
  2. Is there another way to create the key without using the same salt everytime(Based on a given passphrase)?
  3. Is this encryption done using AES128 or AES256?
  4. Will the IV be always 16 bytes, or can this change?

    static void Main(string[] args)
    {
        const string stringToEncrypt = "String to be encrypted/decrypted. Encryption is done via AesManaged";
        const string password = "m1Sup3rS3cre!Password";

        string encrypted = EncryptString(stringToEncrypt, password);
        string roundtrip = DecryptStringFromBytes_Aes(encrypted, password);

        Console.WriteLine("Original:   {0}", stringToEncrypt);
        Console.WriteLine("Round Trip: {0}", roundtrip);

        Console.ReadLine();
    }

    static string EncryptString(string plainText, string password)
    {
        string encryptedString;

        using (AesManaged aesAlg = new AesManaged())
        {
            aesAlg.Key = PasswordAsByte(password);
            ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

            using (MemoryStream msEncrypt = new MemoryStream())
            {
                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                    {
                        swEncrypt.Write(plainText);
                    }
                    var encrypted = msEncrypt.ToArray();

                    encryptedString = Encoding.Default.GetString(aesAlg.IV);
                    encryptedString += Encoding.Default.GetString(encrypted);
                }
            }
        }
        return encryptedString;
    }

    static string DecryptStringFromBytes_Aes(string cipherText, string password)
    {
        using (AesManaged aesAlg = new AesManaged())
        {
            aesAlg.Key = PasswordAsByte(password);

            aesAlg.IV = Encoding.Default.GetBytes(cipherText).Take(16).ToArray();

            ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

            var encryptedByteArray = Encoding.Default.GetBytes(cipherText).Skip(16).ToArray();

            using (MemoryStream msDecrypt = new MemoryStream(encryptedByteArray))
            {
                using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                {
                    using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                    {
                        return srDecrypt.ReadToEnd();
                    }
                }
            }
        }
    }

    private static byte[] PasswordAsByte(string password)
    {
        byte[] salt = Encoding.Default.GetBytes("foobar42");
        Rfc2898DeriveBytes passwordBytes = new Rfc2898DeriveBytes(password, salt);

        return passwordBytes.GetBytes(32);
    }
Jon Skeet
people
quotationmark

No, this is not okay.

1) You're using Encoding.Default in various places. Don't do that - it means you're at the whim of the platform you're on. Always use an explicit encoding, ideally UTF-8 in most cases.

2) You're using Encoding.GetString / Encoding.GetBytes to convert arbitrary binary data to a string and back. That's almost bound to lose data. (It happened to succeed on my machine, but it really depends on the encoding - and it's fundamentally a bad idea.) Encoding is designed for data which is inherently text data, and you're just applying an encoding one way or the other. Your encrypted data is inherently binary data. Use Convert.ToBase64String and Convert.FromBase64String instead.

For your other questions:

  • Yes, it's okay to store the IV with the encrypted data, as far as I know.
  • You could use the same approach for the password: generate a different salt each time, and store that with the encrypted text. Not sure whether that's generally recommended or not, I'm afraid.
  • I believe you're controlling whether the key size is 128 or 256 bits, with your call to passwordBytes.GetBytes(32) - that's a 256-bit key, so it's AES256.
  • I believe the IV size for AES is always 16 bytes (128 bits)

people

See more on this question at Stackoverflow