Added application - building out readme

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
This commit is contained in:
Whit Waldo 2025-01-15 22:06:09 -06:00
parent 0727d1d041
commit 800db8452c
7 changed files with 247 additions and 3 deletions

View File

@ -0,0 +1,3 @@
# Dapr cryptography
In this quickstart, you'll create a service that demonstrates

View File

@ -1,2 +1,106 @@
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
using System.Security.Cryptography;
using cryptography;
using Microsoft.Extensions.DependencyInjection.Extensions;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDaprClient();
builder.Services.TryAddSingleton<StringCryptographyOperations>();
builder.Services.TryAddSingleton<StreamCryptographyOperations>();
var app = builder.Build();
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var logger = app.Services.GetRequiredService<ILogger<Program>>();
//Encrypt a string value
var stringOps = app.Services.GetRequiredService<StringCryptographyOperations>();
const string plaintextValue = "P@assw0rd";
var encryptedBase64String = await stringOps.EncryptAsync(plaintextValue, cancellationTokenSource.Token);
var decryptedBase64String = await stringOps.DecryptAsync(encryptedBase64String, cancellationTokenSource.Token);
Log.LogStringEncryption(logger, plaintextValue, decryptedBase64String);
//Encrypt a file
var testFilePath = await FileGenerator.GenerateSmallTestFileAsync(cancellationTokenSource.Token);
var streamOps = app.Services.GetRequiredService<StreamCryptographyOperations>();
var encryptedFileBytes = await streamOps.EncryptAsync(testFilePath, cancellationTokenSource.Token);
using var encryptedMs = new MemoryStream(Convert.FromBase64String(encryptedFileBytes));
var decryptedFilePath = Path.GetTempFileName();
await streamOps.DecryptAsync(encryptedMs, decryptedFilePath, cancellationTokenSource.Token);
var areIdentical = await FileValidator.AreIdentical(testFilePath, decryptedFilePath);
Log.LogStreamEncryption(logger, testFilePath, decryptedFilePath, areIdentical);
//Clean up the created files
File.Delete(testFilePath);
File.Delete(decryptedFilePath);
static partial class Log
{
//It should go without saying that you should not log your plaintext values in production - this is for
//demonstration purposes only.
[LoggerMessage(LogLevel.Information, "Encrypted string from plaintext value '{plaintextValue}' and decrypted to '{decryptedValue}'")]
internal static partial void LogStringEncryption(ILogger logger, string plaintextValue, string decryptedValue);
[LoggerMessage(LogLevel.Information, "Encrypted from file stream '{plaintextFilePath}' and decrypted back from an in-memory stream to a file '{decryptedFilePath}' and the validation check returns '{areIdentical}'")]
internal static partial void LogStreamEncryption(ILogger logger, string plaintextFilePath, string decryptedFilePath, bool areIdentical);
}
static class Constants
{
public const string ComponentName = "localstorage";
public const string KeyName = "rsa-private-key.pem";
}
static class FileGenerator
{
public static async Task<string> GenerateSmallTestFileAsync(CancellationToken cancellationToken)
{
var tempFilePath = Path.GetTempFileName();
await File.WriteAllTextAsync(tempFilePath, """
# The Road Not Taken
## By Robert Lee Frost
Two roads diverged in a yellow wood,
And sorry I could not travel both
And be one traveler, long I stood
And looked down one as far as I could
To where it bent in the undergrowth;
Then took the other, as just as fair
And having perhaps the better claim,
Because it was grassy and wanted wear;
Though as for that, the passing there
Had worn them really about the same,
And both that morning equally lay
In leaves no step had trodden black
Oh, I kept the first for another day!
Yet knowing how way leads on to way,
I doubted if I should ever come back.
I shall be telling this with a sigh
Somewhere ages and ages hence:
Two roads diverged in a wood, and I,
I took the one less traveled by,
And that has made all the difference.
""", cancellationToken);
return tempFilePath;
}
}
static class FileValidator
{
public static async Task<bool> AreIdentical(string path1, string path2)
{
await using var path1Reader = new FileStream(path1, FileMode.Open);
await using var path2Reader = new FileStream(path2, FileMode.Open);
using var md5 = MD5.Create();
var file1Hash = await md5.ComputeHashAsync(path1Reader);
var file2Hash = await md5.ComputeHashAsync(path2Reader);
return file1Hash.SequenceEqual(file2Hash);
}
}

