CrackedRuby logo

CrackedRuby

OpenSSL

Overview

The OpenSSL library in Ruby exposes cryptographic operations through several key classes. The OpenSSL::Cipher class handles symmetric encryption and decryption, while OpenSSL::PKey manages asymmetric cryptography including RSA, DSA, and EC keys. Certificate operations use OpenSSL::X509::Certificate and related classes, with OpenSSL::SSL providing secure socket functionality.

Ruby's OpenSSL implementation maintains the same security model as the underlying C library. All operations require explicit algorithm selection, key management, and parameter configuration. The library does not provide default security settings, requiring developers to make informed choices about cryptographic parameters.

require 'openssl'

# Generate RSA key pair
key = OpenSSL::PKey::RSA.generate(2048)
# => #<OpenSSL::PKey::RSA:0x...>

# Create AES cipher
cipher = OpenSSL::Cipher.new('AES-256-GCM')
# => #<OpenSSL::Cipher:0x...>

# Generate random bytes
random_data = OpenSSL::Random.random_bytes(32)
# => "\x9A\x7B\x2C..." (32 bytes of random data)

The module organizes functionality into logical namespaces. Digest operations use OpenSSL::Digest classes, HMAC operations use OpenSSL::HMAC, and key derivation functions reside in OpenSSL::KDF. Each namespace provides both class-level convenience methods and instance-based workflows.

Certificate management involves multiple interconnected classes. OpenSSL::X509::Certificate represents X.509 certificates, OpenSSL::X509::Request handles certificate signing requests, and OpenSSL::X509::Store manages certificate validation chains. These classes integrate with OpenSSL::PKey for key operations and OpenSSL::ASN1 for low-level structure manipulation.

Basic Usage

Symmetric encryption with OpenSSL::Cipher requires algorithm selection, key derivation, and initialization vector management. The cipher object maintains state through the encryption process, requiring proper initialization before use.

require 'openssl'

# Encrypt data with AES-256-GCM
cipher = OpenSSL::Cipher.new('AES-256-GCM')
cipher.encrypt

# Generate key and IV
key = cipher.random_key
iv = cipher.random_iv

# Set key and IV
cipher.key = key
cipher.iv = iv

plaintext = "Sensitive data to encrypt"
ciphertext = cipher.update(plaintext) + cipher.final
auth_tag = cipher.auth_tag

# Decrypt the data
decipher = OpenSSL::Cipher.new('AES-256-GCM')
decipher.decrypt
decipher.key = key
decipher.iv = iv
decipher.auth_tag = auth_tag

decrypted = decipher.update(ciphertext) + decipher.final
# => "Sensitive data to encrypt"

Asymmetric cryptography centers on key generation and management. RSA keys support both encryption and signing operations, while EC keys provide efficient alternatives for signing and key exchange.

# Generate RSA key pair
rsa_key = OpenSSL::PKey::RSA.generate(2048)
public_key = rsa_key.public_key

# Encrypt with public key
encrypted = public_key.public_encrypt("secret message")

# Decrypt with private key  
decrypted = rsa_key.private_decrypt(encrypted)
# => "secret message"

# Sign data with private key
signature = rsa_key.sign('SHA256', "data to sign")

# Verify signature with public key
verified = public_key.verify('SHA256', signature, "data to sign")
# => true

Message digests and HMAC operations provide data integrity verification. The OpenSSL::Digest class supports various hashing algorithms, while OpenSSL::HMAC adds keyed authentication.

# Create message digest
digest = OpenSSL::Digest.new('SHA256')
hash = digest.digest("data to hash")
# => 32-byte binary hash

# Hexadecimal representation
hex_hash = digest.hexdigest("data to hash")
# => "a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3"

# HMAC with secret key
hmac = OpenSSL::HMAC.digest('SHA256', 'secret_key', 'message')
# => 32-byte authentication code

# Hexadecimal HMAC
hex_hmac = OpenSSL::HMAC.hexdigest('SHA256', 'secret_key', 'message')
# => "hexadecimal representation"

