IdentityShroud/IdentityShroud.Core/Security/AesGcmHelper.cs

71 lines
3.1 KiB
C#
Raw Normal View History

2026-02-06 19:58:01 +01:00
using System.Security.Cryptography;
namespace IdentityShroud.Core.Security;
public static class AesGcmHelper
{
public static byte[] EncryptAesGcm(byte[] plaintext, byte[] key)
{
int tagSize = AesGcm.TagByteSizes.MaxSize;
using var aes = new AesGcm(key, tagSize);
Span<byte> nonce = stackalloc byte[AesGcm.NonceByteSizes.MaxSize];
RandomNumberGenerator.Fill(nonce);
Span<byte> ciphertext = stackalloc byte[plaintext.Length];
Span<byte> tag = stackalloc byte[tagSize];
2026-02-06 19:58:01 +01:00
aes.Encrypt(nonce, plaintext, ciphertext, tag);
// Return concatenated nonce|ciphertext|tag
var result = new byte[nonce.Length + ciphertext.Length + tag.Length];
nonce.CopyTo(result.AsSpan(0, nonce.Length));
ciphertext.CopyTo(result.AsSpan(nonce.Length, ciphertext.Length));
tag.CopyTo(result.AsSpan(nonce.Length + ciphertext.Length, tag.Length));
return result;
2026-02-06 19:58:01 +01:00
}
// --------------------------------------------------------------------
// DecryptAesGcm
// • key 32byte (256bit) secret key (same key used for encryption)
// • payload byte[] containing nonce‖ciphertext‖tag
// • returns the original plaintext bytes
// --------------------------------------------------------------------
public static byte[] DecryptAesGcm(byte[] payload, byte[] key)
{
if (payload == null) throw new ArgumentNullException(nameof(payload));
if (key == null) throw new ArgumentNullException(nameof(key));
if (key.Length != 32) // 256bit key
throw new ArgumentException("Key must be 256bits (32 bytes) for AES256GCM.", nameof(key));
// ----------------------------------------------------------------
// 1⃣ Extract the three components.
// ----------------------------------------------------------------
// AesGcm.NonceByteSizes.MaxSize = 12 bytes (standard GCM nonce length)
// AesGcm.TagByteSizes.MaxSize = 16 bytes (128bit authentication tag)
int nonceSize = AesGcm.NonceByteSizes.MaxSize; // 12
int tagSize = AesGcm.TagByteSizes.MaxSize; // 16
if (payload.Length < nonceSize + tagSize)
throw new ArgumentException("Payload is too short to contain nonce, ciphertext, and tag.", nameof(payload));
ReadOnlySpan<byte> nonce = new(payload, 0, nonceSize);
ReadOnlySpan<byte> ciphertext = new(payload, nonceSize, payload.Length - nonceSize - tagSize);
ReadOnlySpan<byte> tag = new(payload, payload.Length - tagSize, tagSize);
byte[] plaintext = new byte[ciphertext.Length];
using var aes = new AesGcm(key, tagSize);
2026-02-06 19:58:01 +01:00
try
{
aes.Decrypt(nonce, ciphertext, tag, plaintext);
}
catch (CryptographicException ex)
{
// Tag verification failed → tampering or wrong key/nonce.
throw new InvalidOperationException("Decryption failed authentication tag mismatch.", ex);
}
return plaintext;
}
}