70 lines
No EOL
3 KiB
C#
70 lines
No EOL
3 KiB
C#
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];
|
||
|
||
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(ReadOnlyMemory<byte> payload, byte[] key)
|
||
{
|
||
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 = payload.Span[..nonceSize];
|
||
ReadOnlySpan<byte> ciphertext = payload.Span.Slice(nonceSize, payload.Length - nonceSize - tagSize);
|
||
ReadOnlySpan<byte> tag = payload.Span.Slice(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;
|
||
}
|
||
} |