Certificate creation involves multiple steps including key generation, certificate signing request creation, and certificate issuance. Each step requires specific parameter configuration.

# Create certificate signing request
key = OpenSSL::PKey::RSA.generate(2048)
csr = OpenSSL::X509::Request.new

# Set subject distinguished name
csr.subject = OpenSSL::X509::Name.parse("/CN=example.com/O=Example Corp/C=US")
csr.public_key = key.public_key

# Sign the CSR
csr.sign(key, OpenSSL::Digest.new('SHA256'))

# Create self-signed certificate
cert = OpenSSL::X509::Certificate.new
cert.version = 2
cert.serial = 1
cert.not_before = Time.now
cert.not_after = Time.now + (365 * 24 * 60 * 60) # 1 year
cert.public_key = key.public_key
cert.subject = csr.subject
cert.issuer = cert.subject # self-signed

# Sign certificate
cert.sign(key, OpenSSL::Digest.new('SHA256'))

Error Handling & Debugging

OpenSSL operations generate specific exception types that indicate different failure modes. The OpenSSL::OpenSSLError serves as the base exception class, with subclasses providing specific error context.

Cipher operations fail when keys, initialization vectors, or authentication tags are incorrect. Padding errors indicate data corruption or wrong keys, while authentication failures in AEAD modes suggest tampering or incorrect parameters.

def safe_decrypt(ciphertext, key, iv, auth_tag)
  cipher = OpenSSL::Cipher.new('AES-256-GCM')
  cipher.decrypt
  cipher.key = key
  cipher.iv = iv
  cipher.auth_tag = auth_tag
  
  cipher.update(ciphertext) + cipher.final
rescue OpenSSL::Cipher::CipherError => e
  case e.message
  when /bad decrypt/
    raise DecryptionError, "Invalid key or corrupted data"
  when /wrong tag/
    raise AuthenticationError, "Authentication tag verification failed"
  else
    raise CryptographicError, "Cipher operation failed: #{e.message}"
  end
end

Key operations produce OpenSSL::PKey::PKeyError exceptions when parameters are invalid or operations are unsupported. RSA operations fail with specific errors for padding issues, key size mismatches, or unsupported operations.

