CrackedRuby logo

CrackedRuby

SecureRandom

Overview

SecureRandom provides an interface to secure random number generators available on different platforms. Ruby's implementation wraps the underlying operating system's cryptographically strong random number generator, such as /dev/urandom on Unix-like systems or CryptoAPI on Windows.

The module generates random values suitable for security applications including authentication tokens, session identifiers, password salts, and cryptographic keys. SecureRandom methods produce unpredictable output that cannot be reproduced without knowledge of the internal generator state.

require 'securerandom'

# Generate random hex string
token = SecureRandom.hex(16)
# => "a1b2c3d4e5f6789012345678abcdef90"

# Generate UUID
uuid = SecureRandom.uuid
# => "f47ac10b-58cc-4372-a567-0e02b2c3d479"

SecureRandom operates as a module containing class methods rather than requiring instantiation. Each method call produces independent random output through the system's entropy source. The module handles the complexity of interfacing with platform-specific random number generators while providing a consistent Ruby API.

Ruby includes SecureRandom in its standard library, requiring explicit loading with require 'securerandom'. The module supports various output formats including binary strings, hexadecimal strings, Base64 encoding, and structured formats like UUIDs.

Basic Usage

SecureRandom provides several methods for generating random data in different formats. The most commonly used methods generate fixed or variable-length random strings suitable for tokens, identifiers, and other security applications.

require 'securerandom'

# Generate random bytes as binary string
random_bytes = SecureRandom.random_bytes(32)
# => "\x8B\x1A\x9F..." (32 bytes of binary data)

# Generate hex-encoded random string
hex_token = SecureRandom.hex(16)
# => "4a2b8c9d1e3f567890abcdef12345678" (32 characters)

# Generate Base64-encoded random string
base64_token = SecureRandom.base64(16)
# => "Sg+4xK2mP9vQ3sT7wY8zA=="

# Generate URL-safe Base64 string
urlsafe_token = SecureRandom.urlsafe_base64(16)
# => "Sg-4xK2mP9vQ3sT7wY8zA_"

The byte length parameter controls the amount of entropy generated, not the output string length. For hex encoding, the output string length equals twice the byte parameter because each byte produces two hexadecimal characters. Base64 encoding produces approximately 4/3 the length of the input bytes.

# Different byte lengths produce different output lengths
SecureRandom.hex(8)    # => 16-character hex string
SecureRandom.hex(16)   # => 32-character hex string
SecureRandom.hex(32)   # => 64-character hex string

# UUID generation provides standardized format
uuid_v4 = SecureRandom.uuid
# => "550e8400-e29b-41d4-a716-446655440000"

SecureRandom methods accept optional parameters for customizing output. The random_number method generates random integers or floats within specified ranges.

# Generate random integer from 0 to n-1
random_int = SecureRandom.random_number(100)
# => 42 (integer between 0 and 99)

# Generate random float from 0.0 to 1.0
random_float = SecureRandom.random_number
# => 0.8471638688711035

# Generate large random integers
big_random = SecureRandom.random_number(2**128)
# => 123456789012345678901234567890123456789

The alphanumeric method produces strings containing only letters and digits, useful for human-readable tokens that avoid special characters.

# Generate alphanumeric string
readable_token = SecureRandom.alphanumeric(12)
# => "aB3xY7mK9pQs"

# Specify custom length
long_token = SecureRandom.alphanumeric(32)
# => "xY9mN2pQ5rS8tU1vW4zA7bC0dE3fG6hJ"

Error Handling & Debugging

SecureRandom can raise exceptions when the underlying random number generator becomes unavailable or when invalid parameters are provided. The primary exception type is NotImplementedError, which occurs when no secure random source is available on the current platform.

require 'securerandom'

begin
  token = SecureRandom.hex(16)
rescue NotImplementedError => e
  # Handle missing secure random source
  puts "Secure random generation not available: #{e.message}"
  # Fallback strategy or error reporting
end

Parameter validation errors occur when methods receive invalid input. SecureRandom methods expect non-negative integer parameters for length specifications.

begin
  # Invalid negative length
  SecureRandom.hex(-1)
rescue ArgumentError => e
  puts "Invalid parameter: #{e.message}"
  # => "negative argument: -1"
end

begin
  # Invalid parameter type
  SecureRandom.random_number("invalid")
rescue ArgumentError => e
  puts "Type error: #{e.message}"
  # => "invalid argument - invalid"
end

Debugging SecureRandom issues often involves verifying the availability of entropy sources. On Unix-like systems, SecureRandom depends on /dev/urandom accessibility.

# Check if secure random is available
def secure_random_available?
  SecureRandom.hex(1)
  true
rescue NotImplementedError
  false
end

