64 lines
2.7 KiB
C#
64 lines
2.7 KiB
C#
|
|
using System.Security.Cryptography;
|
|||
|
|
|
|||
|
|
namespace IdentityShroud.Core.Security;
|
|||
|
|
|
|||
|
|
public static class AesGcmHelper
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
public static byte[] EncryptAesGcm(byte[] plaintext, byte[] key)
|
|||
|
|
{
|
|||
|
|
using var aes = new AesGcm(key);
|
|||
|
|
byte[] nonce = RandomNumberGenerator.GetBytes(AesGcm.NonceByteSizes.MaxSize);
|
|||
|
|
byte[] ciphertext = new byte[plaintext.Length];
|
|||
|
|
byte[] tag = new byte[AesGcm.TagByteSizes.MaxSize];
|
|||
|
|
|
|||
|
|
aes.Encrypt(nonce, plaintext, ciphertext, tag);
|
|||
|
|
// Return concatenated nonce|ciphertext|tag (or store separately)
|
|||
|
|
return nonce.Concat(ciphertext).Concat(tag).ToArray();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --------------------------------------------------------------------
|
|||
|
|
// 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<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);
|
|||
|
|
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;
|
|||
|
|
}
|
|||
|
|
}
|