From a687110703622c8365ddbadc1a98fe4472617c6e Mon Sep 17 00:00:00 2001 From: paragon Date: Wed, 27 May 2026 19:41:53 -0400 Subject: [PATCH] Add protected memory sample --- .../ProtectedMemorySampleController.cs | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 src/TriunniaLabs.Arch.Sample.Server/Controllers/ProtectedMemorySampleController.cs diff --git a/src/TriunniaLabs.Arch.Sample.Server/Controllers/ProtectedMemorySampleController.cs b/src/TriunniaLabs.Arch.Sample.Server/Controllers/ProtectedMemorySampleController.cs new file mode 100644 index 0000000..ade7685 --- /dev/null +++ b/src/TriunniaLabs.Arch.Sample.Server/Controllers/ProtectedMemorySampleController.cs @@ -0,0 +1,90 @@ +using TriunniaLabs.Arch.Microservice; +using TriunniaLabs.Memory.Protected; + +namespace TriunniaLabs.Arch.Sample.Server +{ + /// + /// A controller that shows how to work with the Protected Memory system. + /// + public class ProtectedMemorySampleController : ArchControllerBase + { + // Services + private readonly IProtectorFactory _protectorFactory; + private readonly IProtector _stringProtector; + + /// + /// Constructor. + /// + /// Reference to the microservice context. + /// Reference to the protector factory. + /// Reference to the string protector. + public ProtectedMemorySampleController(IMicroserviceContext microserviceContext, + IProtectorFactory protectorFactory, + [FromKeyedServices("MyProtectorServiceKey")] IProtector stringProtector) + : base(microserviceContext) + { + _stringProtector = stringProtector ?? throw new ArgumentNullException(nameof(stringProtector)); + _protectorFactory = protectorFactory ?? throw new ArgumentNullException(nameof(protectorFactory)); + } + + /// + /// A sample method that shows how to work with the Protected Memory system. + /// + internal async Task SampleAsync() + { + // In Arch, all data is encrypted in memory via the Protected type. This encryption extends to the database and data transfer objects. + var myString = "My String Value"; + var myProtectedString = new Protected(myString); + + // The hash of a protected type's value is available for equality checking without exposing the encrypted value + var hash = myProtectedString.Hash; + + // Decrypt the protected type to get its value + var value = myProtectedString.GetDecryptedValue(); // "My String Value" + + // Or decrypt just enough to get the bytes (from the byte array converter) without restoring the actual value. + var bytes = myProtectedString.GetDecryptedValueAsBytes(); + + // The protected type can be serialized just by calling ToString(), and this is what is used in appsettings.json or in the database. + // The serialized string is completely encrypted. It cannot be decrypted without a matching configuration. + var serializedString = myProtectedString.ToString(); + + // The static FromEncrypted method can be used to deserialize + myProtectedString = Protected.FromEncrypted(serializedString); + + // Custom protections can be specified as well. This specific arrangement is called Standard Protection. + // It is the default that is used when no other algorithms are specified. + var byteArrayConverter = MessagePackByteArrayConverter.Instance; + var sha256HashAlgorithm = Sha256HashAlgorithm.Instance; + var aesEncryption = AesHashEncryptionAlgorithm.Instance; + var stringConverter = Base64StringConverter.Instance; + myProtectedString = new Protected(myString, byteArrayConverter, sha256HashAlgorithm, [aesEncryption], stringConverter); + + // Use Protectors to protect values with pre-defined custom configurations + var protector = _protectorFactory.CreateProtector(MessagePackByteArrayConverter.ServiceKey, + Sha256HashAlgorithm.ServiceKey, [AesEncryptionAlgorithm.ServiceKey], + Base64StringConverter.ServiceKey); + + // Protect the value using the protector + myProtectedString = protector.Protect(myString); + + // Protectors are also used to receive serialized data + myProtectedString = protector.Receive(serializedString); + myProtectedString = _stringProtector.Receive(serializedString); // They can also be injected + } + + private class MyProtectedSample + { + /// + /// Protector definitions are automatically scanned by the library and added to the service container. + /// If an algorithm or converter isn't specified, the default is used. + /// + [ByteArrayConverter("MessagePackByteArrayConverter.ServiceKey")] + [HashAlgorithm(HmacSha512HashAlgorithm.ServiceKey)] + [EncryptionAlgorithm(AesEncryptionAlgorithm.ServiceKey, Order = 1)] + [StringConverter(Base64StringConverter.ServiceKey)] + [Protector("MyProtectorServiceKey")] // Identifies the protector in the service collection + public string Value { get; set; } = ""; + } + } +}