Skip to content

Add BLOB at-rest encryption support#25836

Draft
ugurozturk wants to merge 2 commits into
abpframework:devfrom
ugurozturk:issue/25835
Draft

Add BLOB at-rest encryption support#25836
ugurozturk wants to merge 2 commits into
abpframework:devfrom
ugurozturk:issue/25835

Conversation

@ugurozturk

@ugurozturk ugurozturk commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Resolve #25835

Description

This PR adds transparent at-rest encryption to the BLOB Storing system. The BLOB stream is encrypted before it reaches the configured storage provider and decrypted while it is read back, so the existing provider packages do not require any changes.

Encryption

  • Encrypts BLOBs with AES-256-GCM in authenticated chunks, keeping memory usage constant and independent of the BLOB size.
  • Derives a unique key per BLOB with PBKDF2-SHA256, a random per-BLOB salt and a configurable iteration count (AbpBlobStoringEncryptionOptions.KdfIterations, allowed range 100,000 - 600,000).
  • Binds every chunk to the format header, the chunk index and the storage identity (normalized container name, BLOB name and tenant id) as associated data, so chunk re-ordering, moving content between BLOBs and cross-tenant substitution are detected.
  • Writes an authenticated terminal record, so cutting complete chunks off the end is detected when the stream is read to its end.
  • Prefixes encrypted BLOBs with the ABPE magic and a format version; a golden-vector test freezes the v1 format on disk.
  • Validates the header parameters (iteration count, chunk size) against upper bounds before deriving keys or allocating buffers.

Configuration and key resolution

  • UseEncryption() enables encryption for a single container or for the default container configuration; DisableEncryption() opts a container out again.
  • The passphrase resolves from the container configuration first, then AbpBlobStoringEncryptionOptions.DefaultPassPhrase. There is no built-in default value: if no passphrase can be resolved, saving and reading fail with a descriptive exception.
  • The key source is recorded in the BLOB header and only that source is used while decrypting, so BLOBs written with the global passphrase stay readable after a container-specific passphrase is configured later.
  • IBlobEncryptionKeyProvider can be replaced to read passphrases from a vault or another secret store, or to supply tenant-specific keys (BlobEncryptionKeySource.Tenant).
  • Reading existing plaintext BLOBs is an explicit opt-in (UseEncryption(allowLegacyPlaintext: true)) intended for migrating containers that already have data; by default, content without the encrypted format fails to be read.

Provider compatibility

  • The encrypting stream exposes the exact encrypted length when the source stream can report its length (including forward-only sources), for providers that require the object size before uploading.
  • When the platform does not support AES-GCM, saving and reading fail before any output is produced (.NET Standard 2.0 always; probed at runtime on .NET Standard 2.1), so no partially written BLOB is left behind.
  • The file system provider now retries a failed save only while it is safe: before the target file is opened, or for a seekable overwrite. A consumed non-replayable stream is never replayed into a corrupted file.

Usage

Encrypt the BLOBs of a specific container:

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

Configure<AbpBlobStoringEncryptionOptions>(options =>
{
    options.DefaultPassPhrase = configuration["MyApp:BlobPassPhrase"];
});

Encrypt all containers by default, with a single container opting out:

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

    options.Containers.Configure<PublicPictureContainer>(container =>
    {
        container.DisableEncryption();
    });
});

Compatibility and operational notes

  • The feature is opt-in; containers that do not enable encryption are not affected at all.
  • Losing or changing a passphrase makes the BLOBs encrypted with it unreadable; the documentation describes the migration steps for changing a passphrase and for enabling encryption on an existing container.
  • Because of the identity binding, changing the container's IsMultiTenant value, moving BLOBs between tenants or containers, or switching to a provider that normalizes names differently requires re-saving (decrypting) the BLOBs first.
  • Key derivation runs on every save and on every open by design (per-BLOB random salt); the "Performance and Cost" section of the documentation explains the cost profile before enabling encryption on hot containers.

Documentation

Adds a dedicated BLOB Encryption document covering enabling, key resolution, custom and tenant-specific key providers, migrating existing containers, changing a passphrase, behavioral changes for encrypted containers, performance cost, the encryption format and troubleshooting.

How to test it?

  • Volo.Abp.BlobStoring.Tests (57 tests): round trips, empty and multi-chunk content, tampering, truncation, wrong keys, identity binding, key-source routing, legacy plaintext handling, cancellation, disposal and the format golden vector.
  • Volo.Abp.BlobStoring.FileSystem.Tests (42 tests): the same scenarios against the real file system, tenant file layout and the save retry behavior.
  • The exact-length contract was validated against a real MinIO server; those tests are included commented-out, following the repository convention for tests that need an external service.

…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
@maliming maliming changed the title Add BLOB Pipeline and ByteEncryption Support Add BLOB at-rest encryption support Jul 22, 2026
@maliming
maliming marked this pull request as draft July 22, 2026 08:12
@maliming
maliming marked this pull request as draft July 22, 2026 08:12
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