IDEncoder 0.5.1

dotnet add package IDEncoder --version 0.5.1
                    
NuGet\Install-Package IDEncoder -Version 0.5.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="IDEncoder" Version="0.5.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="IDEncoder" Version="0.5.1" />
                    
Directory.Packages.props
<PackageReference Include="IDEncoder" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add IDEncoder --version 0.5.1
                    
#r "nuget: IDEncoder, 0.5.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package IDEncoder@0.5.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=IDEncoder&version=0.5.1
                    
Install as a Cake Addin
#tool nuget:?package=IDEncoder&version=0.5.1
                    
Install as a Cake Tool

IDEncoder

NuGet License

A .NET library that encodes numeric IDs (long) into short, URL-safe Base62 strings — similar to how YouTube, Twitter and other services expose public IDs like dQw4w9WgXcQ instead of raw database numbers.

Uses Blowfish encryption under the hood with no external cryptography dependencies — the cipher is implemented from scratch.

Database:  42
URL:       /video/xK9mQ3bPl2a
JSON:      { "id": "xK9mQ3bPl2a", "title": "..." }

Why

  • Hide sequential IDs — users can't guess or enumerate resource URLs
  • Short output — fixed 11 characters with the default Blowfish codec (vs 20 digits for long.MaxValue); 2–12 characters with the Sqids-style codec
  • Reversible — decode back to the original number with the same key
  • Salted encoding — same ID produces different strings for different entity types
  • Zero external crypto dependencies — ships its own Blowfish implementation

Install

dotnet add package IDEncoder                # core: encoding, EncodedId, JSON, DI
dotnet add package IDEncoder.AspNetCore     # + MVC model binding and JSON wiring

The core package has no ASP.NET Core dependency — it works in console apps, workers and plain dotnet/runtime containers. Add IDEncoder.AspNetCore only in web apps.

Quick start

var encoder = new IDEncoder.IDEncoder("my-secret-key");

string encoded = encoder.Encode(42);       // "xK9mQ3bPl2a"
long decoded = encoder.Decode(encoded);    // 42

Salt

By default, the same numeric ID always encodes to the same string. This means a user who knows a video ID could try it as a gallery ID, a user ID, etc.

Salt solves this — the same number produces different strings for different entity types:

encoder.Encode(42);                // "xK9mQ3bPl2a"
encoder.Encode(42, "video");       // "t7RqN1cWm5z"
encoder.Encode(42, "gallery");     // "p3KmW8vLn9a"

// Decoding requires the same salt
encoder.Decode("t7RqN1cWm5z", "video");    // 42
encoder.Decode("p3KmW8vLn9a", "gallery");  // 42

Salted ciphers are cached internally — no performance penalty for reusing the same salt.

Codecs

IDEncoder is a facade over an IIdCodec. Two are built in:

  • BlowfishIdCodec (default) — Blowfish encryption, fixed 11-char output, hides sequential IDs. new IDEncoder("secret") uses this.
  • SqidsIdCodec — simple, non-secure, Sqids-style: short variable-length strings where sequential IDs look dissimilar, with no secret key. Obfuscation only, not cryptography.
services.AddIDEncoder(new SqidsIdCodec());          // simple codec
services.AddIDEncoder(new BlowfishIdCodec("key"));  // explicit Blowfish

Custom codecs

Implement IIdCodec, or derive from IdCodec to get salt caching for free, and register it:

public sealed class MyCodec : IdCodec {
    public override int MaxEncodedLength => 16;
    public override string Encode(long id) => /* ... */;
    public override int Encode(long id, Span<char> destination) => /* ... */;
    public override long Decode(ReadOnlySpan<char> encoded) => /* ... */;
    protected override IIdCodec CreateWithSalt(string salt) => /* salted variation from base */;
}

services.AddIDEncoder(new MyCodec());

Codec output is not self-describing. Don't switch codecs over data that already has encoded IDs in the wild — a string from one codec won't decode correctly with another.

ASP.NET Core integration

Registration

// Key known at startup
services.AddIDEncoder("my-secret-key");

// Key from configuration
services.AddIDEncoder(provider => {
    var config = provider.GetRequiredService<IConfiguration>();
    return config["IDEncoder:SecretKey"]!;
});

// Key loaded later (e.g. from database after startup)
services.AddIDEncoderProvider();