# Enhanced error reporting
def generate_secure_token(length = 32)
  SecureRandom.hex(length)
rescue NotImplementedError => e
  error_details = {
    platform: RUBY_PLATFORM,
    ruby_version: RUBY_VERSION,
    error_message: e.message,
    timestamp: Time.now.iso8601
  }
  raise "SecureRandom unavailable: #{error_details}"
rescue ArgumentError => e
  raise "Invalid token length #{length}: #{e.message}"
end

Memory exhaustion can occur when generating extremely large random values. SecureRandom generates data in memory before returning it, so requesting massive amounts of random data can consume significant resources.

# Risky: generates 1GB of random data in memory
begin
  huge_data = SecureRandom.random_bytes(1024 * 1024 * 1024)
rescue StandardError => e
  puts "Memory or system error: #{e.message}"
end

# Better: generate data in chunks
def generate_large_random_file(filename, total_bytes, chunk_size = 1024)
  File.open(filename, 'wb') do |file|
    remaining = total_bytes
    while remaining > 0
      chunk = [chunk_size, remaining].min
      file.write(SecureRandom.random_bytes(chunk))
      remaining -= chunk
    end
  end
end

Thread Safety & Concurrency

SecureRandom methods are thread-safe and can be called concurrently from multiple threads without synchronization. The underlying random number generator handles concurrent access internally, ensuring each call produces independent random output.

require 'securerandom'

# Safe concurrent usage
threads = Array.new(10) do |i|
  Thread.new do
    tokens = Array.new(100) { SecureRandom.hex(16) }
    puts "Thread #{i}: generated #{tokens.length} unique tokens"
    tokens
  end
end

all_tokens = threads.flat_map(&:value)
unique_tokens = all_tokens.uniq

puts "Total tokens: #{all_tokens.length}"
puts "Unique tokens: #{unique_tokens.length}"
# All tokens should be unique despite concurrent generation

Thread-local random state is not maintained by SecureRandom. Each method call accesses the system's shared entropy pool, which provides cryptographically secure randomness across all threads without requiring thread-specific initialization or cleanup.

# Concurrent token generation for user sessions
class SessionManager
  def self.generate_session_id
    SecureRandom.urlsafe_base64(32)
  end
  
  def self.create_sessions(user_count)
    threads = Array.new(user_count) do |user_id|
      Thread.new do
        session_id = generate_session_id
        csrf_token = SecureRandom.hex(32)
        api_key = SecureRandom.alphanumeric(40)
        
        {
          user_id: user_id,
          session_id: session_id,
          csrf_token: csrf_token,
          api_key: api_key,
          created_at: Time.now
        }
      end
    end
    
    threads.map(&:value)
  end
end

Race conditions cannot occur with SecureRandom output because each method call produces completely independent values. Unlike pseudorandom generators that maintain internal state, SecureRandom draws from the operating system's entropy pool without exposing or modifying shared state.

# No synchronization needed for independent random generation
require 'thread'

class TokenCache
  def initialize
    @cache = {}
    @mutex = Mutex.new
  end
  
  def get_or_generate_token(key)
    @mutex.synchronize do
      @cache[key] ||= SecureRandom.hex(32)
    end
  end
  
  # SecureRandom calls don't need synchronization
  def generate_unique_tokens(count)
    Array.new(count) { SecureRandom.uuid }
  end
end

Producer-consumer patterns work seamlessly with SecureRandom because token generation does not require coordination between threads.

require 'thread'

# Token producer-consumer pattern
token_queue = Queue.new

# Producer threads
producers = Array.new(3) do |i|
  Thread.new do
    100.times do
      token = {
        id: SecureRandom.uuid,
        secret: SecureRandom.hex(24),
        expires_at: Time.now + 3600
      }
      token_queue << token
    end
  end
end

# Consumer thread
consumer = Thread.new do
  processed = 0
  while processed < 300
    token = token_queue.pop
    # Process token (database insertion, validation, etc.)
    processed += 1
  end
  puts "Processed #{processed} tokens"
end

producers.each(&:join)
consumer.join

Performance & Memory

SecureRandom performance varies by platform and requested data size. Generating small amounts of random data (up to several kilobytes) completes quickly, while large requests require more time and memory proportional to the requested size.

require 'benchmark'
require 'securerandom'

# Performance comparison of different methods
Benchmark.bm(20) do |x|
  x.report("hex(16)") do
    10_000.times { SecureRandom.hex(16) }
  end
  
  x.report("base64(16)") do
    10_000.times { SecureRandom.base64(16) }
  end
  
  x.report("uuid") do
    10_000.times { SecureRandom.uuid }
  end
  
  x.report("random_bytes(16)") do
    10_000.times { SecureRandom.random_bytes(16) }
  end