View File

@ -0,0 +1,48 @@
using System.Buffers;
using Dapr.Client;
namespace cryptography;
internal sealed partial class StreamCryptographyOperations(DaprClient daprClient, ILogger<StreamCryptographyOperations> logger)
{
public async Task<string> EncryptAsync(
string filePath,
CancellationToken cancellationToken)
{
await using var sourceFile = new FileStream(filePath, FileMode.Open);
var bufferedEncryptedBytes = new ArrayBufferWriter<byte>();
await foreach (var bytes in (await daprClient.EncryptAsync(Constants.ComponentName, sourceFile,
Constants.KeyName, new EncryptionOptions(KeyWrapAlgorithm.Rsa), cancellationToken))
.WithCancellation(cancellationToken))
{
bufferedEncryptedBytes.Write(bytes.Span);
}
sourceFile.Close();
LogEncryptFile(logger, filePath, bufferedEncryptedBytes.WrittenMemory.Span.Length);
var base64String = Convert.ToBase64String(bufferedEncryptedBytes.WrittenMemory.Span);
return base64String;
}
public async Task DecryptAsync(MemoryStream encryptedBytes, string decryptedFilePath, CancellationToken cancellationToken)
{
await using var decryptedFile = new FileStream(decryptedFilePath, FileMode.Create);
await foreach (var bytes in (await daprClient.DecryptAsync(Constants.ComponentName, encryptedBytes,
Constants.KeyName, cancellationToken))
.WithCancellation(cancellationToken))
{
await decryptedFile.WriteAsync(bytes, cancellationToken);
}
LogDecryptFile(logger, decryptedFilePath);
decryptedFile.Close();
}
[LoggerMessage(LogLevel.Information, "Encrypted file '{filePath}' spanning {encryptedByteCount} bytes")]
static partial void LogEncryptFile(ILogger logger, string filePath, int encryptedByteCount);
[LoggerMessage(LogLevel.Information, "Decrypting in-memory bytes to file '{filePath}'")]
static partial void LogDecryptFile(ILogger logger, string filePath);
}

View File

@ -0,0 +1,31 @@
using System.Text;
using Dapr.Client;
namespace cryptography;
internal sealed partial class StringCryptographyOperations(DaprClient daprClient, ILogger<StringCryptographyOperations> logger)
{
public async Task<string> EncryptAsync(string plaintextValue, CancellationToken cancellationToken)
{
var plaintextBytes = Encoding.UTF8.GetBytes(plaintextValue);
var encryptedBytes = await daprClient.EncryptAsync(Constants.ComponentName, plaintextBytes, Constants.KeyName, new EncryptionOptions(KeyWrapAlgorithm.Rsa), cancellationToken);
LogEncryptionOperation(logger, Constants.KeyName, plaintextValue);
return Convert.ToBase64String(encryptedBytes.Span);
}
public async Task<string> DecryptAsync(string base64EncryptedValue, CancellationToken cancellationToken)
{
var ciphertextBytes = Convert.FromBase64String(base64EncryptedValue);
var decryptedBytes =
await daprClient.DecryptAsync(Constants.ComponentName, ciphertextBytes, Constants.KeyName, cancellationToken);
var plaintextValue = Encoding.UTF8.GetString(decryptedBytes.Span);
LogDecryptionOperation(logger, Constants.KeyName, plaintextValue);
return plaintextValue;
}
[LoggerMessage(LogLevel.Information, "Encrypting string with key {keyName} with plaintext value '{plaintextValue}'")]
static partial void LogEncryptionOperation(ILogger logger, string keyName, string plaintextValue);
[LoggerMessage(LogLevel.Information, "Decrypted string with key {keyName} with plaintext value '{plaintextValue}'")]
static partial void LogDecryptionOperation(ILogger logger, string keyName, string plaintextValue);
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<OutputType>Exe</OutputType>
@ -7,4 +7,8 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapr.AspNetCore" Version="1.15.0-rc02" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
dV1dhNiQEnqs4vDBVn40dnTKSSG2
-----END PRIVATE KEY-----

View File

@ -0,0 +1,2 @@
include ../../../docker.mk
include ../../../validate.mk