Wiaoj.Identifiers.Security
0.2.0-alpha.3
dotnet add package Wiaoj.Identifiers.Security --version 0.2.0-alpha.3
NuGet\Install-Package Wiaoj.Identifiers.Security -Version 0.2.0-alpha.3
<PackageReference Include="Wiaoj.Identifiers.Security" Version="0.2.0-alpha.3" />
<PackageVersion Include="Wiaoj.Identifiers.Security" Version="0.2.0-alpha.3" />
<PackageReference Include="Wiaoj.Identifiers.Security" />
paket add Wiaoj.Identifiers.Security --version 0.2.0-alpha.3
#r "nuget: Wiaoj.Identifiers.Security, 0.2.0-alpha.3"
#:package Wiaoj.Identifiers.Security@0.2.0-alpha.3
#addin nuget:?package=Wiaoj.Identifiers.Security&version=0.2.0-alpha.3&prerelease
#tool nuget:?package=Wiaoj.Identifiers.Security&version=0.2.0-alpha.3&prerelease
Wiaoj.Identifiers.Security
Encrypts Wiaoj.Identifiers under a Wiaoj.Security key ring, so identifier keys are stored, wrapped by the master key and rotated like the rest of your secrets.
Installation
dotnet add package Wiaoj.Identifiers.Security
Usage
using Wiaoj.Identifiers;
public sealed class IdentifierContext : ISecretContext;
builder.Services.AddWiaojSecurity()
.AddEnvironmentMasterKey()
.AddEntityFrameworkKeyStore<AppDbContext>()
.AddManagedProtector<IdentifierContext>();
builder.Services.AddIdentifiers().UseKeyRingCodec<IdentifierContext>();
Generated identifiers then work as usual (id.ToString(), UserId.Parse(text), JSON, binding), with their text written as usr_ + key version + 22 characters.
Compared with UseAesCodec()
UseAesCodec() |
UseKeyRingCodec<TContext>() |
|
|---|---|---|
| Key | One base64 key in configuration | The domain's key ring, stored and wrapped by the master key |
| Rotation | Changing the key breaks every issued identifier | New identifiers use the new version, and earlier ones still parse |
| Construction | AES-128 block with an HMAC tag | The same, keyed per version by a subkey |
How it works
- Per-version key: each key version gets an
AesIdCodec, keyed by a 32-byte subkey derived withISubkeyDeriver<TContext>.DeriveSubkey(version, "wiaoj.identifiers", …). The ring's keys are never exposed. The subkey is derived once per version and cached. - Writing: uses
CurrentKeyVersion. - Reading: looks at the identifier's version character and decodes with every version in
KeyVersionsthat has that character. The tag check refuses a version that didn't write the identifier. - Version character: the base62 digit of the version modulo 62:
1…9,A…Z,a…z, then0. After version 61 the characters repeat; versions sharing a character are both tried, so reading still works.
Rotation
AddManagedProtector<TContext>() reloads the key ring on rotation, and the codec follows the reload. No restart is needed.
await keyRotationService.ForceRotateAsync(); // KeyRotationService<IdentifierContext>
id.ToString(); // now "usr_2…"
UserId.Parse(oldText); // an identifier written as "usr_1…" still parses
Keep retired versions in the ring for as long as their identifiers are in circulation: in URLs, in other systems' databases, in emails. An identifier whose version is removed from the ring can no longer be read.
This is different from secrets encrypted with ISecretProtector, which IDataRotator re-encrypts: identifiers are held by clients, so they can't be rewritten. What you store is Value, the Snowflake (see Wiaoj.Identifiers.EntityFrameworkCore), so rotation never touches your database.
Startup
Using UseKeyRingCodec<TContext>() without AddManagedProtector<TContext>() fails when the host starts, because there is no ISubkeyDeriver<TContext>. Keys are loaded lazily, on the first identifier written or read, and AddManagedProtector pre-warms them while the host starts.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Wiaoj.Identifiers (>= 0.2.0-alpha.3)
- Wiaoj.Security.Abstractions (>= 0.2.0-alpha.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.2.0-alpha.3 | 35 | 9/24/2026 |
| 0.2.0-alpha.2 | 32 | 9/24/2026 |
| 0.2.0-alpha.1 | 37 | 9/24/2026 |
| 0.1.0-alpha.9 | 56 | 9/21/2026 |
| 0.1.0-alpha.8 | 48 | 9/21/2026 |
| 0.1.0-alpha.7 | 54 | 9/18/2026 |
| 0.1.0-alpha.6 | 52 | 9/16/2026 |
| 0.1.0-alpha.5 | 54 | 9/16/2026 |
| 0.1.0-alpha.4 | 52 | 9/16/2026 |