end

#                          user     system      total        real
# hex(16)              0.045000   0.012000   0.057000 (  0.058234)
# base64(16)           0.041000   0.011000   0.052000 (  0.052891)
# uuid                 0.048000   0.013000   0.061000 (  0.062134)
# random_bytes(16)     0.039000   0.010000   0.049000 (  0.049785)

Memory usage scales linearly with the requested random data size. SecureRandom generates the entire requested amount in memory before returning it, so large requests can consume substantial memory.

# Memory-efficient random data generation
def generate_random_file(filename, size_mb, chunk_size_kb = 64)
  chunk_bytes = chunk_size_kb * 1024
  total_bytes = size_mb * 1024 * 1024
  
  File.open(filename, 'wb') do |file|
    bytes_written = 0
    while bytes_written < total_bytes
      remaining = total_bytes - bytes_written
      chunk_size = [chunk_bytes, remaining].min
      
      chunk = SecureRandom.random_bytes(chunk_size)
      file.write(chunk)
      bytes_written += chunk_size
      
      # Optional: report progress
      progress = (bytes_written.to_f / total_bytes * 100).round(1)
      puts "Progress: #{progress}%" if bytes_written % (1024 * 1024) == 0
    end
  end
end

Caching random values can improve performance when the same random data is needed multiple times, but caching reduces security by making values predictable.

# Performance optimization with controlled caching
class TokenGenerator
  def initialize(cache_size = 1000)
    @token_cache = Array.new(cache_size) { SecureRandom.hex(16) }
    @cache_index = 0
    @mutex = Mutex.new
  end
  
  # Fast but less secure - tokens may repeat
  def quick_token
    @mutex.synchronize do
      token = @token_cache[@cache_index]
      @cache_index = (@cache_index + 1) % @token_cache.length
      token
    end
  end
  
  # Slower but fully secure - always unique
  def secure_token
    SecureRandom.hex(16)
  end
end

Bulk generation strategies can optimize performance when many random values are needed simultaneously.

# Efficient bulk random value generation
class BulkTokenGenerator
  def self.generate_tokens(count, token_length = 16)
    # Generate one large random string and split it
    total_bytes = count * token_length
    bulk_random = SecureRandom.random_bytes(total_bytes)
    
    tokens = []
    count.times do |i|
      start_pos = i * token_length
      token_bytes = bulk_random[start_pos, token_length]
      tokens << token_bytes.unpack1('H*')  # Convert to hex
    end
    
    tokens
  end
  
  def self.generate_structured_data(count)
    # More efficient than individual SecureRandom calls
    random_pool = SecureRandom.random_bytes(count * 64)  # 64 bytes per record
    
    count.times.map do |i|
      offset = i * 64
      {
        id: random_pool[offset, 16].unpack1('H*'),
        token: random_pool[offset + 16, 32].unpack1('H*'),
        salt: random_pool[offset + 48, 16].unpack1('H*')
      }
    end
  end
end

Production Patterns

Web applications commonly use SecureRandom for generating session identifiers, CSRF tokens, API keys, and password reset tokens. Production usage requires careful consideration of token length, format, and storage.

# Rails-style session and security token generation
class SecurityTokens
  # Session ID generation
  def self.generate_session_id
    SecureRandom.urlsafe_base64(32)  # 256 bits of entropy
  end
  
  # CSRF token for form protection
  def self.generate_csrf_token
    SecureRandom.base64(32)  # 256 bits, safe for hidden form fields
  end
  
  # API key generation with custom format
  def self.generate_api_key(prefix = 'sk')
    random_part = SecureRandom.alphanumeric(32)
    "#{prefix}_#{random_part}"
  end
  
  # Password reset token with expiration tracking
  def self.generate_reset_token
    {
      token: SecureRandom.urlsafe_base64(32),
      expires_at: Time.now + 3600,  # 1 hour expiration
      created_at: Time.now
    }
  end
end

Database applications often store random tokens alongside user data. Token generation should consider database constraints and indexing requirements.

# Database-aware token generation
class UserToken
  def self.generate_unique_token(model_class, column_name, length = 32)
    max_attempts = 100
    attempts = 0
    
    begin
      token = SecureRandom.urlsafe_base64(length)
      attempts += 1
      
      # Check for uniqueness in database
      existing = model_class.find_by(column_name => token)
      return token unless existing
      
      raise "Failed to generate unique token after #{max_attempts} attempts" if attempts >= max_attempts
    end while existing
  end
  
  # Batch token generation for multiple users
  def self.generate_batch_tokens(count, length = 32)
    tokens = Set.new
    
    while tokens.size < count
      token = SecureRandom.urlsafe_base64(length)
      tokens.add(token)
    end
    
    tokens.to_a
  end
