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 nonce = stackalloc byte[AesGcm.NonceByteSizes.MaxSize]; RandomNumberGenerator.Fill(nonce); Span ciphertext = stackalloc byte[plaintext.Length]; Span tag = stackalloc byte[tagSize]; 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; } // -------------------------------------------------------------------- // DecryptAesGcm // • key – 32‑byte (256‑bit) 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) // 256‑bit key throw new ArgumentException("Key must be 256 bits (32 bytes) for AES‑256‑GCM.", nameof(key)); // ---------------------------------------------------------------- // 1️⃣ Extract the three components. // ---------------------------------------------------------------- // AesGcm.NonceByteSizes.MaxSize = 12 bytes (standard GCM nonce length) // AesGcm.TagByteSizes.MaxSize = 16 bytes (128‑bit 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 nonce = new(payload, 0, nonceSize); ReadOnlySpan ciphertext = new(payload, nonceSize, payload.Length - nonceSize - tagSize); ReadOnlySpan tag = new(payload, payload.Length - tagSize, tagSize); byte[] plaintext = new byte[ciphertext.Length]; using var aes = new AesGcm(key, tagSize); 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; } }