Improve the binary storage format of encrypted secrets. Move the related code from AesGcmHelper into the EncryptionService.

This commit is contained in:
eelke 2026-02-22 19:11:17 +01:00
parent ac08956339
commit 4201d0240d
11 changed files with 110 additions and 131 deletions

View file

@ -1,70 +0,0 @@
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 32byte (256bit) 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) // 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 = 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;
}
}

View file

@ -1,16 +0,0 @@
using System.Security.Cryptography;
namespace IdentityShroud.Core.Security;
public static class RsaHelper
{
/// <summary>
/// Load RSA private key from PKCS#8 format
/// </summary>
public static RSA LoadFromPkcs8(byte[] pkcs8Key)
{
var rsa = RSA.Create();
rsa.ImportPkcs8PrivateKey(pkcs8Key, out _);
return rsa;
}
}