Created
July 14, 2025 09:01
-
-
Save tonyjoanes/918629efdd7cd949dda4cd25f3a75597 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Text; | |
using Azure.Storage.Blobs; | |
using Microsoft.Azure.Functions.Worker; | |
using Microsoft.Extensions.Logging; | |
using Org.BouncyCastle.Bcpg; | |
using Org.BouncyCastle.Bcpg.OpenPgp; | |
using Org.BouncyCastle.Security; | |
namespace EncryptBlobFunction; | |
public class EncryptBlobFunction | |
{ | |
private readonly ILogger<EncryptBlobFunction> _logger; | |
private const int BufferSize = 1024 * 1024; // 1MB buffer for streaming | |
private const string OutputContainerName = "output-container"; | |
public EncryptBlobFunction(ILogger<EncryptBlobFunction> logger) | |
{ | |
_logger = logger; | |
} | |
[Function("EncryptBlobFunction")] | |
public async Task Run( | |
[BlobTrigger("input-container/{name}", Connection = "AzureWebJobsStorage")] | |
Stream inputBlob, | |
string name, | |
FunctionContext context | |
) | |
{ | |
_logger.LogInformation( | |
"Started processing blob: {BlobName} with size: {Size} bytes", | |
name, | |
inputBlob.Length | |
); | |
try | |
{ | |
// Get connection string and create blob service client | |
var connectionString = Environment.GetEnvironmentVariable("AzureWebJobsStorage"); | |
if (string.IsNullOrEmpty(connectionString)) | |
{ | |
throw new InvalidOperationException( | |
"AzureWebJobsStorage connection string is not configured" | |
); | |
} | |
var blobServiceClient = new BlobServiceClient(connectionString); | |
var outputContainerClient = blobServiceClient.GetBlobContainerClient( | |
OutputContainerName | |
); | |
// Ensure output container exists | |
await outputContainerClient.CreateIfNotExistsAsync(); | |
// Generate output blob name with .pgp extension | |
var outputBlobName = Path.GetFileNameWithoutExtension(name) + ".pgp"; | |
var outputBlobClient = outputContainerClient.GetBlobClient(outputBlobName); | |
// Load PGP public key | |
var publicKey = LoadPgpPublicKey(); | |
if (publicKey is null) | |
{ | |
throw new InvalidOperationException( | |
"Failed to load PGP public key from environment variable" | |
); | |
} | |
_logger.LogInformation("Successfully loaded PGP public key"); | |
// Stream encrypt and upload | |
await using var outputStream = await outputBlobClient.OpenWriteAsync(overwrite: true); | |
await EncryptStreamWithPgp(inputBlob, outputStream, publicKey); | |
_logger.LogInformation( | |
"Successfully encrypted and uploaded blob: {OutputBlobName}", | |
outputBlobName | |
); | |
} | |
catch (Exception ex) | |
{ | |
_logger.LogError(ex, "Error processing blob: {BlobName}", name); | |
throw; | |
} | |
} | |
private PgpPublicKey? LoadPgpPublicKey() | |
{ | |
try | |
{ | |
var publicKeyString = Environment.GetEnvironmentVariable("PgpPublicKey"); | |
if (string.IsNullOrEmpty(publicKeyString)) | |
{ | |
_logger.LogError("PgpPublicKey environment variable is not set"); | |
return null; | |
} | |
// Convert ASCII-armored key to stream | |
using var keyStream = new MemoryStream(Encoding.UTF8.GetBytes(publicKeyString)); | |
using var decoderStream = PgpUtilities.GetDecoderStream(keyStream); | |
var pgpPub = new PgpPublicKeyRingBundle(decoderStream); | |
// Get the first public key suitable for encryption | |
foreach (PgpPublicKeyRing keyRing in pgpPub.GetKeyRings()) | |
{ | |
foreach (PgpPublicKey key in keyRing.GetPublicKeys()) | |
{ | |
if (key.IsEncryptionKey) | |
{ | |
_logger.LogInformation( | |
"Found encryption key with ID: {KeyId}", | |
key.KeyId.ToString("X") | |
); | |
return key; | |
} | |
} | |
} | |
_logger.LogError("No encryption key found in the public key ring"); | |
return null; | |
} | |
catch (Exception ex) | |
{ | |
_logger.LogError(ex, "Error loading PGP public key"); | |
return null; | |
} | |
} | |
private async Task EncryptStreamWithPgp( | |
Stream inputStream, | |
Stream outputStream, | |
PgpPublicKey publicKey | |
) | |
{ | |
try | |
{ | |
// Create PGP encryption objects | |
var encryptedDataGenerator = new PgpEncryptedDataGenerator( | |
SymmetricKeyAlgorithmTag.Aes256, | |
withIntegrityPacket: true, | |
new SecureRandom() | |
); | |
encryptedDataGenerator.AddMethod(publicKey); | |
// Create compressed data generator for optional compression | |
var compressedDataGenerator = new PgpCompressedDataGenerator( | |
CompressionAlgorithmTag.Zip | |
); | |
// Create the encrypted stream | |
await using var encryptedStream = encryptedDataGenerator.Open( | |
outputStream, | |
new byte[BufferSize] | |
); | |
await using var compressedStream = compressedDataGenerator.Open(encryptedStream); | |
// Create literal data generator | |
var literalDataGenerator = new PgpLiteralDataGenerator(); | |
var fileName = "encrypted_file.csv"; // Generic filename for security | |
await using var literalStream = literalDataGenerator.Open( | |
compressedStream, | |
PgpLiteralData.Binary, | |
fileName, | |
inputStream.Length, | |
DateTime.UtcNow | |
); | |
// Stream the data in chunks to keep memory usage low | |
var buffer = new byte[BufferSize]; | |
int bytesRead; | |
long totalBytesProcessed = 0; | |
inputStream.Position = 0; // Ensure we start from the beginning | |
while ((bytesRead = await inputStream.ReadAsync(buffer, 0, buffer.Length)) > 0) | |
{ | |
await literalStream.WriteAsync(buffer, 0, bytesRead); | |
totalBytesProcessed += bytesRead; | |
// Log progress for large files | |
if (totalBytesProcessed % (BufferSize * 10) == 0) // Every 10MB | |
{ | |
_logger.LogInformation( | |
"Processed {ProcessedBytes} bytes of {TotalBytes} bytes", | |
totalBytesProcessed, | |
inputStream.Length | |
); | |
} | |
} | |
_logger.LogInformation( | |
"Encryption completed. Total bytes processed: {TotalBytes}", | |
totalBytesProcessed | |
); | |
} | |
catch (Exception ex) | |
{ | |
_logger.LogError(ex, "Error during PGP encryption"); | |
throw; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment