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 |