Question

Encrypt and Decrypt Strings in C# with AES-GCM

csharp.netencryptionmonocryptography

Question

How can I securely encrypt a string in C# and later decrypt it back to its original value? What encryption approach should I use, and how should I handle the encryption key, nonce, and encoded output?

Short Answer

You will learn that reversible encryption requires a secret key, why authenticated encryption is important, and how to encrypt UTF-8 strings with AES-GCM in modern C#. You will also learn when not to decrypt data, such as when storing user passwords.

Concept

Encryption transforms readable plaintext into unreadable ciphertext using a secret key. Decryption uses the same secret key to restore the original plaintext.

For new C# applications, AES-GCM is a strong default for encrypting small pieces of data such as tokens, private configuration values, or sensitive fields. It provides two protections:

  • Confidentiality: people without the key cannot read the ciphertext.
  • Integrity and authenticity: changes to the ciphertext are detected during decryption.

AES-GCM is a symmetric encryption algorithm: the same key is used for encryption and decryption. This means protecting the key is as important as protecting the encrypted data.

A secure encryption operation needs:

  • A random key: secret and kept outside source code.
  • A fresh random nonce for every encryption with the same key. A nonce is not secret, but it must not be reused with that key.
  • A ciphertext: the encrypted bytes.
  • An authentication tag: data used to detect tampering.

Do not use encryption for passwords. Applications should verify passwords, not recover them. Store passwords with a purpose-built password hashing algorithm such as PBKDF2, bcrypt, scrypt, or Argon2 instead.

Mental Model

Think of encryption as putting a message into a locked security box:

  • The plaintext is the message you can read.
  • The key is the unique key that locks and unlocks the box.
  • The nonce is a one-time packing label. It ensures that encrypting the same message twice does not produce the same result.
  • The authentication tag is a tamper-evident seal. If someone changes the box or its contents, the seal no longer verifies.
  • The ciphertext is the locked, unreadable message.

You may store the packing label and the seal beside the box. However, anyone who gets the key can open every box encrypted with that key.

Take Quiz

Syntax and Examples

AES-GCM works with bytes, so convert strings to UTF-8 bytes before encryption and convert them back after decryption.

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

byte[] key = RandomNumberGenerator.GetBytes(32); // 256-bit key
byte[] nonce = RandomNumberGenerator.GetBytes(12);
byte[] plaintext = Encoding.UTF8.GetBytes("Hello, C#!");
byte[] ciphertext = new byte[plaintext.Length];
byte[] tag = new byte[16];

using (var aes = new AesGcm(key, tagSizeInBytes: 16))
{
    aes.Encrypt(nonce, plaintext, ciphertext, tag);
}

Console.WriteLine(Convert.ToBase64String(ciphertext));

byte[] decrypted = new byte[ciphertext.Length];
using (var aes = new AesGcm(key, tagSizeInBytes: 16))
{
    aes.Decrypt(nonce, ciphertext, tag, decrypted);
}

Console.WriteLine(Encoding.UTF8.GetString(decrypted));

ciphertext, nonce, and tag are binary data. Base64 is commonly used to put binary data into JSON, a database text column, or a configuration file. Base64 is encoding, not encryption.

The example creates a random key only for demonstration. If the program exits, that key is lost and the ciphertext can no longer be decrypted. A real application must load the key from secure key storage.

Step by Step Execution

Consider this small encryption flow:

string message = "Order: 42";
byte[] plaintext = Encoding.UTF8.GetBytes(message);
byte[] nonce = RandomNumberGenerator.GetBytes(12);
byte[] ciphertext = new byte[plaintext.Length];
byte[] tag = new byte[16];

using var aes = new AesGcm(key, tagSizeInBytes: 16);
aes.Encrypt(nonce, plaintext, ciphertext, tag);
  1. message is ordinary readable text.
  2. Encoding.UTF8.GetBytes converts the text into bytes because cryptographic APIs operate on bytes.
  3. A new 12-byte random nonce is created. Generate a new one for every call to Encrypt when using the same key.
  4. ciphertext is allocated with the same length as the plaintext. AES-GCM does not expand this byte array; its separate tag has a fixed length.
  5. tag is allocated to hold the authentication data.
  6. Encrypt fills ciphertext and tag.

To decrypt, provide the same key, nonce, ciphertext, and tag:

byte[] decrypted = new byte[ciphertext.Length];
aes.Decrypt(nonce, ciphertext, tag, decrypted);
string originalMessage = Encoding.UTF8.GetString(decrypted);

If the wrong key, nonce, tag, or ciphertext is supplied, Decrypt throws CryptographicException. Treat that as invalid or tampered data; do not attempt to use partial output.

Real World Use Cases

Encryption is appropriate when an application genuinely needs to recover the original value later.

  • Encrypt an API credential before storing it in a database, then decrypt it only when calling the external service.
  • Encrypt sensitive application settings at rest.
  • Protect a private note or document before saving it to local storage.
  • Encrypt a short secret included in a backup export.
  • Protect data exchanged between trusted components when an additional application-level encryption layer is required.

For data in transit, use HTTPS/TLS rather than creating custom encryption around every HTTP request. For full-database or disk protection, use the platform or service encryption features as well as appropriate access controls.

Take Quiz

Real Codebase Usage

In production code, encryption is usually wrapped in a small service rather than repeated throughout controllers or UI code.

Common practices include:

  • Load keys from a secret manager, environment-specific protected configuration, cloud key vault, or hardware-backed key store.
  • Store an encryption key identifier with encrypted records so keys can be rotated later.
  • Include the nonce and tag with each ciphertext. They are required for decryption but do not need secrecy.
  • Use a versioned payload format such as v1:keyId:base64-data so the format can evolve safely.
  • Validate null or empty input before processing it.
  • Catch CryptographicException at an application boundary and return a safe error such as “encrypted value is invalid.” Do not expose key details or plaintext in logs.
  • Restrict which service accounts can read or use the key.

