Authenticated Symmetric Encryption in .NET

Over the last week, we've made a couple of updates to our Codeplex projects to add authenticated symmetric encryption to the managed cryptography surface area for the first time.  Since we've never supported authenticated symmetric algorithms in managed code before, I thought I'd run though some basics about what they are and how to use them.

For starters, in order to use the authenticated symmetric encryption classes, you'll need a few prerequisites:

  • Windows Vista SP 1, Windows Server 2008, or higher
  • .NET framework v3.5 SP 1 or higher
  • Security.Cryptography.dll 1.3 or higher (from https://codeplex.com/clrsecurity)

Authenticated symmetric cryptography differs from the symmetric cryptography that the .NET framework has traditionally supported in that it produces an authentication tag in addition to ciphertext when encrypting data.  This authentication tag can be used to verify that the ciphertext has not been tampered with between when it was encrypted and decrypted.

For example, imagine you encrypted a message using the AES class:

using (Aes aes = new AesCng())

{

    aes.Key = key;

    aes.IV = iv;

 

    using (MemoryStream ms = new MemoryStream())

    using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))

    {

        byte[] plaintext = Encoding.UTF8.GetBytes("Secret data to be encrypted.");

        cs.Write(plaintext, 0, plaintext.Length);

        cs.FlushFinalBlock();

 

        return ms.ToArray();

    }

}

While an attacker cannot read the data encrypted by this operation without knowing the encryption key, they can modify the ciphertext bytes themselves which will result in corruption of the decrypted message on the receiving end:

// The ciphertext is protected from being decrypted without knowledge of the key, however it

// is not protected from being tampered with:

ciphertext[5] = 0x21;

 

using (Aes aes = new AesCng())

{

    aes.Key = key;

    aes.IV = iv;

 

    using (MemoryStream ms = new MemoryStream())

    using (CryptoStream cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write))

    {

        cs.Write(ciphertext, 0, ciphertext.Length);

        cs.FlushFinalBlock();

 

        byte[] decrypted = ms.ToArray();

        string message = Encoding.UTF8.GetString(decrypted);

        Console.WriteLine(message);

    }

}

Which produces output such as:

? ]\??e enc?ypted.

This can be solved by signing the ciphertext, and having the receiving party verify the signature before decrypting the secret message.

Authenticated symmetric algorithms solve this problem by creating an authentication tag which can be used to verify that the ciphertext has not been tampered with since it was generated.

In addition to verifying that the ciphertext has not been modified, the authenticated symmetric algorithms that we have in managed code also take additional authenticated data as an input.  This data is not included in the ciphertext itself - so when decrypting a message that was created with additional authenticated data, the output will not contain the authenticated data.  Instead, the authenticated data is only used in generating the authentication tag.  This means that much like the key and IV, both the encrypting and decrypting parties need to know what the additional authenticated data is otherwise the authentication tag will not verify.

Authenticated Symmetric Algorithm Type Hierarchy

This functionality is exposed in managed code via the AuthenticatedSymmetricAlgorithm base class.  Much like the SymmetricAlgorithm base class, AuthenticatedSymmetricAlgorithm is an abstract class for actual authenticated symmetric algorithms to derive from.

Currently, the only authenticated symmetric algorithm is an authenticated version of AES, which is represented by the AuthenticatedAes abstract base class.  Again, mirroring the symmetric algorithm type hierarchy, AuthenticatedAes is an abstract base class that concrete authenticated AES implementations derive from.  (Much like Aes serves as the base class for AesManaged, AesCryptoServiceProvider and AesCng).

The concrete implementation of AuthenticatedAesCng which is in Security.Cryptography.dll 1.3 is built on top of  CNG, and follows our traditional naming scheme: AuthenticatedAesCng.

Setting up an Authenticated AES Encryptor

