Skip to content

TechnoBro03/Blazor.ZeroStorage

Blazor.ZeroStorage

A secure, zero-storage, zero-knowledge encrypted storage solution for Blazor applications.

Table of Contents

Features

  • Zero-Knowledge: The library cannot read, decrypt, or interpret any stored data without a user actively providing a hardware-derived key.
  • Zero-Storage (Zero-Artifact): No authentication metadata, password hashes, manifests, or key envelopes are stored in the database.
  • Stateless: The library holds no persistent state. Once ZeroStorage is locked or the browser tab closes, the key is wiped from memory.

  • Authenticated Encryption: Utilizes AES-GCM with 256-bit keys, guaranteeing both data confidentiality and integrity.
  • Cryptographic Blinding: Database lookup keys are deterministically hashed via Hash-based Message Authentication Code (HMAC), making it impossible for an attacker to read logical keys or infer relationships between records.
  • Cryptographic Sandboxing (Multi-User): Supports multiple users on the same device. Lookup keys are derived from the user's specific key, so a user's data exists in a mathematically isolated, invisible partition.
  • Hardware-Bound Entropy: Utilizes the WebAuthn Pseudo-Random Function (PRF) extension to extract cryptographically secure 256-bit encryption keys directly from the user's hardware passkey.
  • Metadata Obfuscation: Supports uniform cryptographic padding to mask the true byte-size of payloads, preventing attackers from guessing content based on payload length.
  • Per-Item Key Derivation: Uses HMAC-based Extract-and-Expand Key Derivation Function (HKDF) to generate a unique encryption key for every single record, isolating any potential compromise to a single item.
  • Same-Origin Isolation: Built on top of the browser's native IndexedDB, guaranteeing that the encrypted data is inaccessible to other domains or websites.
  • Stateless Recovery: Allows exporting the raw encryption key as a typo-resistant Base58 string. This enables complete recovery without storing any fallback mechanisms or backup hashes in the database.

Installation

To install the NuGet package, run the following command:

dotnet add package Blazor.ZeroStorage

Tip

See here for more information on installing NuGet packages.

API Reference

Classes

Name Description
ZeroStorage A secure, zero-storage, zero-knowledge encrypted storage solution for Blazor applications.

Extensions

Name Description
IServiceCollection Extension methods to add ZeroStorage services.
Convert Extension methods to add conversion functionality.

Getting Started

Adding the Service

To add ZeroStorage services to your Blazor application, call the AddZeroStorage extension method on your IServiceCollection in the Program.cs file:

using Blazor.ZeroStorage;
public class Program
{
	public static async Task Main(string[] args)
	{
		var builder = WebAssemblyHostBuilder.CreateDefault(args);
		builder.RootComponents.Add<App>("#app");
		builder.RootComponents.Add<HeadOutlet>("head::after");

		// Add ZeroStorage services
		builder.Services.AddZeroStorage();

		await builder.Build().RunAsync();
	}
}

Injecting the Service

To use ZeroStorage, inject the ZeroStorage service into your component or service:

@page "/"

@using Blazor.ZeroStorage

@inject ZeroStorage Storage

-or-

using Blazor.ZeroStorage;
using Microsoft.AspNetCore.Components;

public partial class Home
{
	[Inject]
	public ZeroStorage Storage { get; set; } = null!;
}

-or-

using Blazor.ZeroStorage;

public class MyService(ZeroStorage storage) {}

Tip

See the Example Project for a more detailed demonstration of how to use the library in a Blazor application.

Advanced Application

Because Blazor.ZeroStorage is a zero-storage, zero-knowledge library, it provides a very minimal API surface.
This means that while it can be used directly to store and retrieve encrypted records, additional design patterns and abstractions may be desired to build a full-featured application on top of it.

Note

The following examples are heavily simplified for demonstration purposes. In a production application, you would likely want to add error handling, logging, etc.

Record Manager

One possible abstraction is to create a "Record Manager" wrapper around ZeroStorage.
This Record Manager maintains an internal list of keys to allow for features like listing all keys or clearing a user's data, which are not natively supported by ZeroStorage due to its zero-knowledge design.

using Blazor.ZeroStorage;

public class RecordManager(ZeroStorage storage)
{
	// A internal key to track all keys that have been set, so we can implement GetKeys and Clear
	private const string Keys = "__keys";

	public async Task SetAsync(string key, string value)
	{
		// Get the current list of keys, add the new key if it doesn't exist, and save it back to storage
		var keys = await storage.GetAsync<ICollection<string>>(Keys) ?? [];
		if (!keys.Contains(key))
		{
			keys.Add(key);
			await storage.SetAsync(Keys, keys);
		}

		await storage.SetAsync(key, value);
	}

	public async Task<string?> GetAsync(string key)
	{
		return await storage.GetAsync<string>(key);
	}

	public async Task DeleteAsync(string key)
	{
		// Get the current list of keys, remove the key if it exists, and save it back to storage
		var keys = await storage.GetAsync<ICollection<string>>(Keys) ?? [];
		if (keys.Remove(key))
			await storage.SetAsync(Keys, keys);

		await storage.DeleteAsync(key);
	}

	public async Task<ICollection<string>> GetKeysAsync()
	{
		return await storage.GetAsync<ICollection<string>>(Keys) ?? [];
	}

	public async Task ClearAsync()
	{
		// Get all keys and delete them one by one, then delete the internal key list as well
		var keys = await GetKeysAsync();
		foreach (var key in keys)
			await storage.DeleteAsync(key);

		await storage.DeleteAsync(Keys);
	}
}

TTL Cache

Another possible abstraction is to create a TTL (Time-To-Live) wrapper around ZeroStorage to allow for expiring records after a certain amount of time.
This can be implemented by storing a timestamp along with the value and checking it on retrieval.

using Blazor.ZeroStorage;

internal record TtlRecord<T>(T Value, DateTime Expiry);

public class TtlStorage(ZeroStorage storage)
{
	public async Task SetAsync<T>(string key, T value, TimeSpan ttl)
	{
		// Create a TtlRecord with the value and the expiry time, then save it to storage
		var record = new TtlRecord<T>(value, DateTime.UtcNow.Add(ttl));
		await storage.SetAsync(key, record);
	}

	public async Task<T?> GetAsync<T>(string key)
	{
		// Retrieve the TtlRecord from storage, check if it has expired, and return the value if it is still valid
		var record = await storage.GetAsync<TtlRecord<T>>(key);
		if (record is null) return default;

		// If the record has expired, delete it from storage and return default
		if (DateTime.UtcNow > record.Expiry)
		{
			await storage.DeleteAsync(key);
			return default;
		}

		return record.Value;
	}
}

About

A secure, zero-storage, zero-knowledge encrypted storage solution for Blazor applications.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Contributors