end

Microservices and distributed systems require tokens that remain secure across service boundaries and network transmission.

# Service-to-service authentication tokens
class ServiceAuthentication
  def self.generate_service_token(service_name, environment)
    timestamp = Time.now.to_i
    random_component = SecureRandom.hex(16)
    
    token_data = {
      service: service_name,
      env: environment,
      issued_at: timestamp,
      nonce: random_component
    }
    
    # In production, this would be signed with a secret key
    token_payload = Base64.strict_encode64(token_data.to_json)
    signature = SecureRandom.hex(32)  # Placeholder for actual HMAC
    
    "#{token_payload}.#{signature}"
  end
  
  # Request correlation IDs for distributed tracing
  def self.generate_correlation_id
    timestamp = Time.now.strftime('%Y%m%d%H%M%S')
    random_suffix = SecureRandom.alphanumeric(8)
    "#{timestamp}-#{random_suffix}"
  end
end

Load balancing and caching considerations become important when SecureRandom usage scales to high-traffic applications.

# High-throughput token generation with monitoring
class HighVolumeTokenGenerator
  def initialize(pool_size = 10000)
    @token_pool = Queue.new
    @pool_size = pool_size
    @generated_count = 0
    @mutex = Mutex.new
    
    populate_pool
    start_background_replenishment
  end
  
  def get_token
    begin
      token = @token_pool.pop(non_block: true)
      increment_usage_counter
      token
    rescue ThreadError
      # Pool empty, generate immediately
      SecureRandom.hex(16)
    end
  end
  
  private
  
  def populate_pool
    @pool_size.times do
      @token_pool << SecureRandom.hex(16)
    end
  end
  
  def start_background_replenishment
    Thread.new do
      loop do
        if @token_pool.size < @pool_size / 2
          (@pool_size - @token_pool.size).times do
            @token_pool << SecureRandom.hex(16)
          end
        end
        sleep 1
      end
    end
  end
  
  def increment_usage_counter
    @mutex.synchronize { @generated_count += 1 }
  end
  
  def stats
    {
      pool_size: @token_pool.size,
      generated_count: @generated_count,
      timestamp: Time.now
    }
  end
end

Reference

Core Methods

Method Parameters Returns Description
SecureRandom.random_bytes(n) n (Integer) String Generate n random bytes as binary string
SecureRandom.hex(n = nil) n (Integer, optional) String Generate hex string from n random bytes
SecureRandom.base64(n = nil) n (Integer, optional) String Generate Base64 string from n random bytes
SecureRandom.urlsafe_base64(n = nil, padding = false) n (Integer, optional), padding (Boolean) String Generate URL-safe Base64 string
SecureRandom.uuid None String Generate RFC 4122 version 4 UUID
SecureRandom.random_number(n = 0) n (Numeric, optional) Integer or Float Generate random number 0 <= x < n
SecureRandom.alphanumeric(n = nil) n (Integer, optional) String Generate alphanumeric string

Default Byte Lengths

Method Default Bytes Default Output Length
hex() 16 32 characters
base64() 16 ~22 characters
urlsafe_base64() 16 ~22 characters
alphanumeric() 16 16 characters

UUID Format

xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
  • x: any hexadecimal digit (0-9, a-f)
  • 4: version identifier (always 4 for random UUID)
  • y: variant bits (8, 9, A, or B)

Exception Types

Exception Condition Resolution
NotImplementedError No secure random source available Check platform entropy sources
ArgumentError Invalid parameter type or value Validate input parameters
StandardError System-level errors (rare) Check system resources and permissions

Character Sets

Method Character Set Total Characters
hex 0-9, a-f 16
base64 A-Z, a-z, 0-9, +, / 64
urlsafe_base64 A-Z, a-z, 0-9, -, _ 64
alphanumeric A-Z, a-z, 0-9 62

Entropy Calculations

Method Bytes Bits of Entropy Possible Values
hex(8) 8 64 2^64
hex(16) 16 128 2^128
hex(32) 32 256 2^256
uuid 16 122 2^122 (6 bits fixed)
base64(16) 16 128 2^128
alphanumeric(16) ~10 ~79 62^16 ≈ 2^95

Platform-Specific Entropy Sources

Platform Primary Source Fallback Source
Linux /dev/urandom /dev/random
macOS /dev/urandom /dev/random
Windows CryptGenRandom None
FreeBSD /dev/urandom /dev/random
OpenBSD arc4random /dev/urandom

Thread Safety Summary

Aspect Thread Safe Notes
Method calls Yes All methods are thread-safe
Concurrent generation Yes No synchronization required
Output uniqueness Yes Independent random values per call
State sharing No No shared state between calls
Performance impact Minimal System entropy source handles concurrency