Skip to main content
Logo ByteAether

ByteAether.Ulid v1.3.0: Enhanced ULID Generation Control and Security

ByteAether.Ulid v1.3.0, a significant update to our high-performance .NET ULID implementation, has been released. This version introduces enhanced flexibility and security for ULID generation, reinforcing its suitability for modern applications.

ByteAether aims to provide a compliant and dependable ULID solution for .NET developers. ULIDs combine universal uniqueness with lexicographical sortability, making them suitable for distributed systems and time-ordered data by optimizing indexing and querying.

Enhanced Generation Control with GenerationOptions

Previous versions of ByteAether.Ulid utilized a bool? isMonotonic and Ulid.DefaultIsMonotonic flags for monotonic generation control. Version 1.3.0 replaces this with a new configuration object, GenerationOptions, offering granular control over ULID generation. GenerationOptions includes three parameters: Monotonicity, InitialRandomSource, and IncrementRandomSource.

GenerationOptions Explained

Let's explore the new GenerationOptions in detail:

Monotonicity

This option controls the monotonic behavior of ULIDs, offering choices to balance predictability and uniqueness:

  • NonMonotonic
    Generates a fully random Random component, suitable when strict sequential ordering is not required.
  • MonotonicIncrement (Default)
    Maintains strict monotonic progression by incrementing the random portion of the ULID by 1, mirroring previous monotonic generation behavior.
  • MonotonicRandom1Byte
  • MonotonicRandom2Byte
  • MonotonicRandom3Byte
  • MonotonicRandom4Byte
    These options enhance security and unpredictability. As discussed in ULID specification issue #105, incrementing the random part by 1 can lead to predictable ULIDs within the same millisecond, potentially enabling enumeration attacks. To mitigate this, these options allow configuring a random increment from 1-byte (1–256) to 4-byte (1–4,294,967,296) range using the IncrementRandomSource. This adds a layer of randomness, making ULIDs more difficult to guess while preserving lexicographical sortability, directly addressing enumeration mitigation concerns in the ULID specification.

InitialRandomSource and IncrementRandomSource

Both InitialRandomSource and IncrementRandomSource properties are of type IRandomProvider. This design provides a flexible way to define how randomness is produced, allowing users to implement and provide their own custom random number generators.

The InitialRandomSource option specifies the source of randomness for the initial random component of the ULID. The IncrementRandomSource setting determines the source of randomness for the increment value when using MonotonicRandomXByte options for monotonicity.

  • CryptographicallySecureRandomProvider (Default for InitialRandomSource)
    Utilizes a cryptographically secure random number generator, ensuring high entropy and suitability for security-sensitive applications.
  • PseudoRandomProvider (Default for IncrementRandomSource)
    Employs a faster pseudo-random algorithm, suitable for scenarios where cryptographic security isn't a primary concern, and performance is prioritized.

Default Behavior and Backward Compatibility

Version 1.3.0's default settings ensure a seamless transition, matching previous ByteAether.Ulid monotonic generation behavior:

Ulid.DefaultGenerationOptions = new Ulid.GenerationOptions
{
	Monotonicity = MonotonicityOptions.MonotonicIncrement,
	InitialRandomSource = new CryptographicallySecureRandomProvider(),
	IncrementRandomSource = new PseudoRandomProvider()
}

Upgrading to v1.3.0 will not alter existing ULID generation logic unless GenerationOptions are explicitly configured and assigned to Ulid.DefaultGenerationOptions.

For backward compatibility, public API methods with a bool? isMonotonic parameter and the Ulid.DefaultIsMonotonic static property remain, but are marked [Obsolete]. These are deprecated to encourage migration to the GenerationOptions-based methods for enhanced control and flexibility. Obsolete methods and properties will be removed in a future version and users are encouraged to update their code for future compatibility and to leverage new capabilities.

Why ULIDs?

Identifier selection is crucial in distributed systems. GUIDs provide uniqueness but lack sortability, hindering database indexing and querying. Integer IDs are sortable but may conflict in distributed environments due to their limited scope.

ULIDs combine universal uniqueness with lexicographical sortability:

  • Universally Unique: Ensures global distinctness across systems.
  • Lexicographically Sortable: Enables efficient time-based sorting for database performance and chronological data retrieval.
  • Human-Readable: More manageable than GUIDs.

ByteAether.Ulid is a robust and compliant .NET implementation. It addresses potential issues from the official ULID specification, such as the "random part overflow" issue, by allowing timestamp increments to ensure unique ULIDs even at high generation rates.

Benchmarking against other .NET ULID implementations (e.g., NetUlid, Ulid, NUlid), Guid, and GuidV7 consistently shows ByteAether.Ulid as highly performant and fully compliant with the ULID specification, ensuring dependable generation.

Installation and Usage

Install ByteAether.Ulid v1.3.0 via NuGet:

dotnet add package ByteAether.Ulid

Example of GenerationOptions usage:

using ByteAether.Ulid;
using static ByteAether.Ulid.Ulid.GenerationOptions;

var optionsWithRandomIncrement = new Ulid.GenerationOptions
{
	// 4-byte random increment
	Monotonicity = MonotonicityOptions.MonotonicRandom4Byte,
	// Cryptographically secure initial random
	InitialRandomSource = new CryptographicallySecureRandomProvider(),
	// Cryptographically secure increment random
	IncrementRandomSource = new CryptographicallySecureRandomProvider()
};

var ulidWithRandomInc = Ulid.New(optionsWithRandomIncrement);
Console.WriteLine($"ULID with random increment: {ulidWithRandomInc}");

// Set default generation options globally
Ulid.DefaultGenerationOptions = new()
{
	Monotonicity = MonotonicityOptions.MonotonicIncrement,
	// Pseudo-randomness for speed
	InitialRandomSource = new PseudoRandomProvider(),
	// IncrementRandomSource is not used by MonotonicIncrement
};

var defaultUlid = Ulid.New();
Console.WriteLine($"ULID from default pseudo-random source: {defaultUlid}");

Ecosystem Integration

ByteAether.Ulid integrates with popular .NET libraries and frameworks:

  • ASP.NET Core: Integrates as a route or query parameter with a built-in TypeConverter.
  • System.Text.Json (.NET 5.0+): Includes a JsonConverter for serialization and deserialization.
  • EF Core: Supports ULIDs as primary keys or properties via a custom ValueConverter (Ulid to byte[]).
  • Dapper: Supports custom TypeHandler for Ulid to byte[] conversion.
  • MessagePack: Provides a custom MessagePackResolver for Ulid (as byte[]) serialization and deserialization.
  • Newtonsoft.Json: A custom JsonConverter enables Ulid (as string) serialization and deserialization.

Contribute to ByteAether.Ulid

We are committed to the continuous improvement of ByteAether.Ulid. Contributions, including pull requests, bug reports, and feature suggestions, are welcome. Your input is valuable for shaping the project's future.

Upgrade to ByteAether.Ulid v1.3.0 for enhanced control, security, and performance in ULID generation.

Latest posts in the ByteAether.Ulid series

ByteAether.Ulid v1.3.0: Enhanced ULID Generation Control and Security
31 July 2025
Prioritizing Reliability When Milliseconds Aren't Enough
19 June 2025
ULIDs as the Default Choice for Modern Systems: Lessons from Shopify's Payment Infrastructure
11 February 2025