Skip to content

Add BLOB Pipeline and ByteEncryption Support#25836

Open
ugurozturk wants to merge 1 commit into
abpframework:devfrom
ugurozturk:issue/25835
Open

Add BLOB Pipeline and ByteEncryption Support#25836
ugurozturk wants to merge 1 commit into
abpframework:devfrom
ugurozturk:issue/25835

Conversation

@ugurozturk

@ugurozturk ugurozturk commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Description

Resolves #25835

This PR introduces a provider-independent BLOB stream pipeline and a built-in encryption contributor for encrypting BLOBs at rest. It also adds IByteArrayEncryptionService to Volo.Abp.Security for authenticated encryption of binary data and streams.

Previously, BLOB content flowed directly between IBlobContainer and IBlobProvider. Applications that needed encryption, compression, validation, virus scanning, or similar content transformations had to replace the container/provider or add provider-specific implementations. The new pipeline provides a reusable interception point above all storage providers, so the existing provider packages do not require any changes.

BLOB pipeline

  • Adds IBlobPipelineContributor with OnSaveAsync and OnGetAsync stream transformation hooks.
  • Adds per-container PipelineContributors configuration.
  • Composes contributors configured on the default container with contributors configured on named containers, even when a named container overrides its provider.
  • Executes contributors in registration order while saving and in reverse order while reading, allowing transformations to be safely chained.
  • Resolves contributors through dependency injection and keeps their scopes alive for the complete lifetime of lazy transformed streams.
  • Leaves containers without contributors on the existing direct provider path.

Authenticated binary encryption

  • Adds IByteArrayEncryptionService with byte-array and stream APIs.
  • Uses AES-256-GCM on supported target frameworks.
  • Uses AES-256-CBC with HMAC-SHA256 (encrypt-then-MAC) on .NET Standard 2.0.
  • Derives keys with PBKDF2 and supports configurable passphrase, salt, iteration count, and chunk size through AbpByteArrayEncryptionOptions.
  • Processes data in authenticated chunks, keeping memory usage independent of the total payload size.
  • Binds the format header and chunk index to every chunk as associated data, preventing chunk reordering or movement between payloads.
  • Writes an authenticated terminal record so complete-chunk truncation and removal are detected.
  • Rejects malformed chunk sizes above 16 MB before allocating buffers.

Built-in BLOB encryption

  • Adds UseEncryption() for enabling encryption on an individual container or on the default container configuration.
  • Adds BlobEncryptionContributor, IBlobEncryptionService, and IBlobEncryptionKeyProvider.
  • Resolves the passphrase in the following order:
    1. Container-specific passphrase supplied to UseEncryption(...).
    2. The encrypted, tenant-only Abp.BlobStoring.Encryption.TenantPassPhrase setting.
    3. AbpBlobStoringEncryptionOptions.DefaultPassPhrase.
  • Allows applications to replace IBlobEncryptionKeyProvider with a vault/KMS-backed implementation.
  • Prefixes encrypted BLOBs with the ABPE magic header and a format version.
  • Returns existing BLOBs without the encryption header unchanged, allowing encryption to be enabled for containers that already contain plaintext data.
  • Supports non-seekable provider response streams without copying the entire BLOB into memory.
  • Exposes the exact encrypted stream length when the source length is known, preserving compatibility with providers such as MinIO that require the object size before upload.

Usage

Enable encryption for a specific container:

Configure<AbpBlobStoringOptions>(options =>
{
    options.Containers.Configure<ProfilePictureContainer>(container =>
    {
        container.UseEncryption();
    });
});

Configure<AbpBlobStoringEncryptionOptions>(options =>
{
    options.DefaultPassPhrase = "my-global-passphrase";
});

Enable encryption for all containers by default:

Configure<AbpBlobStoringOptions>(options =>
{
    options.Containers.ConfigureDefault(container =>
    {
        container.UseEncryption();
    });
});

Set a tenant-specific passphrase:

await _settingManager.SetForTenantAsync(
    tenantId,
    BlobStoringEncryptionSettings.TenantPassPhrase,
    "tenant-secret-passphrase"
);

Create and register a custom pipeline contributor:

public class CompressionPipelineContributor : IBlobPipelineContributor, ITransientDependency
{
    public Task<Stream> OnSaveAsync(BlobPipelineSaveArgs args)
    {
        // Return a stream that compresses args.BlobStream while it is read.
    }

