85b362e8cd
- add the standalone HrynCo.Common solution and projects - include the shared common library source and tests - add package metadata and a repo gitignore
62 lines
2.0 KiB
C#
62 lines
2.0 KiB
C#
namespace HrynCo.Common.Security;
|
|
|
|
using System;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
|
|
public sealed class SecretProtector : ISecretProtector
|
|
{
|
|
private readonly byte[] _key;
|
|
|
|
public SecretProtector(string encryptionKey)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(encryptionKey))
|
|
{
|
|
throw new InvalidOperationException("Secret encryption key is not configured.");
|
|
}
|
|
|
|
_key = Convert.FromBase64String(encryptionKey);
|
|
if (_key.Length != 32)
|
|
{
|
|
throw new InvalidOperationException("Secret encryption key must be 32 bytes encoded as Base64.");
|
|
}
|
|
}
|
|
|
|
public string Protect(string plaintext)
|
|
{
|
|
byte[] nonce = RandomNumberGenerator.GetBytes(12);
|
|
byte[] plaintextBytes = Encoding.UTF8.GetBytes(plaintext);
|
|
byte[] ciphertext = new byte[plaintextBytes.Length];
|
|
byte[] tag = new byte[16];
|
|
|
|
using var aes = new AesGcm(_key, tagSizeInBytes: 16);
|
|
aes.Encrypt(nonce, plaintextBytes, ciphertext, tag);
|
|
|
|
byte[] payload = new byte[nonce.Length + tag.Length + ciphertext.Length];
|
|
Buffer.BlockCopy(nonce, 0, payload, 0, nonce.Length);
|
|
Buffer.BlockCopy(tag, 0, payload, nonce.Length, tag.Length);
|
|
Buffer.BlockCopy(ciphertext, 0, payload, nonce.Length + tag.Length, ciphertext.Length);
|
|
|
|
return $"v1:{Convert.ToBase64String(payload)}";
|
|
}
|
|
|
|
public string Unprotect(string protectedValue)
|
|
{
|
|
if (!protectedValue.StartsWith("v1:", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException("Unsupported protected value format.");
|
|
}
|
|
|
|
byte[] payload = Convert.FromBase64String(protectedValue[3..]);
|
|
byte[] nonce = payload[..12];
|
|
byte[] tag = payload[12..28];
|
|
byte[] ciphertext = payload[28..];
|
|
byte[] plaintext = new byte[ciphertext.Length];
|
|
|
|
using var aes = new AesGcm(_key, tagSizeInBytes: 16);
|
|
aes.Decrypt(nonce, ciphertext, tag, plaintext);
|
|
|
|
return Encoding.UTF8.GetString(plaintext);
|
|
}
|
|
}
|