The authentication tag is generated by an authenticated chaining algorithm, which is used in place of the standard chaining modes that AES can use (such as CBC or ECB).  Currently CNG supports two algorithms for generating an authentication tag with AES:

  1. Galois/Counter Mode - this is the default, and is represented by CngChainingMode.Gcm.  (https://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf)
  2. Coutner with CBC-MAC - this is selected by using CngChainingMode.Ccm.  (https://www.ietf.org/rfc/rfc3610.txt)

Since neither of these chaining modes are supported by the standard CipherMode enumeration, AuthenticatedAesCng adds a new property called CngMode which allows you to specify a CngChainingMode rather than a standard CipherMode.  Trying to use the traditional Mode property when the CngChainingMode is set to one of these new values will result in an exception.

The IV property is used to setup what the two algorithm specifications above refer to as a nonce.  Unlike traditional AES, the IV size does not match the block size, but is instead specified by the chaining mode.  For instance, both GCM and CCM can work with an IV of 12 bytes.

AuthenticatedSymmetricAlgorithms also have an AuthenticatedData byte array property which is used to setup the additional authentication data being used in the tag generation.  This property is optional - leaving the value null means that the authentication tag is generated only from the plaintext.

The last interesting property on the encryption side is the TagSize property.  This property specifies the size (in bits) of the authentication tag to generate.  The LegalTagSizes property contains information about which sizes are valid for the current chaining mode (and the ValidTagSize method allows you to quickly test to see if a tag size is valid).

Once the AuthenticatedAesCng object is setup, we'll need to create an encryptor to do the actual encryption operation.  This can be done by calling the CreateAuthenticatedEncryptor method.  CreateAuthenticatedEncryptor returns an IAuthenticatdCryptoTransform rather than an ICryptoTransform since IAuthenticatedCryptoTransform allows us access to the authentication tag after the encryption is done.  The CreateEncryptor overloads also return IAuthenticatedCryptoTransforms, however they are typed as ICryptoTransform because they're defined on the SymmetricAlgorithm base type.  If you call one of the these methods, then you'll have to manually cast to IAuthenticatedCryptoTransform.

Putting this all together, code to encrypt and generate an authentication tag using AuthenticatedAesCng would look something like this:

using (AuthenticatedAesCng aes = new AuthenticatedAesCng())

{

    // Setup an authenticated chaining mode - The two current CNG options are

    // CngChainingMode.Gcm and CngChainingMode.Ccm. This should be done before setting up

    // the other properties, since changing the chaining mode can update things such as the

    // valid and current tag sizes.

    aes.CngMode = CngChainingMode.Ccm;

 

    // Keys work the same as standard AES

    aes.Key = key;

 

    // The IV (called the nonce in many of the authenticated algorithm specs) is not sized for

    // the input block size. Instead its size depends upon the algorithm. 12 bytes works

    // for both GCM and CCM. Generate a random 12 byte nonce here.

    nonce = new byte[12];

    rng.GetBytes(nonce);

    aes.IV = nonce;

 

    // Authenticated data becomes part of the authentication tag that is generated during

    // encryption, however it is not part of the ciphertext. That is, when decrypting the

    // ciphertext the authenticated data will not be produced. However, if the

    // authenticated data does not match at encryption and decryption time, the

    // authentication tag will not validate.

    aes.AuthenticatedData = Encoding.UTF8.GetBytes("Additional authenticated data");

 

    // Perform the encryption - this works nearly the same as standard symmetric encryption,

    // however instead of using an ICryptoTransform we use an IAuthenticatedCryptoTrasform

    // which provides access to the authentication tag.

    using (MemoryStream ms = new MemoryStream())

    using (IAuthenticatedCryptoTransform encryptor = aes.CreateAuthenticatedEncryptor())

    using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))

    {

        // Encrypt the secret message

        byte[] plaintext = Encoding.UTF8.GetBytes("Secret data to be encrypted and authenticated.");

        cs.Write(plaintext, 0, plaintext.Length);

 

        // Finish the encryption and get the output authentication tag and ciphertext

        cs.FlushFinalBlock();

        tag = encryptor.GetTag();

        ciphertext = ms.ToArray();

    }

}