    public Task<Stream> OnGetAsync(BlobPipelineGetArgs args)
    {
        // Return a stream that decompresses args.BlobStream while it is read.
    }
}

Configure<AbpBlobStoringOptions>(options =>
{
    options.Containers.Configure<MyContainer>(container =>
    {
        container.PipelineContributors.Add<CompressionPipelineContributor>();
        container.UseEncryption();
    });
});

In this example, the BLOB is compressed and then encrypted during save; it is decrypted and then decompressed during read.

Compatibility and operational notes

  • This feature is opt-in. Existing containers behave as before unless a pipeline contributor is configured.
  • Existing plaintext BLOBs remain readable after encryption is enabled because payloads without the ABPE header pass through unchanged.
  • New or overwritten BLOBs are encrypted once encryption is enabled.
  • Losing or changing a passphrase makes BLOBs encrypted with the previous passphrase unreadable.
  • Changing the key-derivation salt or iteration count also makes previously encrypted payloads unreadable unless the original values are restored.
  • Custom contributors should return lazy stream wrappers where possible to preserve constant-memory processing.

Documentation

  • Adds a dedicated BLOB Encryption & Pipeline guide.
  • Documents per-container/default encryption, tenant and global passphrase resolution, custom key providers, compatibility behavior, encryption format, and custom pipeline contributors.
  • Adds the new guide to the BLOB Storing documentation and navigation.

Checklist

  • I fully tested it as developer / designer and created unit / integration tests
  • I documented it (or no need to document or I will create a separate documentation issue)

How to test it?

Automated verification completed:

dotnet test framework/test/Volo.Abp.Security.Tests/Volo.Abp.Security.Tests.csproj --no-restore
dotnet test framework/test/Volo.Abp.BlobStoring.Tests/Volo.Abp.BlobStoring.Tests.csproj --no-restore

dotnet build framework/src/Volo.Abp.Security/Volo.Abp.Security.csproj --no-restore
dotnet build framework/src/Volo.Abp.BlobStoring/Volo.Abp.BlobStoring.csproj --no-restore

Results:

  • Volo.Abp.Security.Tests: 24 passed.
  • Volo.Abp.BlobStoring.Tests: 29 passed.
  • Both affected source projects build for netstandard2.0, netstandard2.1, net8.0, net9.0, and net10.0.

The automated coverage includes:

  • Byte-array and multi-chunk stream round trips.
  • Tampered data, wrong passphrases, missing terminal records, and oversized chunk headers.
  • Container-specific, tenant-specific, and global passphrase resolution.
  • Raw-at-rest encryption and transparent decryption through IBlobContainer.
  • Default and named-container contributor composition across provider overrides.
  • Custom contributor ordering.
  • Contributor scope lifetime while providers and callers consume lazy streams.
  • Exact encrypted stream length for seekable input.
  • Constant-memory handling and disposal of non-seekable provider streams.
  • Backward-compatible reads of existing plaintext BLOBs.

Manual smoke test:

  1. Configure UseEncryption() for a test container and provide a passphrase.
  2. Save a BLOB through IBlobContainer.
  3. Inspect the provider's stored bytes and verify that they start with ABPE and do not contain the plaintext payload.
  4. Read the BLOB through IBlobContainer and verify that the original content is returned.
  5. Place a plaintext BLOB directly in the provider and verify that it remains readable through the same encrypted container.
  6. Repeat under two tenant contexts with different tenant passphrases and verify that the stored ciphertext differs while both tenants can read their own BLOBs.

…decryption

- Introduced IByteArrayEncryptionService interface for encrypting and decrypting binary data with authenticated encryption.
- Implemented methods for encrypting and decrypting byte arrays and streams, including options for passphrase and salt.
- Enhanced blob storing tests to cover encryption scenarios, including tenant-specific passphrases and custom pipeline contributors.
- Added FakeInMemoryBlobProvider and various test containers to facilitate testing of blob storage with encryption.
- Created unit tests for ByteArrayEncryptionService to validate encryption and decryption functionality, including edge cases for tampered data and oversized chunks.
@ugurozturk ugurozturk changed the title feat: Add IByteArrayEncryptionService for binary data encryption and decryption Add BLOB Pipeline and ByteEncryption Support Jul 18, 2026
@maliming
maliming self-requested a review July 20, 2026 01:43
@maliming maliming added this to the 10.7-preview milestone Jul 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request : [BlobStoring] Add a pipeline/interceptor mechanism to IBlobContainer (for encryption, compression, etc.)

2 participants