def safe_rsa_encrypt(data, public_key, max_retries: 3)
  retries = 0
  begin
    public_key.public_encrypt(data, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
  rescue OpenSSL::PKey::RSAError => e
    case e.message
    when /data too large for key size/
      raise DataTooLargeError, "Data exceeds RSA key capacity"
    when /unknown padding type/
      raise PaddingError, "Invalid padding mode specified"
    else
      retries += 1
      if retries <= max_retries
        sleep(0.1 * retries)
        retry
      else
        raise EncryptionError, "RSA encryption failed after #{max_retries} retries: #{e.message}"
      end
    end
  end
end

Certificate validation produces OpenSSL::X509::CertificateError and OpenSSL::X509::StoreError exceptions. Chain validation failures, expired certificates, and revocation status errors require different handling approaches.

def validate_certificate_chain(cert, intermediates, root_store)
  store = OpenSSL::X509::Store.new
  store.add_cert(root_store)
  
  intermediates.each { |intermediate| store.add_cert(intermediate) }
  
  store_context = OpenSSL::X509::StoreContext.new(store, cert)
  
  unless store_context.verify
    error_code = store_context.error
    case error_code
    when OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED
      raise CertificateExpiredError, "Certificate expired: #{cert.subject}"
    when OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT
      raise MissingIssuerError, "Cannot find issuer certificate"
    when OpenSSL::X509::V_ERR_CERT_CHAIN_TOO_LONG
      raise ChainTooLongError, "Certificate chain exceeds maximum length"
    else
      raise CertificateValidationError, "Validation failed: #{store_context.error_string}"
    end
  end
  
  true
rescue OpenSSL::X509::StoreError => e
  raise CertificateStoreError, "Store operation failed: #{e.message}"
end

Debugging cryptographic operations requires careful logging without exposing sensitive data. Key fingerprints, certificate serial numbers, and algorithm parameters provide debugging information without compromising security.

class CryptoDebugger
  def self.debug_cipher_operation(cipher, operation)
    {
      algorithm: cipher.name,
      operation: operation,
      key_length: cipher.key_len,
      iv_length: cipher.iv_len,
      auth_data_length: cipher.auth_data_length,
      timestamp: Time.now.iso8601
    }
  end
  
  def self.debug_certificate(cert)
    {
      subject: cert.subject.to_s,
      issuer: cert.issuer.to_s,
      serial: cert.serial.to_s,
      not_before: cert.not_before.iso8601,
      not_after: cert.not_after.iso8601,
      public_key_algorithm: cert.public_key.class.name,
      fingerprint: Digest::SHA256.hexdigest(cert.to_der)
    }
  end
end

Production Patterns

SSL/TLS client configuration requires careful parameter selection for security and compatibility. The OpenSSL::SSL::SSLContext class manages cipher suites, protocol versions, and certificate verification settings.

class SecureHttpClient
  def self.create_ssl_context
    context = OpenSSL::SSL::SSLContext.new
    
    # Set minimum TLS version
    context.min_version = OpenSSL::SSL::TLS1_2_VERSION
    context.max_version = OpenSSL::SSL::TLS1_3_VERSION
    
    # Configure cipher suites
    context.ciphers = [
      'TLS_AES_256_GCM_SHA384',
      'TLS_CHACHA20_POLY1305_SHA256',
      'TLS_AES_128_GCM_SHA256',
      'ECDHE-RSA-AES256-GCM-SHA384',
      'ECDHE-RSA-CHACHA20-POLY1305'
    ]
    
    # Enable certificate verification
    context.verify_mode = OpenSSL::SSL::VERIFY_PEER
    context.verify_hostname = true
    
    # Load system certificate store
    context.cert_store = OpenSSL::X509::Store.new
    context.cert_store.set_default_paths
    
    context
  end
  
  def self.make_request(uri, ca_file: nil)
    socket = TCPSocket.new(uri.host, uri.port || 443)
    ssl_context = create_ssl_context
    ssl_context.ca_file = ca_file if ca_file
    
    ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
    ssl_socket.hostname = uri.host
    ssl_socket.connect
    
    # Verify connection security
    verify_connection_security(ssl_socket)
    
    ssl_socket
  ensure
    ssl_socket&.close
    socket&.close
  end
  
  private
  
  def self.verify_connection_security(ssl_socket)
    cipher = ssl_socket.cipher
    raise InsecureConnectionError, "Weak cipher: #{cipher[0]}" if weak_cipher?(cipher[0])
    
    cert = ssl_socket.peer_cert
    raise CertificateError, "Certificate expires soon" if cert.not_after < Time.now + (7 * 24 * 60 * 60)
  end
  
  def self.weak_cipher?(cipher_name)
    cipher_name.match?(/RC4|DES|MD5|NULL/)
  end
end

Data encryption for storage requires key derivation, secure random generation, and authenticated encryption. Production systems need key rotation capabilities and secure key storage integration.

class DataEncryption
  ALGORITHM = 'AES-256-GCM'.freeze
  KEY_DERIVATION_ITERATIONS = 100_000
  
  def initialize(master_key)
    @master_key = master_key
  end
  
  def encrypt(plaintext, additional_data: nil)
    salt = OpenSSL::Random.random_bytes(32)
    key = derive_key(@master_key, salt)
    
    cipher = OpenSSL::Cipher.new(ALGORITHM)
    cipher.encrypt
    cipher.key = key
    iv = cipher.random_iv
    cipher.auth_data = additional_data if additional_data
    
    ciphertext = cipher.update(plaintext) + cipher.final
    auth_tag = cipher.auth_tag
    
    # Combine all components
    [
      salt,
      iv,
      auth_tag,
      ciphertext
    ].map { |component| Base64.strict_encode64(component) }.join('.')
  end
  
  def decrypt(encrypted_data, additional_data: nil)
    components = encrypted_data.split('.')
    raise ArgumentError, "Invalid encrypted data format" unless components.length == 4
    
    salt, iv, auth_tag, ciphertext = components.map { |c| Base64.strict_decode64(c) }
    key = derive_key(@master_key, salt)
    
    cipher = OpenSSL::Cipher.new(ALGORITHM)
    cipher.decrypt
    cipher.key = key
    cipher.iv = iv
    cipher.auth_tag = auth_tag
    cipher.auth_data = additional_data if additional_data
    
    cipher.update(ciphertext) + cipher.final
  end
  
  private
  
  def derive_key(master_key, salt)
    OpenSSL::KDF.pbkdf2_hmac(
      master_key,
      salt: salt,
      iterations: KEY_DERIVATION_ITERATIONS,
      length: 32,
      hash: OpenSSL::Digest.new('SHA256')
    )
  end
end

Certificate management in production requires automated renewal, validation monitoring, and secure storage. Integration with certificate authorities and ACME protocol implementations handles certificate lifecycle management.

class CertificateManager
  def initialize(key_store:, cert_store:, ca_config:)
    @key_store = key_store
    @cert_store = cert_store
    @ca_config = ca_config
  end
  
  def check_certificate_expiry(domain)
    cert = @cert_store.get_certificate(domain)
    return { status: :missing, domain: domain } unless cert
    
    days_until_expiry = (cert.not_after - Time.now) / (24 * 60 * 60)
    
    if days_until_expiry <= 7
      { status: :expired_soon, domain: domain, days: days_until_expiry.to_i }
    elsif days_until_expiry <= 30
      { status: :expiring, domain: domain, days: days_until_expiry.to_i }
    else
      { status: :valid, domain: domain, days: days_until_expiry.to_i }
    end
  end
  
  def renew_certificate(domain)
    # Generate new key pair
    new_key = OpenSSL::PKey::RSA.generate(2048)
    
    # Create certificate signing request
    csr = create_csr(domain, new_key)
    
    # Submit to certificate authority
    new_cert = submit_csr_to_ca(csr)
    
    # Validate new certificate
    validate_new_certificate(new_cert, new_key, domain)
    
    # Store new key and certificate
    @key_store.store_key(domain, new_key)
    @cert_store.store_certificate(domain, new_cert)
    
    { status: :renewed, domain: domain, expires_at: new_cert.not_after }
  rescue StandardError => e
    { status: :failed, domain: domain, error: e.message }
  end
  
  private
  
  def create_csr(domain, key)
    csr = OpenSSL::X509::Request.new
    csr.subject = OpenSSL::X509::Name.parse("/CN=#{domain}")
    csr.public_key = key.public_key
    
    # Add Subject Alternative Name extension
    extension_factory = OpenSSL::X509::ExtensionFactory.new
    san_extension = extension_factory.create_extension(
      'subjectAltName',
      "DNS:#{domain},DNS:*.#{domain}"
    )
    csr.add_attribute(
      OpenSSL::X509::Attribute.new(
        'extReq',
        OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence([san_extension])])
      )
    )
    
    csr.sign(key, OpenSSL::Digest.new('SHA256'))
    csr
  end
  
  def validate_new_certificate(cert, key, expected_domain)
    # Verify certificate matches private key
    test_data = 'certificate validation test'
    signature = key.sign('SHA256', test_data)
    
    unless cert.public_key.verify('SHA256', signature, test_data)
      raise CertificateValidationError, "Certificate does not match private key"
    end
    
    # Verify domain in certificate
    cert_domains = extract_domains_from_certificate(cert)
    unless cert_domains.include?(expected_domain)
      raise CertificateValidationError, "Certificate does not include expected domain: #{expected_domain}"
    end
  end
