chore: add hrynco common library solution
- add the standalone HrynCo.Common solution and projects - include the shared common library source and tests - add package metadata and a repo gitignore
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
namespace HrynCo.Common.Security;
|
||||
|
||||
public interface ISecretProtector
|
||||
{
|
||||
string Protect(string plaintext);
|
||||
|
||||
string Unprotect(string protectedValue);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user