Notice how the tag is accessed by calling GetTag on the IAuthenticatedCryptoTransform after we've completed all encryption and flushed the final block.  If GetTag is called before this, it will throw an InvalidOperaitonException as AuthenticatedAesCng does not support generating partial tags for partially encrypted data.  Also, encryption will notably not set the Tag property of the AuthenticatedAesCng object which was used to create the encryptor.  (The tag is an output, not an input, and therefore does not get propagated back to the AuthenticatedAesCng object which acts like a crypto transform factory).

Setting up an Authenticated AES Decryptor

Setting up an authenticated AES object to do decryption is very similar to setting one up to do encryption.  The Key, IV, AuthenticationData, and CngMode all need to be setup to match the parameters in place when the ciphertext being decrypted was encrypted.  The only additional property that needs to be set is the Tag property.  Unsurprisingly, this should be set to be the output of the GetTag call on the encryptor.

We'll end up with decryption code along the lines of:

// To decrypt, we need to know the nonce, key, additional authenticated data, and

// authentication tag.

using (AuthenticatedAesCng aes = new AuthenticatedAesCng())

{

    // Chaining modes, keys, and IVs must match between encryption and decryption

    aes.CngMode = CngChainingMode.Ccm;

    aes.Key = key;

    aes.IV = nonce;

 

    // If the authenticated data does not match between encryption and decryption, then the

    // authentication tag will not match either, and the decryption operation will fail.

    aes.AuthenticatedData = Encoding.UTF8.GetBytes("Additional authenticated data");

 

    // The tag that was generated during encryption gets set here as input to the decryption

    // operation. This is in contrast to the encryption code path which does not use the

    // Tag property (since it is an output from encryption).

    aes.Tag = tag;

 

    // Decryption works the same as standard symmetric encryption

    using (MemoryStream ms = new MemoryStream())

    using (CryptoStream cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write))

    {

        cs.Write(ciphertext, 0, ciphertext.Length);

 

        // If the authentication tag does not match, we'll fail here with a

        // CryptographicException, and the ciphertext will not be decrypted.

        cs.FlushFinalBlock();

 

        byte[] plaintext = ms.ToArray();

        Console.WriteLine("Decrypted and authenticated message: {0}", Encoding.UTF8.GetString(plaintext));

    }

}

If the authentication tag generated while decrypting the ciphertext (taking into account any optional authenticated data provided), then an exception will be thrown when decryption is completed.  The decryptor will also not produce any plaintext until the authentication tag is verified - this way partial plaintext cannoot be used if the authentication tag does not match.

Now that we're using AuthenticatedAesCng to do our encryption, the scenario where someone tampers the ciphertext no longer works.  While we won't be able to access the corrupted plaintext anymore, we also will be unable to mistake it for valid plaintext.  If the authentication tag does not match while decrypting (most commonly because the ciphertext was tampered with or the authenticated data was not correct), the following exception is thrown:

Unhandled Exception: System.Security.Cryptography.CryptographicException: The computed authentication tag did not match the input authentication tag.

   at Security.Cryptography.BCryptNative.SymmetricDecrypt(SafeBCryptKeyHandle key, Byte[] input, Byte[] chainData, BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO& authenticationInfo)
   at Security.Cryptography.BCryptAuthenticatedSymmetricCryptoTransform.CngTransform(Byte[] input, Int32 inputOffset, Int32 inputCount)
   at Security.Cryptography.BCryptAuthenticatedSymmetricCryptoTransform.TransformFinalBlock(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount)
   at System.Security.Cryptography.CryptoStream.FlushFinalBlock()

Security.Cryptography.Debug Support

The Security.Cryptography.Debug library has also been updated on the Codeplex in order to support the same type of debugging of AuthenicatedSymmetricAlgorithm objects that was already supported for SymmetricAlgorithm objects.  This support is enabled in the v1.1 release and higher of Security.Cryptography.Debug.dll - but since it requires a dependency on the Security.Cryptography.dll library, it is not enabled in the FxOnly builds.  Instead, you'll need to download the full binary package or build the sources manually to get the AuthenticatedSymmetricAlgorithm debugging support.