Module: Familia::SecureIdentifier

Included in:
Familia, VerifiableIdentifier
Defined in:
lib/familia/secure_identifier.rb

Overview

Cryptographically secure random identifiers.

Strength tiers

256-bit : cryptographic secrets, session tokens, API keys 128-bit : business/user IDs, product SKUs, non-secret resources 64-bit : request tracing, log correlation, ephemeral tags

All methods use SecureRandom; collisions are probabilistic and scale with the number of generated values, not time.

Instance Method Summary collapse

Instance Method Details

#generate_id(base = 36) ⇒ String

256-bit identifier – the "full-strength" version.

Safe for:

  • cryptographic secrets, session tokens, API keys
  • any identifier that must resist brute-force or intentional guessing

Parameters:

  • base (Integer) (defaults to: 36)

    2–36, defaults to 36 for URL-safe chars

Returns:

  • (String)

    identifier in specified base, zero-padded to minimum length for 256 bits



30
31
32
# File 'lib/familia/secure_identifier.rb', line 30

def generate_id(base = 36)
  _generate_secure_id(bits: 256, base: base)
end

#generate_lite_id(base = 36) ⇒ String

128-bit identifier – the "lite" version.

Safe for:

  • ~ 10¹⁵ generated values (collision risk < 10⁻⁹)
  • business/user IDs, product SKUs, non-secret resources

NOT safe for:

  • security tokens that must resist intentional guessing

Parameters:

  • base (Integer) (defaults to: 36)

    2–36, defaults to 36 for URL-safe chars

Returns:

  • (String)

    identifier in specified base, zero-padded to minimum length for 128 bits



45
46
47
# File 'lib/familia/secure_identifier.rb', line 45

def generate_lite_id(base = 36)
  _generate_secure_id(bits: 128, base: base)
end

#generate_trace_id(base = 36) ⇒ String

64-bit identifier – the "trace" version.

Safe for:

  • request tracing, log correlation, ephemeral tags
  • up to ~ 10⁹ values (collision risk < 10⁻⁶)

NOT safe for:

  • long-lived identifiers or security contexts

Parameters:

  • base (Integer) (defaults to: 36)

    2–36, defaults to 36 for URL-safe chars

Returns:

  • (String)

    identifier in specified base, zero-padded to minimum length for 64 bits



60
61
62
# File 'lib/familia/secure_identifier.rb', line 60

def generate_trace_id(base = 36)
  _generate_secure_id(bits: 64, base: base)
end

#shorten_to_trace_id(hex_id, base: 36) ⇒ String

Creates a deterministic 64-bit trace identifier from a longer hex ID.

This is a convenience method for truncate_hex(hex_id, bits: 64). Useful for creating short, consistent IDs for logging and tracing.

Parameters:

  • hex_id (String)

    The input hexadecimal string.

  • bits (Integer)

    The desired output bit length (e.g., 128, 64). Defaults to 128.

  • base (Integer) (defaults to: 36)

    The numeric base for the output string (2-36). Defaults to 36.

Returns:

  • (String)

    A new, shorter identifier in the specified base.



71
72
73
# File 'lib/familia/secure_identifier.rb', line 71

def shorten_to_trace_id(hex_id, base: 36)
  truncate_hex(hex_id, bits: 64, base: base)
end

#truncate_hex(hex_id, bits: 128, base: 36) ⇒ String

Deterministically truncates a hexadecimal ID to a specified bit length.

This function preserves the most significant bits of the input hex_id to create a shorter, yet still random-looking, identifier.

Parameters:

  • hex_id (String)

    The input hexadecimal string.

  • bits (Integer) (defaults to: 128)

    The desired output bit length (e.g., 128, 64). Defaults to 128.

  • base (Integer) (defaults to: 36)

    The numeric base for the output string (2-36). Defaults to 36.

Returns:

  • (String)

    A new, shorter identifier in the specified base.

Raises:

  • (ArgumentError)

    if hex_id is not a valid hex string, or if input_bits is less than the desired output bits.



86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/familia/secure_identifier.rb', line 86

def truncate_hex(hex_id, bits: 128, base: 36)
  target_length = SecureIdentifier.min_length_for_bits(bits, base)
  input_bits = hex_id.length * 4

  raise ArgumentError, "Invalid hexadecimal string: #{hex_id}" unless hex_id.match?(/\A[0-9a-fA-F]+\z/)

  if input_bits < bits
    raise ArgumentError, "Input bits (#{input_bits}) cannot be less than desired output bits (#{bits})."
  end

  # Truncate by right-shifting to keep the most significant bits
  truncated_int = hex_id.to_i(16) >> (input_bits - bits)
  truncated_int.to_s(base).rjust(target_length, '0')
end