CrackedRuby logo

CrackedRuby

Public Key Cryptography

Comprehensive guide to implementing public key cryptography operations in Ruby using OpenSSL bindings for encryption, digital signatures, and certificate management.

Standard Library Security and Cryptography
4.5.5

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