end

Common Pitfalls

Initialization vector reuse in symmetric encryption compromises security completely. Each encryption operation requires a unique IV, even when using the same key. Counter modes and GCM modes are particularly vulnerable to IV reuse attacks.

# WRONG: Reusing IV
cipher = OpenSSL::Cipher.new('AES-256-GCM')
cipher.encrypt
cipher.key = shared_key
cipher.iv = fixed_iv  # Never do this!

# CORRECT: Generate unique IV for each operation
def encrypt_with_unique_iv(plaintext, key)
  cipher = OpenSSL::Cipher.new('AES-256-GCM')
  cipher.encrypt
  cipher.key = key
  iv = cipher.random_iv  # Always generate new IV
  
  ciphertext = cipher.update(plaintext) + cipher.final
  auth_tag = cipher.auth_tag
  
  # Return IV with ciphertext for decryption
  { iv: iv, ciphertext: ciphertext, auth_tag: auth_tag }
end

Key derivation without salt allows rainbow table attacks against password-derived keys. The salt must be cryptographically random and stored with the derived key material.

# WRONG: No salt in key derivation
derived_key = OpenSSL::KDF.pbkdf2_hmac(password, iterations: 100_000, length: 32)

# CORRECT: Always use random salt
def derive_key_safely(password, stored_salt = nil)
  salt = stored_salt || OpenSSL::Random.random_bytes(32)
  
  derived_key = OpenSSL::KDF.pbkdf2_hmac(
    password,
    salt: salt,
    iterations: 100_000,
    length: 32,
    hash: OpenSSL::Digest.new('SHA256')
  )
  
  { key: derived_key, salt: salt }