Then wire MVC (IDEncoder.AspNetCore package):

services.AddControllers(o => o.UseIDEncoderModelBinding())  // EncodedId route binding + [Salt]
    .AddIDEncoderJson();                                    // EncodedId JSON + [Salt] via DI

AddIDEncoderJson() binds JSON serialization to the encoder registered in DI (any of the three registration styles above, including the deferred provider). The manual AddJsonOptions(o => o.JsonSerializerOptions.UseIDEncoderSalts()) setup still works and uses the process-wide ambient encoder instead.

For deferred initialization, call Configure when the key becomes available:

public class AppInitializer(IDEncoderProvider encoderProvider, IMyConfigService config) {
    public async Task InitializeAsync() {
        string secret = await config.GetSecretAsync("ID_ENCODER_SECRET");
        encoderProvider.Configure(secret);
    }
}

EncodedId struct

A long wrapper that automatically encodes/decodes at JSON and route-binding boundaries:

// DTO — JSON output is a Base62 string
public record ChatResult(EncodedId Id, string Title);

// Mapping — implicit conversion from long
return new ChatResult(Id: chat.Id, Title: chat.Title);
// { "id": "xK9mQ3bPl2a", "title": "My chat" }

// Controller — parsed from URL automatically via IParsable
[HttpGet("{id}")]
public async Task<IActionResult> Get(EncodedId id) {
    long dbId = id; // implicit conversion to long
    var chat = await db.Chats.FindAsync(dbId);
    return Ok(new ChatResult(chat.Id, chat.Title));
}

The database stores a plain long. Encoding only happens at the JSON/URL boundary.

Salt via attribute

Use [Salt("...")] on properties to automatically apply per-entity-type salt during JSON serialization:

public record VideoResult(
    [property: Salt("video")] EncodedId Id,
    string Title
);

public record GalleryResult(
    [property: Salt("gallery")] EncodedId Id,
    string Title
);

// Same DB id = 42, but different JSON output:
// VideoResult:   { "id": "t7RqN1cWm5z", "title": "..." }
// GalleryResult: { "id": "p3KmW8vLn9a", "title": "..." }

Salt support in JSON and route binding is enabled by the standard wiring:

services.AddControllers(o => o.UseIDEncoderModelBinding())
    .AddIDEncoderJson();

Salt also works on controller parameters for route/query binding:

[HttpPost("{id}/test")]
public async Task<IActionResult> TestConnection([Salt("s3")] EncodedId id) {
    long dbId = id; // correctly decoded with "s3" salt
}

JSON behavior

Input Output
Write(42) "xK9mQ3bPl2a"
Read("xK9mQ3bPl2a") 42
Read(42) rejected with a 400 by default

Raw JSON numbers are rejected by default: if they were accepted, any endpoint binding an EncodedId from a request body would let clients address resources by plain sequential numbers ({"id": 1}, {"id": 2}, …), bypassing the encoding entirely. If you are migrating clients that still send numbers, enable it explicitly and consciously:

.AddIDEncoderJson(allowNumericInput: true)
// or: o.JsonSerializerOptions.UseIDEncoder(encoder, allowNumericInput: true)

Security notes

  • Encoding hides IDs; it does not authorize. Nearly any well-formed 11-character Base62 string decodes to some long — decode success does not prove the caller was ever given that ID. Always perform existence and authorization checks after decoding.
  • Blowfish encoding is deterministic: the same ID with the same key and salt always produces the same string. Use salts to stop IDs from being tried across entity types.
  • SqidsIdCodec is obfuscation only — anyone can decode its output without a key.

Zero-alloc API

For high-throughput scenarios, span-based overloads avoid heap allocations:

// Encode into a stack buffer sized for the active codec
Span<char> buffer = stackalloc char[encoder.MaxEncodedLength];
int n = encoder.Encode(42, buffer);

// Decode from the written slice (works for fixed- and variable-length codecs)
long id = encoder.Decode(buffer[..n]);
Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on IDEncoder:

Package Downloads
IDEncoder.AspNetCore

ASP.NET Core integration for IDEncoder — EncodedId model binding with salt support and MVC JSON wiring

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.5.1 42 7/23/2026
0.5.0 111 7/2/2026
0.4.0 115 6/15/2026
0.3.0 174 3/18/2026
0.2.0 123 3/17/2026
0.1.1 136 3/17/2026