// src/GeoVLog.Core/Crypto/AesGcmHelper.cs
//
// Thin wrapper over that
// enforces the GeoVLog crypto parameters:
//
// • 256-bit key (32 bytes)
// • 96-bit IV (12 bytes, NIST-recommended for GCM)
// • 128-bit authentication tag (16 bytes)
//
// Starting with .NET 8 the ctor new AesGcm(key) is **obsolete**; the
// tag size must be specified explicitly. This helper hides that detail and
// provides span-based Encrypt / Decrypt utilities with runtime checks.
using System;
using System.Security.Cryptography;
namespace GeoVLog.Core.Crypto;
///
/// AES-256-GCM encryption / decryption helpers with fixed IV and tag sizes.
///
public static class AesGcmHelper
{
/// Required key length in bytes (256 bit).
public const int KeySize = 32;
/// Required nonce / IV length in bytes (96 bit).
public const int IvSize = 12;
/// Authentication-tag length in bytes (128 bit).
public const int TagSize = 16;
///
/// Encrypts with AES-256-GCM.
///
/// Plaintext to encrypt.
/// 32-byte AES key.
///
/// Tuple containing a freshly generated IV, authentication tag, and
/// ciphertext (same length as ).
///
///
/// Thrown when length ≠ .
///
public static (byte[] iv, byte[] tag, byte[] cipher) Encrypt(
ReadOnlySpan plain,
ReadOnlySpan key)
{
if (key.Length != KeySize)
throw new ArgumentException("AES-256 key must be 32 bytes.", nameof(key));
byte[] iv = RandomNumberGenerator.GetBytes(IvSize);
byte[] tag = new byte[TagSize];
byte[] cipher = new byte[plain.Length];
// Specify tag size to silence .NET 8 obsoletion warning
using var aes = new AesGcm(key, TagSize);
aes.Encrypt(iv, plain, cipher, tag);
return (iv, tag, cipher);
}
///
/// Decrypts a GCM payload and verifies its authentication tag.
///
/// Ciphertext bytes.
/// 32-byte AES key.
/// 12-byte nonce used during encryption.
/// 16-byte authentication tag.
/// Plaintext bytes.
///
/// Thrown when tag verification fails.
///
///
/// Thrown when any input buffer does not match expected size.
///
public static byte[] Decrypt(
ReadOnlySpan cipher,
ReadOnlySpan key,
ReadOnlySpan iv,
ReadOnlySpan tag)
{
if (key.Length != KeySize) throw new ArgumentException("Key must be 32 bytes.");
if (iv.Length != IvSize) throw new ArgumentException("IV must be 12 bytes.");
if (tag.Length != TagSize) throw new ArgumentException("Tag must be 16 bytes.");
byte[] plain = new byte[cipher.Length];
using var aes = new AesGcm(key, TagSize);
aes.Decrypt(iv, cipher, tag, plain);
return plain;
}
}