AES Encryption in .NET Application (Part 2)

Beyond Confidentiality and Integrity

In the previous blog post, our main concern was safeguarding the confidentiality of information.
The AES’s Cipher Block Chaining (CBC) mode offers protection against unauthorized access to plaintext. However, verifying that the decrypted ciphertext originates from an authorized sender with the secret key rather than a malicious entity is equally crucial. And that’s the key we are looking for.

Authenticity

Even though we can check for data authenticity on plaintext (before the encryption) or ciphertext (after the encryption).
Here, we focus on the encrypt-then-sign paradigm (also exist sign-then-encrypt). In short description, this paradigm proves that the content received is precisely the content that was sent and signed after encryption by the person we expected.

In developing software, there are two common approaches:

  • Using a Secret-Key Hash Function: Apply a hash function with a secret key to the output of the encryption cipher. Maintain two private keys: one for encryption and another for hash verification. For instance, we can apply SHA256 with a secret key on the result of the AES CBC cipher, often referred to as AES-256-CBC-HMAC-SHA-256.
  • Authenticated Encryption (AE) Cipher: Utilize an Authenticated Encryption cipher that ensures confidentiality and authenticity using a single secret key. AES-256 in GCM (Galois/Counter Mode) mode is a suitable option. Importantly, .NET offers a managed class for GCM.

Enhanced implementation using AES GCM

Figure 1: AesGcm class default values in .NET
Source: .NET 7 code

We will use class AesGCM() from System.Security.Cryptography namespace included in .NET 7. 

To create an instance of the class, you must provide argument encryption or decryption key 32 bytes long (allowed max block size), as I mentioned in the previous blog post. We also have Nonce and Tag instead of Initialization vector (IV), but the working pattern will be nearly identical.

Figure2: KeySizes for Tag and Nonce
Source: .NET 7 code


During implementation, we set the length of Nonce and Tag. I always prefer the highest value, but there is an option to personalize your choice. Changing these to lower sizes will be adverse for your cipher security.
My target was to create the most sufficient, performant, and easy-to-maintain implementation in .NET. ReadOnlySpans and Spans provide a type-safe representation of a contiguous memory region. Both of them are allocated on a stack, a potent tool in the right hands.
Stackalloc allows memory allocation on the stack, so it is also a proper way to be memory efficient.
We proceed with encrypting and decrypting in just one scope, so this approach is suited and beneficial for application performance.
Disclaimer: In C#, the default stack size (per Thread) is 1MB. I assume you will be ciphering data much smaller than this size; however, change the keyword Stackalloc to new if unsure.

Let’s have a look at the snippet of code below.

Significant differences (comparing with implementation previously):

  • The IV property is replaced by Nonce. From the application developer’s point of view, we can think of Nonce as an IV with a shorter length (96 bits instead of 128 bits). Under the hood, the Nonce is concatenated to a 32-bit integer to create the final IV. That 32-bit integer is maintained internally by the cipher.
  • The Nonce is filled by RandomNumberGenerator. It is a common practice; it is genuinely randomized, but there is a warning. Our encryption key has to be changed from time to time. The best implementation would be having a bunch of keys and juggling them. It would be more bulletproof because the only vulnerability here is that Nonce could be repeated and cause possible issues.
  • The new Tag property allows us to verify the ciphertext without decrypting it. Both Nonce and Tag must be transmitted alongside the ciphertext. IV, Nonce, and Tag do not need to be kept secret in the application (e.g., in appsettings.json).
    We stick to the paradigm encrypt-then-sign, so this is our sign-on encrypted text.
  • Using similar lines of code, we achieved all needed cryptographic selling points with a primary focus on ensuring authenticity so malicious attacks are less possible.
  • Spans are more memory-efficient than strings, and that sweetens the solution. We slice every time without allocation memory, which is powerful during runtime applications.

Conclusion

In these articles, we embarked on a journey from a simple implementation of AES encryption to an enhanced performance that ensures data confidentiality, integrity, and authenticity at an excellent level. You are familiar with traps you can meet during AES implementation.
In closing, at Aspire, security isn’t just a buzzword—it’s a core value, and we’re committed to raising the bar regarding safeguarding data and ensuring the privacy and trust of our users.
Stay tuned for more insights, innovations, and security solutions.

Tags: , , ,