A typical design stores a record like this:

Version: 1
KeyId: customer-data-2025
Payload: Base64(nonce || tag || ciphertext)

The application gets the correct key using KeyId, then parses and decrypts Payload. This makes key rotation possible without guessing which key was used.

Common Mistakes

Hard-coding a key

// Do not do this.
byte[] key = Encoding.UTF8.GetBytes("my-secret-key-123");

Source code is copied, committed, logged, and shared. Keep keys in a secure secret store. Also, this example may not have a valid AES key length.

Reusing an AES-GCM nonce

// Unsafe: the nonce must not be reused with the same AES-GCM key.
byte[] nonce = new byte[12];

An all-zero nonce is repeated every time. Generate a fresh nonce with RandomNumberGenerator.GetBytes(12) for each encryption.

Omitting the authentication tag

Using an unauthenticated mode or discarding the tag can allow modified ciphertext to be accepted. Store the tag and verify it with Decrypt.

Treating Base64 as security

string encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes("secret"));

Anyone can decode this. Base64 changes representation; it does not hide data.

Encrypting passwords

If an attacker obtains an encryption key, encrypted passwords can be recovered. Use a salted password hash and a password-verification API instead.

Losing the key or nonce

Without the original key, decryption is impossible by design. Without the nonce and tag, an AES-GCM payload cannot be decrypted or authenticated. Store required metadata alongside the ciphertext.

Using Random for cryptographic values

Random is not a cryptographically secure random-number generator. Use RandomNumberGenerator for keys, nonces, salts, and tokens.

Comparisons

TechniqueCan recover original value?Suitable for secrets at rest?Main use
AES-GCM encryptionYes, with the keyYesValues that must later be read
Base64 encodingYes, by anyoneNoRepresent binary data as text
HashingNoFor password verificationCompare a supplied value with a stored verifier
Password hashingNoYes, for passwordsSlow, salted password storage
RSA encryptionUsually, with private keySometimesPublic-key exchange and small payloads
AES-CBCAES-GCM
Provides encryption but needs separate integrity protection, such as HMAC.Provides encryption and authentication together.
Requires careful padding and IV handling.Does not use padding; requires unique nonces.
Easy to use incorrectly in custom code.Usually preferred for new symmetric-encryption designs.

AES-GCM is not automatically the right choice for every storage problem. For example, ASP.NET Core Data Protection is often more convenient for application-managed protected values, while password hashes are required for user passwords.

Take Quiz

Cheat Sheet

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

byte[] key = RandomNumberGenerator.GetBytes(32); // AES-256 key
byte[] nonce = RandomNumberGenerator.GetBytes(12); // new for every encryption
byte[] tag = new byte[16];
byte[] plaintext = Encoding.UTF8.GetBytes("text");
byte[] ciphertext = new byte[plaintext.Length];

using var aes = new AesGcm(key, tagSizeInBytes: 16);
aes.Encrypt(nonce, plaintext, ciphertext, tag);
  • AES key sizes: 16, 24, or 32 bytes. Use 32 bytes for AES-256 when appropriate.
  • AES-GCM nonce: commonly 12 bytes; never reuse it with the same key.
  • Authentication tag: commonly 16 bytes.
  • Save: nonce + tag + ciphertext and any key/version metadata.
  • Encode binary payloads with Convert.ToBase64String for text storage.
  • Decode with Convert.FromBase64String before decryption.
  • Expect CryptographicException for a wrong key or modified payload.
  • Never hard-code keys, log plaintext secrets, or use reversible encryption for passwords.
  • AesGcm is available in modern .NET. Ensure the target framework supports it.

FAQ

Can I encrypt a string directly in C#?

Cryptographic APIs encrypt bytes, not .NET strings. Convert the string to UTF-8 bytes before encryption and convert decrypted bytes back with UTF-8.

Can I use the same AES key to encrypt many strings?

Yes, but every AES-GCM encryption using that key must use a new unique nonce. Generate a random nonce for each operation and store it with the ciphertext.

Should the nonce be secret?

No. The nonce is required for decryption and can be stored alongside the ciphertext. Its uniqueness, not secrecy, is the important property.

Can I store encrypted data as a string in a database?

Yes. Combine the nonce, tag, and ciphertext, then Base64-encode the combined bytes. Store key/version metadata separately or in a structured payload.

Why does AES-GCM decryption throw CryptographicException?

The key may be incorrect, or the nonce, tag, or ciphertext may have changed. Treat the payload as invalid rather than trying to recover it.

Is AES encryption appropriate for user passwords?

No. Passwords should be salted and hashed with a password-hashing algorithm so the original password cannot be recovered.

Where should I store the encryption key?

Use a managed secret store or platform protection mechanism, such as a cloud key vault, OS credential store, or protected production configuration. Do not put it in source code or a public configuration file.

Take Quiz

Mini Project

Description

Build a small C# console utility that protects a private note with AES-GCM. It creates a payload that contains the nonce, authentication tag, and ciphertext, then decrypts the payload to prove that the original note can be recovered only with the correct key.

Goal

Encrypt a user-provided note into a Base64 payload and decrypt it back to the original text.

Requirements

Use AES-GCM with a randomly generated 32-byte key. Generate a new 12-byte nonce for encryption. Store the nonce, 16-byte tag, and ciphertext in one Base64-encoded payload. Decrypt the payload and display the recovered note. Handle tampered or invalid payloads safely.

Take Quiz

Keep learning

Related questions

View more