Overview
Ruby implements public key cryptography through the OpenSSL
standard library, which provides bindings to the OpenSSL C library. The primary classes include OpenSSL::PKey
for key management, OpenSSL::Cipher
for encryption operations, and OpenSSL::X509
for certificate handling.
Public key cryptography enables secure communication between parties without prior key exchange. Ruby's OpenSSL integration supports multiple algorithms including RSA, DSA, and Elliptic Curve (EC) cryptography. The library handles key generation, encryption, decryption, digital signatures, and certificate operations.
The OpenSSL::PKey
module contains algorithm-specific classes like OpenSSL::PKey::RSA
, OpenSSL::PKey::DSA
, and OpenSSL::PKey::EC
. Each class provides methods for key generation, serialization, and cryptographic operations. Ruby automatically manages memory allocation and deallocation for cryptographic objects through garbage collection.
require 'openssl'
# Generate RSA key pair
rsa_key = OpenSSL::PKey::RSA.generate(2048)
puts rsa_key.public_key.to_pem
# => -----BEGIN PUBLIC KEY-----
# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQE...
Ruby's cryptographic operations integrate with the broader ecosystem through consistent interfaces. The library supports PEM and DER formats for key serialization, standard padding schemes, and industry-standard digest algorithms.
# Check if key contains private component
puts rsa_key.private? # => true
puts rsa_key.public_key.private? # => false
# Get key parameters
puts rsa_key.n.to_s(16) # Public modulus in hex
puts rsa_key.e.to_s(16) # Public exponent in hex
The OpenSSL integration maintains compatibility with external systems and standard cryptographic protocols. Ruby applications can generate keys compatible with other languages and systems, exchange certificates with web servers, and implement standard protocols like TLS handshakes.
Basic Usage
Key Generation
Ruby generates cryptographic key pairs through class-level methods on algorithm-specific classes. The generation process creates both public and private keys simultaneously, with the private key containing both components.
require 'openssl'
# RSA key generation with different sizes
rsa_2048 = OpenSSL::PKey::RSA.generate(2048)
rsa_4096 = OpenSSL::PKey::RSA.generate(4096)
# DSA key generation
dsa_key = OpenSSL::PKey::DSA.generate(2048)
# Elliptic Curve key generation
ec_key = OpenSSL::PKey::EC.generate('prime256v1')
Key generation accepts size parameters for RSA and DSA algorithms, while elliptic curve keys require curve name specification. Ruby supports numerous named curves including NIST standard curves and Brainpool curves.
Encryption and Decryption
Public key encryption uses the recipient's public key for encryption and their private key for decryption. Ruby implements this through the public_encrypt
and private_decrypt
methods.
message = "Secret message"
rsa_key = OpenSSL::PKey::RSA.generate(2048)
# Encrypt with public key
encrypted = rsa_key.public_encrypt(message)
puts encrypted.length # => 256 (for 2048-bit key)
# Decrypt with private key
decrypted = rsa_key.private_decrypt(encrypted)
puts decrypted # => "Secret message"
Ruby supports multiple padding schemes through optional parameters. The default OpenSSL::PKey::RSA::PKCS1_PADDING
provides security for most applications, while OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
offers additional security features.
# Using OAEP padding
encrypted_oaep = rsa_key.public_encrypt(message, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
decrypted_oaep = rsa_key.private_decrypt(encrypted_oaep, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
Digital Signatures
Digital signatures provide authentication and non-repudiation through cryptographic proof of message origin. Ruby implements signing through digest algorithms combined with private key operations.
message = "Document to sign"
rsa_key = OpenSSL::PKey::RSA.generate(2048)
# Create signature using SHA256 digest
digest = OpenSSL::Digest::SHA256.new
signature = rsa_key.sign(digest, message)
# Verify signature using public key
public_key = rsa_key.public_key
verified = public_key.verify(digest, signature, message)
puts verified # => true
Ruby supports various digest algorithms including SHA1, SHA256, SHA384, and SHA512. The choice of digest algorithm affects security strength and performance characteristics.
Key Serialization
Ruby serializes keys to standard formats for storage and transmission. The PEM format uses Base64 encoding with header and footer lines, while DER format provides binary encoding.
rsa_key = OpenSSL::PKey::RSA.generate(2048)
# PEM format serialization
pem_private = rsa_key.to_pem
pem_public = rsa_key.public_key.to_pem
# DER format serialization
der_private = rsa_key.to_der
der_public = rsa_key.public_key.to_der
# Load keys from serialized data
loaded_key = OpenSSL::PKey::RSA.new(pem_private)
loaded_public = OpenSSL::PKey::RSA.new(pem_public)
Advanced Usage
Key Loading and Validation
Ruby loads keys from various sources including files, strings, and external systems. The loading process validates key structure and mathematical properties to ensure cryptographic correctness.
# Load key from file with error handling
begin
key_data = File.read('private_key.pem')
rsa_key = OpenSSL::PKey::RSA.new(key_data)
rescue OpenSSL::PKey::RSAError => e
puts "Invalid RSA key: #{e.message}"
end
# Load password-protected key
encrypted_key = File.read('encrypted_key.pem')
password = 'secret_password'
rsa_key = OpenSSL::PKey::RSA.new(encrypted_key, password)
# Validate key consistency
if rsa_key.private?
# Check if public/private components match
test_message = "validation_test"
encrypted = rsa_key.public_encrypt(test_message)
decrypted = rsa_key.private_decrypt(encrypted)
puts "Key valid: #{test_message == decrypted}"
end
Advanced key validation includes parameter checking for mathematical correctness. RSA keys must satisfy specific mathematical relationships between primes and exponents.
def validate_rsa_key(key)
return false unless key.private?
# Check basic parameter existence
return false if key.n.nil? || key.e.nil? || key.d.nil?
# Verify mathematical relationships
# e * d ≡ 1 (mod φ(n))
phi_n = (key.p - 1) * (key.q - 1)
return (key.e * key.d) % phi_n == 1
rescue => e
puts "Validation error: #{e.message}"
false
end
Hybrid Encryption Systems
Production systems combine symmetric and asymmetric encryption for performance and security. Ruby implements hybrid systems using public key cryptography for key exchange and symmetric algorithms for data encryption.
def hybrid_encrypt(data, public_key)
# Generate random AES key
aes_key = OpenSSL::Random.random_bytes(32) # 256-bit key
iv = OpenSSL::Random.random_bytes(16) # 128-bit IV
# Encrypt data with AES
cipher = OpenSSL::Cipher::AES.new(256, :CBC)
cipher.encrypt
cipher.key = aes_key
cipher.iv = iv
encrypted_data = cipher.update(data) + cipher.final
# Encrypt AES key with RSA public key
encrypted_key = public_key.public_encrypt(aes_key)
# Return combined structure
{
encrypted_key: encrypted_key,
iv: iv,
encrypted_data: encrypted_data
}
end
def hybrid_decrypt(encrypted_package, private_key)
# Decrypt AES key with RSA private key
aes_key = private_key.private_decrypt(encrypted_package[:encrypted_key])
# Decrypt data with AES
decipher = OpenSSL::Cipher::AES.new(256, :CBC)
decipher.decrypt
decipher.key = aes_key
decipher.iv = encrypted_package[:iv]
decrypted_data = decipher.update(encrypted_package[:encrypted_data])
decrypted_data << decipher.final
end
# Usage example
message = "Large document with sensitive information..." * 100
rsa_key = OpenSSL::PKey::RSA.generate(2048)
encrypted_package = hybrid_encrypt(message, rsa_key.public_key)
decrypted_message = hybrid_decrypt(encrypted_package, rsa_key)
puts decrypted_message == message # => true
Certificate Generation and Management
Ruby creates and manages X.509 certificates for public key infrastructure. Certificate generation involves key pairs, distinguished names, and various extensions for specific use cases.
def generate_self_signed_cert(key, subject_name, days_valid = 365)
cert = OpenSSL::X509::Certificate.new
cert.version = 2
cert.serial = rand(2**32)
# Set validity period
cert.not_before = Time.now
cert.not_after = Time.now + (days_valid * 24 * 60 * 60)
# Set subject and issuer (same for self-signed)
name = OpenSSL::X509::Name.parse(subject_name)
cert.subject = name
cert.issuer = name
# Set public key
cert.public_key = key.public_key
# Add extensions
ef = OpenSSL::X509::ExtensionFactory.new
ef.subject_certificate = cert
ef.issuer_certificate = cert
cert.add_extension(ef.create_extension("basicConstraints", "CA:TRUE", true))
cert.add_extension(ef.create_extension("keyUsage", "keyCertSign, cRLSign", true))
cert.add_extension(ef.create_extension("subjectKeyIdentifier", "hash", false))
cert.add_extension(ef.create_extension("authorityKeyIdentifier", "keyid:always", false))
# Sign certificate
cert.sign(key, OpenSSL::Digest::SHA256.new)
cert
end
# Generate CA certificate
ca_key = OpenSSL::PKey::RSA.generate(4096)
ca_cert = generate_self_signed_cert(
ca_key,
"/C=US/O=Example Corp/CN=Example CA",
3650 # 10 years
)
puts ca_cert.to_pem
Elliptic Curve Operations
Elliptic Curve cryptography provides equivalent security to RSA with smaller key sizes. Ruby supports various named curves and curve-specific operations including key agreement protocols.
# Generate EC key pairs on different curves
p256_key = OpenSSL::PKey::EC.generate('prime256v1')
p384_key = OpenSSL::PKey::EC.generate('secp384r1')
p521_key = OpenSSL::PKey::EC.generate('secp521r1')
# ECDH key agreement
alice_key = OpenSSL::PKey::EC.generate('prime256v1')
bob_key = OpenSSL::PKey::EC.generate('prime256v1')
# Derive shared secret
alice_shared = alice_key.dh_compute_key(bob_key.public_key)
bob_shared = bob_key.dh_compute_key(alice_key.public_key)
puts alice_shared == bob_shared # => true
puts alice_shared.length # => 32 (for P-256 curve)
# ECDSA signatures with explicit digest
message = "Message to sign with ECDSA"
ec_key = OpenSSL::PKey::EC.generate('prime256v1')
digest = OpenSSL::Digest::SHA256.digest(message)
signature = ec_key.dsa_sign_asn1(digest)
verified = ec_key.dsa_verify_asn1(digest, signature)
puts verified # => true
Error Handling & Debugging
Common Cryptographic Errors
Cryptographic operations generate specific error types that require targeted handling strategies. Ruby's OpenSSL binding raises detailed exceptions for different failure modes.
def safe_key_operation(key_data, operation)
begin
key = OpenSSL::PKey::RSA.new(key_data)
case operation
when :encrypt
yield key if block_given?
when :decrypt
yield key if block_given?
end
rescue OpenSSL::PKey::RSAError => e
case e.message
when /data too large for key size/
puts "ERROR: Message exceeds key capacity. Use hybrid encryption."
when /padding check failed/
puts "ERROR: Incorrect padding or corrupted ciphertext."
when /bad decrypt/
puts "ERROR: Wrong private key or corrupted data."
else
puts "RSA Error: #{e.message}"
end
nil
rescue OpenSSL::Cipher::CipherError => e
puts "Cipher Error: #{e.message}"
nil
end
end
# Example usage with error handling
rsa_key = OpenSSL::PKey::RSA.generate(1024) # Small key for demonstration
large_message = "A" * 200 # Too large for 1024-bit key
safe_key_operation(rsa_key.to_pem, :encrypt) do |key|
encrypted = key.public_encrypt(large_message) # Will raise error
end
Key Format Validation
Invalid key formats produce specific error patterns. Ruby provides methods to detect and diagnose key format problems before cryptographic operations.
def validate_key_format(key_data)
formats = [
{ type: OpenSSL::PKey::RSA, name: 'RSA' },
{ type: OpenSSL::PKey::DSA, name: 'DSA' },
{ type: OpenSSL::PKey::EC, name: 'EC' }
]
formats.each do |format|
begin
key = format[:type].new(key_data)
puts "Valid #{format[:name]} key detected"
puts "- Key size: #{key.respond_to?(:n) ? key.n.num_bits : 'N/A'} bits"
puts "- Has private key: #{key.private?}"
return { valid: true, type: format[:name], key: key }
rescue OpenSSL::PKey::PKeyError
next
end
end
# Try to identify common format issues
if key_data.include?('-----BEGIN')
if key_data.include?('ENCRYPTED')
return { valid: false, error: 'Password-protected key requires password' }
else
return { valid: false, error: 'Malformed PEM structure' }
end
else
return { valid: false, error: 'Unknown key format - expected PEM or DER' }
end
end
# Test with various key formats
test_cases = [
"-----BEGIN RSA PRIVATE KEY-----\nInvalid_content\n-----END RSA PRIVATE KEY-----",
"Invalid binary data",
File.read('encrypted_key.pem') # Encrypted key without password
]
test_cases.each_with_index do |key_data, i|
puts "Test #{i + 1}: #{validate_key_format(key_data)}"
end
Signature Verification Failures
Digital signature verification fails for multiple reasons including wrong keys, tampered data, or incorrect digest algorithms. Ruby applications must handle these scenarios gracefully.
def comprehensive_signature_verify(message, signature, public_key, digest_class = OpenSSL::Digest::SHA256)
begin
digest = digest_class.new
result = public_key.verify(digest, signature, message)
unless result
# Perform diagnostic checks
diagnostics = {
key_type: public_key.class.name,
key_size: public_key.respond_to?(:n) ? public_key.n.num_bits : nil,
signature_size: signature.length,
message_size: message.length,
digest_type: digest_class.name
}
puts "Signature verification failed. Diagnostics:"
diagnostics.each { |k, v| puts " #{k}: #{v}" }
# Test with different digest algorithms
[OpenSSL::Digest::SHA1, OpenSSL::Digest::SHA256, OpenSSL::Digest::SHA512].each do |alt_digest|
next if alt_digest == digest_class
begin
alt_result = public_key.verify(alt_digest.new, signature, message)
puts " Alternative #{alt_digest.name}: #{alt_result ? 'SUCCESS' : 'failed'}"
rescue => e
puts " Alternative #{alt_digest.name}: error - #{e.message}"
end
end
end
result
rescue OpenSSL::PKey::PKeyError => e
puts "Key error during verification: #{e.message}"
false
rescue => e
puts "Unexpected error: #{e.message}"
false
end
end
# Demonstration with intentionally failing verification
rsa_key = OpenSSL::PKey::RSA.generate(2048)
message = "Original message"
tampered_message = "Tampered message"
digest = OpenSSL::Digest::SHA256.new
signature = rsa_key.sign(digest, message)
# This will fail and show diagnostics
comprehensive_signature_verify(tampered_message, signature, rsa_key.public_key)
Memory and Resource Management
Cryptographic operations consume significant memory and CPU resources. Ruby's garbage collector handles most cleanup automatically, but applications must manage resource usage in high-throughput scenarios.
def secure_key_cleanup
keys = []
begin
# Generate multiple keys for demonstration
10.times do |i|
key = OpenSSL::PKey::RSA.generate(2048)
keys << key
# Force key data to be overwritten (Ruby limitation: not guaranteed)
if key.private?
# Clear sensitive data from memory (best effort)
key.instance_variables.each do |var|
key.instance_variable_set(var, nil)
end
end
end
ensure
# Explicit cleanup
keys.clear
GC.start # Force garbage collection
end
puts "Memory cleanup completed"
end
# Monitor memory usage during cryptographic operations
def memory_benchmark
start_memory = `ps -o rss= -p #{$$}`.to_i
yield if block_given?
end_memory = `ps -o rss= -p #{$$}`.to_i
puts "Memory delta: #{end_memory - start_memory} KB"
end
memory_benchmark do
secure_key_cleanup
end
Performance & Memory
Cryptographic Algorithm Performance
Different public key algorithms exhibit varying performance characteristics for key generation, encryption, and signature operations. Ruby's OpenSSL integration reflects the underlying C library performance.
require 'benchmark'
def performance_comparison
key_sizes = [1024, 2048, 4096]
algorithms = [:rsa, :dsa]
puts "Key Generation Performance (5 iterations each):"
puts "-" * 60
key_sizes.each do |size|
algorithms.each do |alg|
time = Benchmark.realtime do
5.times do
case alg
when :rsa
OpenSSL::PKey::RSA.generate(size)
when :dsa
OpenSSL::PKey::DSA.generate(size)
end
end
end
avg_time = time / 5
puts sprintf("%-8s %4d-bit: %8.3f seconds/key", alg.to_s.upcase, size, avg_time)
end
end
# Elliptic curve comparison
puts "\nElliptic Curve Generation:"
curves = ['prime256v1', 'secp384r1', 'secp521r1']
curves.each do |curve|
time = Benchmark.realtime do
10.times { OpenSSL::PKey::EC.generate(curve) }
end
avg_time = time / 10
puts sprintf("%-12s: %8.3f seconds/key", curve, avg_time)
end
end
performance_comparison
Encryption Operation Scaling
Public key encryption performance scales non-linearly with key size and message length. Understanding these characteristics helps optimize application performance.
def encryption_benchmarks
key_sizes = [1024, 2048, 4096]
message_sizes = [16, 64, 100, 190] # Bytes, staying under key limits
puts "RSA Encryption Performance (100 operations each):"
puts "Key Size | Msg Size | Encrypt (ms) | Decrypt (ms) | Total (ms)"
puts "-" * 65
key_sizes.each do |key_size|
rsa_key = OpenSSL::PKey::RSA.generate(key_size)
max_message_size = (key_size / 8) - 42 # PKCS#1 v1.5 padding overhead
message_sizes.each do |msg_size|
next if msg_size > max_message_size
message = "X" * msg_size
# Encryption benchmark
encrypt_time = Benchmark.realtime do
100.times { rsa_key.public_encrypt(message) }
end
# Prepare encrypted message for decryption benchmark
encrypted = rsa_key.public_encrypt(message)
# Decryption benchmark
decrypt_time = Benchmark.realtime do
100.times { rsa_key.private_decrypt(encrypted) }
end
encrypt_ms = (encrypt_time * 1000) / 100
decrypt_ms = (decrypt_time * 1000) / 100
total_ms = encrypt_ms + decrypt_ms
puts sprintf("%8d | %8d | %12.2f | %12.2f | %10.2f",
key_size, msg_size, encrypt_ms, decrypt_ms, total_ms)
end
end
end
encryption_benchmarks
Memory Usage Optimization
Large-scale cryptographic operations require careful memory management to prevent excessive allocation and garbage collection pressure.
class CryptoPool
def initialize(key_size: 2048, pool_size: 10)
@key_size = key_size
@available_keys = []
@in_use_keys = []
@mutex = Mutex.new
# Pre-generate key pool
pool_size.times do
@available_keys << OpenSSL::PKey::RSA.generate(@key_size)
end
end
def with_key
key = acquire_key
begin
yield key
ensure
release_key(key)
end
end
private
def acquire_key
@mutex.synchronize do
if @available_keys.empty?
# Generate new key if pool exhausted
key = OpenSSL::PKey::RSA.generate(@key_size)
puts "Pool exhausted - generating new key"
else
key = @available_keys.pop
end
@in_use_keys << key
key
end
end
def release_key(key)
@mutex.synchronize do
@in_use_keys.delete(key)
@available_keys << key
end
end
end
# Usage example with memory monitoring
def test_crypto_pool
pool = CryptoPool.new(key_size: 2048, pool_size: 5)
start_memory = `ps -o rss= -p #{$$}`.to_i
# Simulate high-volume operations
1000.times do |i|
pool.with_key do |key|
message = "Message #{i}"
encrypted = key.public_encrypt(message)
decrypted = key.private_decrypt(encrypted)
end
if i % 100 == 0
current_memory = `ps -o rss= -p #{$$}`.to_i
puts "Operation #{i}: Memory usage #{current_memory} KB (+#{current_memory - start_memory})"
GC.start if (current_memory - start_memory) > 10000 # Force GC if using >10MB extra
end
end
end_memory = `ps -o rss= -p #{$$}`.to_i
puts "Final memory delta: #{end_memory - start_memory} KB"
end
test_crypto_pool
Signature Performance Analysis
Digital signature operations exhibit different performance patterns than encryption, with verification typically faster than signing.
def signature_performance_analysis
algorithms = [
{ type: OpenSSL::PKey::RSA, size: 2048, name: 'RSA-2048' },
{ type: OpenSSL::PKey::RSA, size: 4096, name: 'RSA-4096' },
{ type: OpenSSL::PKey::DSA, size: 2048, name: 'DSA-2048' }
]
ec_curves = [
{ curve: 'prime256v1', name: 'ECDSA-P256' },
{ curve: 'secp384r1', name: 'ECDSA-P384' },
{ curve: 'secp521r1', name: 'ECDSA-P521' }
]
message = "Performance test message for signature benchmarking" * 10
iterations = 100
puts "Digital Signature Performance (#{iterations} operations):"
puts "Algorithm | Sign (ms/op) | Verify (ms/op) | Ratio"
puts "-" * 55
# RSA and DSA benchmarks
algorithms.each do |alg_config|
key = alg_config[:type].generate(alg_config[:size])
digest = OpenSSL::Digest::SHA256.new
# Signing benchmark
sign_time = Benchmark.realtime do
iterations.times { key.sign(digest, message) }
end
# Prepare signature for verification
signature = key.sign(digest, message)
public_key = key.public_key
# Verification benchmark
verify_time = Benchmark.realtime do
iterations.times { public_key.verify(digest, signature, message) }
end
sign_ms = (sign_time * 1000) / iterations
verify_ms = (verify_time * 1000) / iterations
ratio = sign_ms / verify_ms
puts sprintf("%-12s | %12.3f | %14.3f | %5.1fx",
alg_config[:name], sign_ms, verify_ms, ratio)
end
# Elliptic curve benchmarks
ec_curves.each do |curve_config|
key = OpenSSL::PKey::EC.generate(curve_config[:curve])
digest = OpenSSL::Digest::SHA256.digest(message)
# ECDSA signing
sign_time = Benchmark.realtime do
iterations.times { key.dsa_sign_asn1(digest) }
end
signature = key.dsa_sign_asn1(digest)
# ECDSA verification
verify_time = Benchmark.realtime do
iterations.times { key.dsa_verify_asn1(digest, signature) }
end
sign_ms = (sign_time * 1000) / iterations
verify_ms = (verify_time * 1000) / iterations
ratio = sign_ms / verify_ms
puts sprintf("%-12s | %12.3f | %14.3f | %5.1fx",
curve_config[:name], sign_ms, verify_ms, ratio)
end
end
signature_performance_analysis
Production Patterns
Configuration Management
Production cryptographic systems require secure configuration management with proper key rotation, algorithm selection, and security parameter validation.
class CryptoConfig
SUPPORTED_ALGORITHMS = {
rsa: { min_size: 2048, recommended_size: 3072, max_age_days: 730 },
ec: {
curves: %w[prime256v1 secp384r1 secp521r1],
recommended_curve: 'prime256v1',
max_age_days: 365
}
}.freeze
DIGEST_ALGORITHMS = %w[SHA256 SHA384 SHA512].freeze
def self.load_from_env
{
key_algorithm: ENV.fetch('CRYPTO_ALGORITHM', 'rsa').downcase.to_sym,
key_size: ENV.fetch('CRYPTO_KEY_SIZE', '3072').to_i,
ec_curve: ENV.fetch('CRYPTO_EC_CURVE', 'prime256v1'),
digest_algorithm: ENV.fetch('CRYPTO_DIGEST', 'SHA256'),
key_rotation_days: ENV.fetch('KEY_ROTATION_DAYS', '365').to_i,
private_key_path: ENV.fetch('PRIVATE_KEY_PATH'),
public_key_path: ENV.fetch('PUBLIC_KEY_PATH'),
key_password: ENV['KEY_PASSWORD'] # Optional
}
end
def self.validate_config(config)
errors = []
case config[:key_algorithm]
when :rsa
min_size = SUPPORTED_ALGORITHMS[:rsa][:min_size]
errors << "RSA key size #{config[:key_size]} below minimum #{min_size}" if config[:key_size] < min_size
when :ec
unless SUPPORTED_ALGORITHMS[:ec][:curves].include?(config[:ec_curve])
errors << "Unsupported EC curve: #{config[:ec_curve]}"
end
else
errors << "Unsupported algorithm: #{config[:key_algorithm]}"
end
unless DIGEST_ALGORITHMS.include?(config[:digest_algorithm])
errors << "Unsupported digest: #{config[:digest_algorithm]}"
end
unless File.exist?(config[:private_key_path])
errors << "Private key file not found: #{config[:private_key_path]}"
end
errors.empty? ? { valid: true } : { valid: false, errors: errors }
end
end
# Production key manager
class ProductionKeyManager
def initialize(config = nil)
@config = config || CryptoConfig.load_from_env
validation = CryptoConfig.validate_config(@config)
unless validation[:valid]
raise ArgumentError, "Invalid configuration: #{validation[:errors].join(', ')}"
end
@private_key = load_private_key
@public_key = @private_key.public_key
end
def sign_data(data, digest_algorithm = nil)
digest_class = get_digest_class(digest_algorithm || @config[:digest_algorithm])
digest = digest_class.new
case @private_key
when OpenSSL::PKey::RSA, OpenSSL::PKey::DSA
@private_key.sign(digest, data)
when OpenSSL::PKey::EC
digest_data = digest_class.digest(data)
@private_key.dsa_sign_asn1(digest_data)
end
end
def verify_signature(data, signature, digest_algorithm = nil)
digest_class = get_digest_class(digest_algorithm || @config[:digest_algorithm])
case @public_key
when OpenSSL::PKey::RSA, OpenSSL::PKey::DSA
digest = digest_class.new
@public_key.verify(digest, signature, data)
when OpenSSL::PKey::EC
digest_data = digest_class.digest(data)
@public_key.dsa_verify_asn1(digest_data, signature)
end
end
def key_age_days
key_file_mtime = File.mtime(@config[:private_key_path])
((Time.now - key_file_mtime) / (24 * 60 * 60)).to_i
end
def rotation_required?
key_age_days > @config[:key_rotation_days]
end
private
def load_private_key
key_data = File.read(@config[:private_key_path])
password = @config[:key_password]
case @config[:key_algorithm]
when :rsa
OpenSSL::PKey::RSA.new(key_data, password)
when :ec
OpenSSL::PKey::EC.new(key_data, password)
when :dsa
OpenSSL::PKey::DSA.new(key_data, password)
end
rescue OpenSSL::PKey::PKeyError => e
raise "Failed to load private key: #{e.message}"
end
def get_digest_class(algorithm)
case algorithm
when 'SHA256' then OpenSSL::Digest::SHA256
when 'SHA384' then OpenSSL::Digest::SHA384
when 'SHA512' then OpenSSL::Digest::SHA512
else raise ArgumentError, "Unsupported digest algorithm: #{algorithm}"
end
end
end
Secure Communication Protocols
Production systems implement secure communication patterns using public key cryptography for authentication and key exchange.
class SecureChannel
def initialize(local_private_key, remote_public_key)
@local_private_key = local_private_key
@remote_public_key = remote_public_key
@session_keys = {}
end
def establish_session
# Generate ephemeral key pair for perfect forward secrecy
ephemeral_key = case @local_private_key
when OpenSSL::PKey::RSA
OpenSSL::PKey::RSA.generate(2048)
when OpenSSL::PKey::EC
OpenSSL::PKey::EC.generate(@local_private_key.group.curve_name)
end
# Create key exchange message
exchange_message = {
ephemeral_public_key: ephemeral_key.public_key.to_pem,
timestamp: Time.now.to_i,
nonce: OpenSSL::Random.random_bytes(16)
}
# Sign the exchange message
message_data = exchange_message.to_json
signature = sign_message(message_data)
# Derive session key using ECDH (if both keys are EC)
if ephemeral_key.is_a?(OpenSSL::PKey::EC) && @remote_public_key.is_a?(OpenSSL::PKey::EC)
shared_secret = ephemeral_key.dh_compute_key(@remote_public_key)
session_key = derive_session_key(shared_secret, exchange_message[:nonce])
@session_keys[:encryption] = session_key[0..31] # First 32 bytes for encryption
@session_keys[:mac] = session_key[32..63] # Next 32 bytes for MAC
end
{
exchange_message: exchange_message,
signature: signature,
session_established: !@session_keys.empty?
}
end
def encrypt_message(plaintext)
raise "Session not established" unless @session_keys[:encryption]
iv = OpenSSL::Random.random_bytes(16)
cipher = OpenSSL::Cipher::AES.new(256, :CBC)
cipher.encrypt
cipher.key = @session_keys[:encryption]
cipher.iv = iv
ciphertext = cipher.update(plaintext) + cipher.final
# Calculate HMAC for authentication
hmac = OpenSSL::HMAC.digest('SHA256', @session_keys[:mac], iv + ciphertext)
{
iv: iv,
ciphertext: ciphertext,
hmac: hmac
}
end
def decrypt_message(encrypted_data)
raise "Session not established" unless @session_keys[:encryption]
# Verify HMAC
expected_hmac = OpenSSL::HMAC.digest('SHA256', @session_keys[:mac],
encrypted_data[:iv] + encrypted_data[:ciphertext])
unless OpenSSL::HMAC.secure_compare(encrypted_data[:hmac], expected_hmac)
raise "Message authentication failed"
end
# Decrypt message
decipher = OpenSSL::Cipher::AES.new(256, :CBC)
decipher.decrypt
decipher.key = @session_keys[:encryption]
decipher.iv = encrypted_data[:iv]
decipher.update(encrypted_data[:ciphertext]) + decipher.final
end
private
def sign_message(message)
digest = OpenSSL::Digest::SHA256.new
case @local_private_key
when OpenSSL::PKey::RSA, OpenSSL::PKey::DSA
@local_private_key.sign(digest, message)
when OpenSSL::PKey::EC
digest_data = OpenSSL::Digest::SHA256.digest(message)
@local_private_key.dsa_sign_asn1(digest_data)
end
end
def derive_session_key(shared_secret, nonce)
# HKDF-like key derivation
salt = nonce
info = "session_key_derivation"
OpenSSL::HMAC.digest('SHA256', salt, shared_secret + info)
end
end
# Usage in production service
def secure_communication_example
# Load production keys
server_key = OpenSSL::PKey::EC.generate('prime256v1')
client_key = OpenSSL::PKey::EC.generate('prime256v1')
# Establish secure channel
server_channel = SecureChannel.new(server_key, client_key.public_key)
session_info = server_channel.establish_session
if session_info[:session_established]
# Send encrypted message
message = "Confidential business data requiring encryption"
encrypted = server_channel.encrypt_message(message)
puts "Message encrypted successfully"
puts "Ciphertext size: #{encrypted[:ciphertext].length} bytes"
# Decrypt message (in production, this would be on the client side)
client_channel = SecureChannel.new(client_key, server_key.public_key)
client_channel.instance_variable_set(:@session_keys, server_channel.instance_variable_get(:@session_keys))
decrypted = client_channel.decrypt_message(encrypted)
puts "Decryption successful: #{decrypted == message}"
end
end
secure_communication_example
Audit Logging and Compliance
Production cryptographic systems require comprehensive audit trails for security compliance and incident investigation.
class CryptoAuditLogger
def initialize(log_file_path = '/var/log/crypto_audit.log')
@log_file = log_file_path
@logger = Logger.new(@log_file, 'daily')
@logger.level = Logger::INFO
@logger.formatter = proc do |severity, datetime, progname, msg|
"[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] #{severity}: #{msg}\n"
end
end
def log_key_generation(algorithm, key_size, purpose = nil)
event = {
action: 'key_generation',
algorithm: algorithm,
key_size: key_size,
purpose: purpose,
timestamp: Time.now.iso8601,
process_id: Process.pid,
user_id: Process.uid
}
@logger.info("KEY_GEN: #{event.to_json}")
end
def log_signature_operation(operation, key_id, data_hash, success)
event = {
action: "signature_#{operation}",
key_identifier: key_id,
data_sha256: data_hash,
success: success,
timestamp: Time.now.iso8601,
client_ip: get_client_ip,
session_id: get_session_id
}
level = success ? :info : :warn
@logger.send(level, "SIG_#{operation.upcase}: #{event.to_json}")
end
def log_encryption_operation(operation, algorithm, data_size, success)
event = {
action: "encryption_#{operation}",
cipher_algorithm: algorithm,
data_size_bytes: data_size,
success: success,
timestamp: Time.now.iso8601,
performance_ms: Thread.current[:crypto_operation_time]
}
@logger.info("ENCRYPT_#{operation.upcase}: #{event.to_json}")
end
def log_key_access(key_id, operation, authorized)
event = {
action: 'key_access',
key_identifier: key_id,
operation: operation,
authorized: authorized,
timestamp: Time.now.iso8601,
access_source: caller_locations(1, 1)[0].path
}
level = authorized ? :info : :error
@logger.send(level, "KEY_ACCESS: #{event.to_json}")
end
private
def get_client_ip
# In web context, extract from request
Thread.current[:client_ip] || 'localhost'
end
def get_session_id
Thread.current[:session_id] || 'no_session'
end
end
# Production crypto service with audit logging
class AuditedCryptoService
def initialize(key_manager, audit_logger = nil)
@key_manager = key_manager
@audit_logger = audit_logger || CryptoAuditLogger.new
@key_id = generate_key_identifier
end
def sign_document(document_content)
start_time = Time.now
data_hash = OpenSSL::Digest::SHA256.hexdigest(document_content)
begin
@audit_logger.log_key_access(@key_id, 'sign', true)
Thread.current[:crypto_operation_time] = ((Time.now - start_time) * 1000).round(2)
signature = @key_manager.sign_data(document_content)
@audit_logger.log_signature_operation('sign', @key_id, data_hash, true)
{
signature: signature,
algorithm: @key_manager.instance_variable_get(:@config)[:key_algorithm],
key_id: @key_id,
timestamp: Time.now.iso8601
}
rescue => e
@audit_logger.log_signature_operation('sign', @key_id, data_hash, false)
@logger.error("Signature operation failed: #{e.message}")
raise
end
end
def verify_document(document_content, signature_data)
data_hash = OpenSSL::Digest::SHA256.hexdigest(document_content)
begin
result = @key_manager.verify_signature(document_content, signature_data[:signature])
@audit_logger.log_signature_operation('verify', signature_data[:key_id], data_hash, result)
result
rescue => e
@audit_logger.log_signature_operation('verify', signature_data[:key_id], data_hash, false)
raise
end
end
private
def generate_key_identifier
key_data = @key_manager.instance_variable_get(:@public_key).to_der
OpenSSL::Digest::SHA256.hexdigest(key_data)[0..15] # First 16 chars of hash
end
end
Reference
Core Classes
Class | Purpose | Key Methods |
---|---|---|
OpenSSL::PKey::RSA |
RSA public key cryptography | generate , public_encrypt , private_decrypt , sign , verify |
OpenSSL::PKey::DSA |
DSA digital signatures | generate , sign , verify |
OpenSSL::PKey::EC |
Elliptic curve cryptography | generate , dsa_sign_asn1 , dsa_verify_asn1 , dh_compute_key |
OpenSSL::X509::Certificate |
X.509 certificate handling | new , sign , verify , to_pem , to_der |
OpenSSL::Digest |
Cryptographic hash functions | SHA1.new , SHA256.new , SHA384.new , SHA512.new |
RSA Key Methods
Method | Parameters | Returns | Description |
---|---|---|---|
RSA.generate(size, exponent=65537) |
size (Integer), exponent (Integer) |
OpenSSL::PKey::RSA |
Generate new RSA key pair |
#public_encrypt(data, padding=PKCS1_PADDING) |
data (String), padding (Constant) |
String |
Encrypt data with public key |
#private_decrypt(data, padding=PKCS1_PADDING) |
data (String), padding (Constant) |
String |
Decrypt data with private key |
#sign(digest, data) |
digest (OpenSSL::Digest), data (String) |
String |
Create digital signature |
#verify(digest, signature, data) |
digest (OpenSSL::Digest), signature (String), data (String) |
Boolean |
Verify digital signature |
#to_pem(cipher=nil, password=nil) |
cipher (OpenSSL::Cipher), password (String) |
String |
Export key in PEM format |
#to_der |
None | String |
Export key in DER format |
#public_key |
None | OpenSSL::PKey::RSA |
Extract public key component |
#private? |
None | Boolean |
Check if private key present |
RSA Padding Constants
Constant | Value | Description |
---|---|---|
PKCS1_PADDING |
1 | PKCS#1 v1.5 padding (default) |
PKCS1_OAEP_PADDING |
4 | PKCS#1 OAEP padding (recommended) |
NO_PADDING |
3 | No padding (use with caution) |
Elliptic Curve Methods
Method | Parameters | Returns | Description |
---|---|---|---|
EC.generate(curve_name) |
curve_name (String) |
OpenSSL::PKey::EC |
Generate EC key pair |
#dsa_sign_asn1(digest) |
digest (String) |
String |
Create ECDSA signature |
#dsa_verify_asn1(digest, signature) |
digest (String), signature (String) |
Boolean |
Verify ECDSA signature |
#dh_compute_key(public_key) |
public_key (OpenSSL::PKey::EC) |
String |
Compute ECDH shared secret |
#group |
None | OpenSSL::PKey::EC::Group |
Get curve parameters |
Supported Elliptic Curves
Curve Name | Key Size | Security Level | Description |
---|---|---|---|
prime256v1 |
256-bit | ~128-bit | NIST P-256, most common |
secp384r1 |
384-bit | ~192-bit | NIST P-384 |
secp521r1 |
521-bit | ~256-bit | NIST P-521 |
secp256k1 |
256-bit | ~128-bit | Bitcoin curve |
Certificate Generation
Method | Parameters | Returns | Description |
---|---|---|---|
X509::Certificate.new |
None | OpenSSL::X509::Certificate |
Create empty certificate |
#subject=(name) |
name (OpenSSL::X509::Name) |
OpenSSL::X509::Name |
Set certificate subject |
#issuer=(name) |
name (OpenSSL::X509::Name) |
OpenSSL::X509::Name |
Set certificate issuer |
#public_key=(key) |
key (OpenSSL::PKey) |
OpenSSL::PKey |
Set certificate public key |
#sign(key, digest) |
key (OpenSSL::PKey), digest (OpenSSL::Digest) |
OpenSSL::X509::Certificate |
Sign certificate |
#verify(key) |
key (OpenSSL::PKey) |
Boolean |
Verify certificate signature |
Error Hierarchy
StandardError
└── OpenSSL::OpenSSLError
├── OpenSSL::PKey::PKeyError
│ ├── OpenSSL::PKey::RSAError
│ ├── OpenSSL::PKey::DSAError
│ └── OpenSSL::PKey::ECError
├── OpenSSL::Cipher::CipherError
├── OpenSSL::Digest::DigestError
└── OpenSSL::X509::X509Error
├── OpenSSL::X509::CertificateError
└── OpenSSL::X509::ExtensionError
Common Error Messages
Error Pattern | Cause | Solution |
---|---|---|
data too large for key size |
Message exceeds RSA key capacity | Use hybrid encryption or larger key |
padding check failed |
Wrong padding scheme or corrupted data | Verify padding parameter matches |
bad decrypt |
Wrong private key or corrupted ciphertext | Check key correspondence |
unknown curve name |
Invalid elliptic curve specification | Use supported curve names |
invalid key format |
Malformed key data | Validate PEM/DER structure |
Performance Characteristics
Operation | RSA-2048 | RSA-4096 | ECDSA-P256 | Notes |
---|---|---|---|---|
Key Generation | ~100ms | ~2000ms | ~5ms | EC significantly faster |
Signature Creation | ~5ms | ~30ms | ~1ms | Private key operations |
Signature Verification | ~0.3ms | ~0.8ms | ~2ms | Public key operations |
Encryption (117 bytes) | ~0.5ms | ~1.5ms | N/A | RSA only for small data |
Security Recommendations
Parameter | Minimum | Recommended | Notes |
---|---|---|---|
RSA Key Size | 2048 bits | 3072 bits | 4096 bits for long-term |
EC Curve | P-256 | P-256 | P-384 for government use |
Hash Algorithm | SHA-256 | SHA-256 | SHA-512 for high security |
Key Rotation | 2 years | 1 year | More frequent for high-risk |