0
\$\begingroup\$

My application needs to implement authenticated encryption using C#. There is a .NET class called encryptAndAuthenticate, but it is only supported on windows 8 or later, and I need the code to also work on windows 7. I created a class for internal use in my app that uses the encrypt-then-authenticate model. I use AES and HMAC-SHA256.

The code was borrowed heavily from two sites:

If you see mistakes in my use of the .NET crypto functions or general mistakes please let me know!

Note: I didn't add the XML comment blocks to my functions because in my opinion (you may disagree) they take up extra space and don't provide much benefit if your class is only used inside your own app.

internal static class CryptoLayer
{
    private const int saltSizeBytes = 32;
    private const int aesKeySizeBytes = 32;
    private const int aesIVSizeBytes = 16;
    private const int hmacKeySizeBytes = 32;
    private const int hmacTagSizeBytes = 32;
    // 3 values must be derived (aes key, aes iv, and hmac key).
    // Number of total iterations is 3 * keyDerivationIterations.
    // Takes about 500 ms total.
    private const int keyDerivationIterations = 20000;

    // This is called to generate a random byte array of size saltSizeBytes.
    public static byte[] GenerateSalt()
    {
        using (var randomGenerator = new RNGCryptoServiceProvider())
        {
            var salt = new byte[saltSizeBytes];
            randomGenerator.GetBytes(salt);
            return salt;
        }
    }

    // Take in password and salt and derive the key and IV for the aes encryption and the key for hmac.
    private static void DeriveKeys(string password, byte[] salt, out byte[] aesKey, out byte[] aesIV, out byte[] hmacKey)
    {
        // Derive the key, IV, and mac key using the password and the salt.
        using (var derivedKey = new Rfc2898DeriveBytes(password, salt, keyDerivationIterations))
        {
            aesKey = derivedKey.GetBytes(aesKeySizeBytes);
            aesIV = derivedKey.GetBytes(aesIVSizeBytes);
            hmacKey = derivedKey.GetBytes(hmacKeySizeBytes);
        }
    }

    // Takes in plain text, password, and salt. Populates output parameters cipherText and macTag.
    public static void EncryptThenAuthenicate(string plainText, string password, byte[] salt, out byte[] cipherText, out byte[] macTag)
    {
        // Default output params to null
        cipherText = null;
        macTag = null;
        try
        {
            // Populate aesKey, aesIV, and hmacKey via key derivation function
            byte[] aesKey, aesIV, hmacKey;
            DeriveKeys(password, salt, out aesKey, out aesIV, out hmacKey);

            // Set up AES encryption provider object.
            using (var aesAlg = new AesCryptoServiceProvider())
            {
                aesAlg.Key = aesKey;
                aesAlg.IV = aesIV;

                // Create an encrytor to perform the stream transform.
                var encryptor = aesAlg.CreateEncryptor(aesKey, aesIV);

                // Create the streams used for encryption.
                using (var msEncrypt = new MemoryStream())
                {
                    using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                    {
                        using (var swEncrypt = new StreamWriter(csEncrypt))
                        {
                            //Write all data to the stream.
                            swEncrypt.Write(plainText);
                        }
                        cipherText = msEncrypt.ToArray();
                    }
                }
            }

            // Generate mac tag using by using hmac function on IV||cipherText.
            using (var hmac = new HMACSHA256(hmacKey))
            {
                using (var msEncrypt = new MemoryStream())
                {
                    using (var binaryWriter = new BinaryWriter(msEncrypt))
                    {
                        // Prepend IV
                        binaryWriter.Write(aesIV);
                        // Write cipher text
                        binaryWriter.Write(cipherText);
                        binaryWriter.Flush();
                        //Authenticate IV||cipherText and create mac tag
                        macTag = hmac.ComputeHash(msEncrypt.ToArray());
                    }
                }
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(String.Format("Encryption failed. {0}", ex.Message), "Encryption Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }

    // Takes in cipherText, password, salt, and mac tag.
    // First, authenticates aesIV||cipherText using mac tag.
    // Then, if that succeeds, decrypts and returns plainText.
    public static string AuthenticateThenDecrypt(byte[] cipherText, string password, byte[] salt, byte[] macTag)
    {
        try
        {
            // Populate aesKey, aesIV, and hmacKey via key derivation function
            byte[] aesKey, aesIV, hmacKey;
            DeriveKeys(password, salt, out aesKey, out aesIV, out hmacKey);

            // Create byte array to store IV||cipherText
            byte[] ivAndCipherText = new byte[aesIV.Length + cipherText.Length];
            aesIV.CopyTo(ivAndCipherText, 0);
            cipherText.CopyTo(ivAndCipherText, aesIV.Length);

            // Calculate if stored macTag matches calcuated macTag.  If not, return error.
            using (var hmac = new HMACSHA256(hmacKey))
            {
                // Calculate what mac tag should be.
                var calcTag = hmac.ComputeHash(ivAndCipherText);

                // Compare with constant time comparison to prevent timing attacks.
                var mismatch = 0;
                for (var i = 0; i < macTag.Length; i++)
                    mismatch |= macTag[i] ^ calcTag[i];

                // If message doesn't authenticate, return error and exit routine.
                if (mismatch != 0)
                    throw new Exception("Incorrect password or corrupted data.");

                // Authentication suceeded, now decrypt...

                // Set up AES encryption provider object.
                using (var aesAlg = new AesCryptoServiceProvider())
                {
                    aesAlg.Key = aesKey;
                    aesAlg.IV = aesIV;

                    // Create a decrytor to perform the stream transform.
                    var decryptor = aesAlg.CreateDecryptor(aesKey, aesIV);

                    // Create the streams used for decryption.
                    using (var msDecrypt = new MemoryStream(cipherText))
                    {
                        using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                        {
                            using (var srDecrypt = new StreamReader(csDecrypt))
                            {
                                // Read the decrypted bytes from the decrypting stream and return as string.
                                return srDecrypt.ReadToEnd();
                            }
                        }
                    }

                }
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(String.Format("Decryption failed. {0}", ex.Message),"Decryption Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return string.Empty;
        }
    }
}
\$\endgroup\$
  • \$\begingroup\$ I'm going to choose to disagree on the comment blocks comment. \$\endgroup\$ – nhgrif Apr 9 '16 at 16:55
2
\$\begingroup\$
  • Number of total iterations is 3 * keyDerivationIterations.

    The total size you're deriving is 80 bytes. Since SHA-1 outputs 20 bytes, that's 4 times not 3 times the cost.

    Also note that the attacker only needs to derive either the HMAC key or the AES key, so you gifted them with a factor 2 speedup.

    Output only 20 bytes and expand them using HKDF-Expand.

  • Why do you pass the salt to the encryption function? It should generate the salt itself and output it alongside the ciphertext. Leaving it to the caller only risks them screwing up and reusing a salt.

  • Why output the MAC tag as separate output? Standard practice is combining it with the ciphertext.
  • The name of the class sucks. I'd call it something like PasswordBasedEncryption
  • You derive the IV from the same masterkey you derive the keys from. So there is no point in authenticating it.
\$\endgroup\$
  • \$\begingroup\$ 1. "Output only 20 bytes and expand them using HKDF-Expand." - Is there a function included in dot net for doing this? I didn't find one in my searches. 2. "You derive the IV from the same masterkey you derive the keys from. So there is no point in authenticating it." In my original post at cryptography stack exchange one of the posters said I should include the IV in the HMAC input. (crypto.stackexchange.com/questions/34362/…) Was he incorrect? \$\endgroup\$ – Ralph P Apr 18 '16 at 21:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.