end

Certificate hostname verification bypasses create security vulnerabilities. Disabling hostname verification allows man-in-the-middle attacks even with valid certificates.

# WRONG: Disabling certificate verification
context = OpenSSL::SSL::SSLContext.new
context.verify_mode = OpenSSL::SSL::VERIFY_NONE  # Never do this in production!

# CORRECT: Proper certificate verification
def create_secure_context(trusted_ca_file = nil)
  context = OpenSSL::SSL::SSLContext.new
  context.verify_mode = OpenSSL::SSL::VERIFY_PEER
  context.verify_hostname = true
  
  # Use system certificate store or custom CA file
  context.cert_store = OpenSSL::X509::Store.new
  if trusted_ca_file
    context.ca_file = trusted_ca_file
  else
    context.cert_store.set_default_paths
  end
  
  context
end

Signature verification timing attacks occur when verification functions return early on mismatched signatures. Constant-time comparison prevents information leakage about the expected signature.

# WRONG: Early return allows timing attacks
def verify_signature_insecure(public_key, signature, data)
  expected_signature = public_key.sign('SHA256', data)
  signature == expected_signature  # Timing attack vulnerability
end

# CORRECT: Constant-time comparison
def verify_signature_secure(public_key, signature, data)
  begin
    public_key.verify('SHA256', signature, data)
  rescue OpenSSL::PKey::PKeyError
    false
  end
end

# For HMAC verification, use secure comparison
def verify_hmac_secure(secret, message, provided_hmac)
  expected_hmac = OpenSSL::HMAC.digest('SHA256', secret, message)
  
  # Constant-time comparison prevents timing attacks
  return false unless provided_hmac.bytesize == expected_hmac.bytesize
  
  result = 0
  provided_hmac.bytes.zip(expected_hmac.bytes) do |a, b|
    result |= a ^ b
  end
  
  result.zero?
end

Random number generation using predictable sources compromises cryptographic security. Ruby's rand function is not cryptographically secure and should never be used for keys, IVs, or nonces.

# WRONG: Using predictable random numbers
iv = Array.new(16) { rand(256) }.pack('C*')  # Predictable!
key = (0..31).map { rand(256) }.pack('C*')   # Predictable!

# CORRECT: Use cryptographically secure random number generation
def generate_secure_key(length = 32)
  OpenSSL::Random.random_bytes(length)
end

def generate_secure_iv(cipher)
  cipher.random_iv
end

# Seed additional entropy if needed (usually not required)
OpenSSL::Random.load_random_file('/dev/random')

Exception handling that exposes cryptographic details aids attackers in distinguishing between different failure modes. Generic error messages prevent information leakage about internal cryptographic state.

# WRONG: Exposing cryptographic details in exceptions
def decrypt_user_data(encrypted_data, user_password)
  # ... decryption logic ...
rescue OpenSSL::Cipher::CipherError => e
  raise "Decryption failed: #{e.message}"  # Exposes internal details
end

# CORRECT: Generic error messages
def decrypt_user_data_secure(encrypted_data, user_password)
  # ... decryption logic ...
rescue OpenSSL::Cipher::CipherError, OpenSSL::OpenSSLError
  raise AuthenticationError, "Authentication failed"  # Generic message
end

Reference

Core Classes

Class Purpose Key Methods
OpenSSL::Cipher Symmetric encryption/decryption #encrypt, #decrypt, #update, #final
OpenSSL::PKey::RSA RSA public-key cryptography #public_encrypt, #private_decrypt, #sign, #verify
OpenSSL::PKey::EC Elliptic curve cryptography #dh_compute_key, #sign, #verify
OpenSSL::Digest Message digests #digest, #hexdigest, #update
OpenSSL::HMAC Hash-based message authentication .digest, .hexdigest
OpenSSL::X509::Certificate X.509 certificates #verify, #check_private_key
OpenSSL::SSL::SSLContext SSL/TLS context configuration #ciphers=, #verify_mode=

Cipher Algorithms

Algorithm Key Size (bits) Block Size Mode Security Level
AES-128-GCM 128 128 GCM High
AES-256-GCM 256 128 GCM High
ChaCha20-Poly1305 256 Stream AEAD High
AES-128-CBC 128 128 CBC Medium
AES-256-CBC 256 128 CBC Medium

Key Generation Methods

Method Parameters Returns Usage
OpenSSL::PKey::RSA.generate(size) size (Integer) OpenSSL::PKey::RSA RSA key pair generation
OpenSSL::PKey::EC.generate(curve) curve (String/Group) OpenSSL::PKey::EC EC key pair generation
OpenSSL::Random.random_bytes(length) length (Integer) String Cryptographically secure random data
cipher.random_key None String Random key for cipher
cipher.random_iv None String Random IV for cipher

Digest Algorithms

Algorithm Output Size (bits) Security Level Usage
SHA256 256 High General purpose
SHA384 384 High High security applications
SHA512 512 High High security applications
SHA3-256 256 High Alternative to SHA-2
BLAKE2b Variable High Fast hashing

SSL/TLS Configuration Options

Option Values Description
verify_mode VERIFY_NONE, VERIFY_PEER Certificate verification mode
min_version TLS1_2_VERSION, TLS1_3_VERSION Minimum TLS protocol version
max_version TLS1_2_VERSION, TLS1_3_VERSION Maximum TLS protocol version
ciphers Array of cipher names Allowed cipher suites
verify_hostname true, false Enable hostname verification

Exception Hierarchy

Exception Parent Class Raised When
OpenSSL::OpenSSLError StandardError Base OpenSSL exception
OpenSSL::Cipher::CipherError OpenSSL::OpenSSLError Cipher operation failure
OpenSSL::PKey::PKeyError OpenSSL::OpenSSLError Public key operation failure
OpenSSL::X509::CertificateError OpenSSL::OpenSSLError Certificate operation failure
OpenSSL::SSL::SSLError OpenSSL::OpenSSLError SSL/TLS operation failure

Key Derivation Functions

Function Parameters Output Use Case
OpenSSL::KDF.pbkdf2_hmac password, salt, iterations, length, hash Derived key Password-based key derivation
OpenSSL::KDF.scrypt password, salt, N, r, p, length Derived key Memory-hard key derivation
OpenSSL::KDF.hkdf ikm, salt, info, length, hash Derived key Key expansion

Certificate Extensions

Extension OID Name Purpose
2.5.29.17 Subject Alternative Name Additional hostnames/IPs
2.5.29.15 Key Usage Allowed key operations
2.5.29.37 Extended Key Usage Specific key usage purposes
2.5.29.19 Basic Constraints CA certificate constraints
2.5.29.14 Subject Key Identifier Key identifier