CrackedRuby CrackedRuby

Overview

Encryption converts plaintext data into ciphertext using mathematical algorithms and secret keys. The process makes information unreadable to unauthorized parties while allowing authorized recipients to decrypt and recover the original data. Modern software systems require encryption for protecting user credentials, securing network communications, safeguarding stored data, and meeting regulatory compliance requirements.

The encryption process involves three primary components: plaintext (original data), an encryption algorithm (cipher), and a key (secret value). The algorithm applies mathematical transformations to the plaintext using the key, producing ciphertext. Decryption reverses this process using either the same key (symmetric encryption) or a related key (asymmetric encryption).

require 'openssl'

# Basic encryption demonstration
cipher = OpenSSL::Cipher.new('AES-256-CBC')
cipher.encrypt
key = cipher.random_key
iv = cipher.random_iv

plaintext = "Sensitive user data"
ciphertext = cipher.update(plaintext) + cipher.final
# => "\x8F\x2E\xA3..." (binary ciphertext)

Encryption appears throughout modern applications: HTTPS secures web traffic, database encryption protects stored records, encrypted messaging ensures private communications, and filesystem encryption guards local data. The discipline combines mathematics, computer science, and engineering to address practical security challenges while maintaining system performance and usability.

Key Principles

Encryption systems operate on fundamental principles that determine their security properties and practical applicability. Understanding these principles enables informed decisions about which encryption approaches suit specific requirements.

Confidentiality Through Computational Difficulty

Encryption security depends on computational infeasibility rather than information hiding. A secure cipher remains secure even when attackers know the algorithm, relying solely on key secrecy. This principle, Kerckhoffs's principle, means that algorithm details can be public and peer-reviewed while security depends entirely on keeping keys secret. Breaking encryption without the key requires computational resources beyond practical reach.

# Security depends on key secrecy, not algorithm secrecy
cipher = OpenSSL::Cipher.new('AES-256-GCM')
cipher.encrypt

# Key must remain secret - algorithm is public
key = cipher.random_key  # 32 bytes for AES-256
iv = cipher.random_iv    # Initialization vector

# Public algorithm, secret key model
ciphertext = cipher.update("Secret message") + cipher.final

Symmetric vs Asymmetric Cryptography

Symmetric encryption uses identical keys for encryption and decryption. The sender and receiver must share the secret key through a secure channel before communication begins. AES, ChaCha20, and DES represent symmetric algorithms. Performance characteristics favor symmetric encryption for bulk data encryption, with AES-256 encrypting data at gigabytes per second on modern hardware.

Asymmetric encryption uses mathematically related key pairs: a public key for encryption and a private key for decryption. Anyone can encrypt data using the public key, but only the private key holder can decrypt it. RSA, ECC, and ElGamal exemplify asymmetric algorithms. Computational overhead makes asymmetric encryption significantly slower than symmetric approaches, typically used for key exchange rather than bulk encryption.

# Symmetric encryption - same key for both operations
symmetric_cipher = OpenSSL::Cipher.new('AES-256-CBC')
symmetric_cipher.encrypt
shared_key = symmetric_cipher.random_key

encrypted = symmetric_cipher.update("Data") + symmetric_cipher.final

# Asymmetric encryption - different keys
rsa_key = OpenSSL::PKey::RSA.new(2048)
public_key = rsa_key.public_key
private_key = rsa_key

# Encrypt with public key
encrypted_data = public_key.public_encrypt("Data")

# Decrypt with private key
decrypted_data = private_key.private_decrypt(encrypted_data)

Block Ciphers and Stream Ciphers

Block ciphers process fixed-size blocks of data (typically 128 bits for AES). Input shorter than the block size requires padding, while longer input gets divided into blocks. Block ciphers operate in modes that determine how multiple blocks interact. Stream ciphers encrypt data one bit or byte at a time, generating a keystream that combines with plaintext through XOR operations.

Initialization Vectors and Nonces

Encrypting identical plaintext with the same key produces identical ciphertext, leaking information about repeated data patterns. Initialization vectors (IVs) and nonces (numbers used once) randomize encryption output. Each encryption operation should use a unique, unpredictable IV. The IV need not remain secret but must never repeat with the same key.

cipher = OpenSSL::Cipher.new('AES-256-CBC')
cipher.encrypt
key = cipher.random_key

# Different IVs produce different ciphertext for same plaintext
plaintext = "Repeated message"

cipher.iv = cipher.random_iv
ciphertext1 = cipher.update(plaintext) + cipher.final

cipher.reset
cipher.encrypt
cipher.key = key
cipher.iv = cipher.random_iv  # Different IV
ciphertext2 = cipher.update(plaintext) + cipher.final

# ciphertext1 != ciphertext2 despite identical plaintext and key

Authentication and Encryption

Encryption alone provides confidentiality but not integrity or authenticity. An attacker can modify ciphertext without detection if the system lacks authentication. Authenticated encryption modes combine encryption with message authentication codes (MAC), ensuring both confidentiality and integrity. AES-GCM and ChaCha20-Poly1305 represent authenticated encryption with associated data (AEAD) algorithms.

Key Management Complexity

Key generation, distribution, storage, rotation, and destruction present practical challenges exceeding the mathematical complexity of encryption algorithms. Weak key management undermines strong encryption. Keys require cryptographically secure random generation, secure storage protected from unauthorized access, and regular rotation to limit exposure from potential compromise.

Ruby Implementation

Ruby provides encryption capabilities through the OpenSSL library, exposing symmetric and asymmetric cryptographic operations through a high-level API. The OpenSSL::Cipher class implements symmetric encryption, while OpenSSL::PKey classes handle asymmetric operations.

Symmetric Encryption with AES

AES (Advanced Encryption Standard) represents the most widely deployed symmetric cipher. Ruby's OpenSSL bindings support AES variants including AES-128, AES-192, and AES-256 (numbers indicate key size in bits) across multiple modes of operation.

require 'openssl'
require 'base64'

def encrypt_data(plaintext, key)
  cipher = OpenSSL::Cipher.new('AES-256-CBC')
  cipher.encrypt
  cipher.key = key
  iv = cipher.random_iv
  
  ciphertext = cipher.update(plaintext) + cipher.final
  
  # Return IV and ciphertext - IV needed for decryption
  {
    iv: Base64.strict_encode64(iv),
    ciphertext: Base64.strict_encode64(ciphertext)
  }
end

def decrypt_data(encrypted_data, key)
  cipher = OpenSSL::Cipher.new('AES-256-CBC')
  cipher.decrypt
  cipher.key = key
  cipher.iv = Base64.strict_decode64(encrypted_data[:iv])
  
  ciphertext = Base64.strict_decode64(encrypted_data[:ciphertext])
  plaintext = cipher.update(ciphertext) + cipher.final
  
  plaintext
end

# Generate secure key
key = OpenSSL::Cipher.new('AES-256-CBC').random_key

# Encrypt
encrypted = encrypt_data("Confidential information", key)
# => {:iv=>"...", :ciphertext=>"..."}

# Decrypt
decrypted = decrypt_data(encrypted, key)
# => "Confidential information"

Authenticated Encryption with GCM

GCM (Galois/Counter Mode) provides authenticated encryption, detecting tampering attempts through authentication tags. This mode eliminates the need for separate MAC operations.

def encrypt_authenticated(plaintext, key, associated_data = nil)
  cipher = OpenSSL::Cipher.new('AES-256-GCM')
  cipher.encrypt
  cipher.key = key
  iv = cipher.random_iv
  
  cipher.auth_data = associated_data if associated_data
  
  ciphertext = cipher.update(plaintext) + cipher.final
  auth_tag = cipher.auth_tag
  
  {
    iv: Base64.strict_encode64(iv),
    ciphertext: Base64.strict_encode64(ciphertext),
    auth_tag: Base64.strict_encode64(auth_tag)
  }
end

def decrypt_authenticated(encrypted_data, key, associated_data = nil)
  cipher = OpenSSL::Cipher.new('AES-256-GCM')
  cipher.decrypt
  cipher.key = key
  cipher.iv = Base64.strict_decode64(encrypted_data[:iv])
  cipher.auth_tag = Base64.strict_decode64(encrypted_data[:auth_tag])
  
  cipher.auth_data = associated_data if associated_data
  
  ciphertext = Base64.strict_decode64(encrypted_data[:ciphertext])
  plaintext = cipher.update(ciphertext) + cipher.final
  
  plaintext
rescue OpenSSL::Cipher::CipherError
  raise "Authentication failed - data may be corrupted or tampered"
end

key = OpenSSL::Cipher.new('AES-256-GCM').random_key
associated_data = "user_id:12345"

encrypted = encrypt_authenticated("Secret data", key, associated_data)
decrypted = decrypt_authenticated(encrypted, key, associated_data)

Asymmetric Encryption with RSA

RSA enables encryption without shared secrets. Public keys encrypt data that only corresponding private keys can decrypt. RSA typically encrypts small amounts of data, such as symmetric keys, rather than bulk data.

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

# Encrypt with public key
plaintext = "Symmetric key material"
ciphertext = public_key.public_encrypt(
  plaintext,
  OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
)

# Decrypt with private key
decrypted = private_key.private_decrypt(
  ciphertext,
  OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
)
# => "Symmetric key material"

# Export keys for storage or transmission
public_pem = public_key.to_pem
private_pem = private_key.to_pem

# Load keys from PEM format
loaded_public = OpenSSL::PKey::RSA.new(public_pem)
loaded_private = OpenSSL::PKey::RSA.new(private_pem)

Hybrid Encryption Pattern

Production systems typically combine symmetric and asymmetric encryption: asymmetric encryption protects symmetric keys, while symmetric encryption handles bulk data. This approach balances security and performance.

class HybridEncryption
  def self.encrypt(plaintext, recipient_public_key)
    # Generate random symmetric key for this message
    cipher = OpenSSL::Cipher.new('AES-256-GCM')
    cipher.encrypt
    symmetric_key = cipher.random_key
    
    # Encrypt data with symmetric key
    encrypted_data = encrypt_symmetric(plaintext, symmetric_key)
    
    # Encrypt symmetric key with recipient's public key
    encrypted_key = recipient_public_key.public_encrypt(
      symmetric_key,
      OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
    )
    
    {
      encrypted_key: Base64.strict_encode64(encrypted_key),
      encrypted_data: encrypted_data
    }
  end
  
  def self.decrypt(encrypted_message, recipient_private_key)
    # Decrypt symmetric key
    encrypted_key = Base64.strict_decode64(encrypted_message[:encrypted_key])
    symmetric_key = recipient_private_key.private_decrypt(
      encrypted_key,
      OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
    )
    
    # Decrypt data with symmetric key
    decrypt_symmetric(encrypted_message[:encrypted_data], symmetric_key)
  end
  
  private
  
  def self.encrypt_symmetric(plaintext, key)
    cipher = OpenSSL::Cipher.new('AES-256-GCM')
    cipher.encrypt
    cipher.key = key
    iv = cipher.random_iv
    
    ciphertext = cipher.update(plaintext) + cipher.final
    
    {
      iv: Base64.strict_encode64(iv),
      ciphertext: Base64.strict_encode64(ciphertext),
      auth_tag: Base64.strict_encode64(cipher.auth_tag)
    }
  end
  
  def self.decrypt_symmetric(encrypted_data, key)
    cipher = OpenSSL::Cipher.new('AES-256-GCM')
    cipher.decrypt
    cipher.key = key
    cipher.iv = Base64.strict_decode64(encrypted_data[:iv])
    cipher.auth_tag = Base64.strict_decode64(encrypted_data[:auth_tag])
    
    ciphertext = Base64.strict_decode64(encrypted_data[:ciphertext])
    cipher.update(ciphertext) + cipher.final
  end
end

# Usage
recipient_key = OpenSSL::PKey::RSA.new(2048)
message = "Large confidential document content..."

encrypted = HybridEncryption.encrypt(message, recipient_key.public_key)
decrypted = HybridEncryption.decrypt(encrypted, recipient_key)

Key Derivation Functions

User passwords lack sufficient entropy for direct use as encryption keys. Key derivation functions (KDF) transform passwords into cryptographically suitable keys through computationally intensive operations that resist brute-force attacks.

require 'openssl'

def derive_key_from_password(password, salt = nil)
  # Generate salt if not provided
  salt ||= OpenSSL::Random.random_bytes(16)
  
  # PBKDF2 with SHA256, 100000 iterations
  key = OpenSSL::PKCS5.pbkdf2_hmac(
    password,
    salt,
    100_000,  # Iteration count - higher is more secure but slower
    32,       # Key length in bytes (256 bits)
    OpenSSL::Digest::SHA256.new
  )
  
  {
    key: key,
    salt: salt
  }
end

# Derive key from password
password = "user_password_123"
derived = derive_key_from_password(password)

# Store salt with encrypted data - needed for decryption
# Salt can be public, but must be unique per password
salt = derived[:salt]
key = derived[:key]

# Use derived key for encryption
cipher = OpenSSL::Cipher.new('AES-256-GCM')
cipher.encrypt
cipher.key = key
# ... encryption continues

Security Implications

Encryption implementation requires careful attention to security considerations beyond selecting a cipher algorithm. Improper usage patterns, weak key management, and subtle implementation errors can completely undermine cryptographic protections.

Key Generation Security

Cryptographically secure random number generation forms the foundation of encryption security. Standard random number generators produce predictable outputs unsuitable for cryptographic keys. Ruby's OpenSSL::Random.random_bytes provides cryptographically secure randomness sourced from operating system entropy pools.

# Insecure - predictable pseudo-random values
insecure_key = Array.new(32) { rand(256) }.pack('C*')

# Secure - cryptographically random values
secure_key = OpenSSL::Random.random_bytes(32)

# Secure key generation for ciphers
cipher = OpenSSL::Cipher.new('AES-256-GCM')
cipher.encrypt
secure_key = cipher.random_key  # Uses OpenSSL's secure RNG

Insufficient key length weakens security regardless of algorithm strength. AES-256 requires 256-bit keys, RSA requires minimum 2048-bit keys for adequate security. Shorter keys become vulnerable to brute-force attacks as computational power increases.

IV and Nonce Management

IV reuse with the same key catastrophically compromises encryption security, particularly for counter-based modes like CTR and GCM. Repeated IVs leak information about plaintext relationships and can enable complete plaintext recovery in some modes.

# Dangerous - IV reuse
cipher = OpenSSL::Cipher.new('AES-256-GCM')
cipher.encrypt
key = cipher.random_key

# Same IV used multiple times - INSECURE
static_iv = cipher.random_iv

cipher.iv = static_iv
ciphertext1 = cipher.update("First message") + cipher.final

cipher.reset
cipher.encrypt
cipher.key = key
cipher.iv = static_iv  # Reused IV - security compromised
ciphertext2 = cipher.update("Second message") + cipher.final

# Secure - unique IV per encryption
def secure_encrypt(plaintext, key)
  cipher = OpenSSL::Cipher.new('AES-256-GCM')
  cipher.encrypt
  cipher.key = key
  cipher.iv = cipher.random_iv  # New IV each time
  
  ciphertext = cipher.update(plaintext) + cipher.final
  
  {
    iv: cipher.iv,
    ciphertext: ciphertext,
    auth_tag: cipher.auth_tag
  }
end

Padding Oracle Attacks

CBC mode with PKCS7 padding becomes vulnerable to padding oracle attacks when systems reveal whether decryption succeeded or padding validation failed. Attackers exploit timing differences or error messages to decrypt ciphertext without keys. Authenticated encryption modes like GCM eliminate this vulnerability class by verifying integrity before decryption.

Timing Attacks

Comparison operations that terminate early upon finding mismatches leak information through timing variations. Authentication tag verification must use constant-time comparison to prevent timing-based attacks.

# Vulnerable to timing attacks - early termination
def insecure_compare(a, b)
  return false if a.length != b.length
  a.bytes.zip(b.bytes).all? { |x, y| x == y }
end

# Constant-time comparison
def secure_compare(a, b)
  return false if a.length != b.length
  
  result = 0
  a.bytes.zip(b.bytes).each { |x, y| result |= x ^ y }
  result == 0
end

# Ruby's built-in secure compare
require 'openssl'

def verify_auth_tag(expected_tag, received_tag)
  OpenSSL.secure_compare(expected_tag, received_tag)
end

Key Storage

Keys stored in source code, configuration files, or environment variables risk exposure through repository leaks, log files, or unauthorized access. Production systems require dedicated key management solutions: hardware security modules (HSM), key management services (KMS), or secure key vaults. Keys should exist in memory only during active use and be cleared after operations complete.

# Insecure - hardcoded key
ENCRYPTION_KEY = "secret_key_12345"

# Better - environment variable (still not ideal for production)
encryption_key = ENV['ENCRYPTION_KEY']

# Production approach - key management service
require 'aws-sdk-kms'

class SecureKeyManager
  def initialize
    @kms = Aws::KMS::Client.new(region: 'us-east-1')
  end
  
  def encrypt_data(plaintext)
    # Generate data key from KMS
    response = @kms.generate_data_key(
      key_id: 'alias/application-key',
      key_spec: 'AES_256'
    )
    
    plaintext_key = response.plaintext
    encrypted_key = response.ciphertext_blob
    
    # Encrypt data with data key
    cipher = OpenSSL::Cipher.new('AES-256-GCM')
    cipher.encrypt
    cipher.key = plaintext_key
    iv = cipher.random_iv
    
    ciphertext = cipher.update(plaintext) + cipher.final
    
    # Clear key from memory
    plaintext_key.replace("\0" * plaintext_key.length)
    
    {
      encrypted_key: Base64.strict_encode64(encrypted_key),
      iv: Base64.strict_encode64(iv),
      ciphertext: Base64.strict_encode64(ciphertext),
      auth_tag: Base64.strict_encode64(cipher.auth_tag)
    }
  end
end

Encryption Is Not Encoding

Base64 encoding provides no security despite obscuring data. Encoding transforms data representation without secret keys and reverses trivially. Encryption requires secret keys and computational difficulty. Systems must never rely on encoding for confidentiality.

# Encoding - reversible without secrets
encoded = Base64.strict_encode64("Secret data")
decoded = Base64.strict_decode64(encoded)  # No key needed

# Encryption - requires secret key
cipher = OpenSSL::Cipher.new('AES-256-GCM')
cipher.encrypt
key = cipher.random_key  # Secret required
cipher.key = key

encrypted = cipher.update("Secret data") + cipher.final
# Cannot decrypt without key

Practical Examples

Real-world encryption scenarios demonstrate practical application patterns, error handling requirements, and integration considerations. These examples show complete implementations suitable for production adaptation.

Encrypted File Storage

Applications frequently need to encrypt files before storing them on disk or cloud storage. This implementation encrypts files with metadata preservation and authenticated encryption.

require 'openssl'
require 'base64'
require 'json'

class EncryptedFileStorage
  ALGORITHM = 'AES-256-GCM'
  
  def initialize(master_key)
    @master_key = master_key
  end
  
  def encrypt_file(source_path, destination_path)
    plaintext = File.binread(source_path)
    
    cipher = OpenSSL::Cipher.new(ALGORITHM)
    cipher.encrypt
    cipher.key = @master_key
    iv = cipher.random_iv
    
    ciphertext = cipher.update(plaintext) + cipher.final
    auth_tag = cipher.auth_tag
    
    # Store metadata with encrypted content
    encrypted_data = {
      version: 1,
      algorithm: ALGORITHM,
      iv: Base64.strict_encode64(iv),
      auth_tag: Base64.strict_encode64(auth_tag),
      ciphertext: Base64.strict_encode64(ciphertext),
      metadata: {
        original_size: plaintext.bytesize,
        encrypted_at: Time.now.utc.iso8601
      }
    }
    
    File.write(destination_path, JSON.generate(encrypted_data))
    
    true
  end
  
  def decrypt_file(encrypted_path, destination_path)
    encrypted_data = JSON.parse(File.read(encrypted_path), symbolize_names: true)
    
    raise "Unsupported version" unless encrypted_data[:version] == 1
    
    cipher = OpenSSL::Cipher.new(encrypted_data[:algorithm])
    cipher.decrypt
    cipher.key = @master_key
    cipher.iv = Base64.strict_decode64(encrypted_data[:iv])
    cipher.auth_tag = Base64.strict_decode64(encrypted_data[:auth_tag])
    
    ciphertext = Base64.strict_decode64(encrypted_data[:ciphertext])
    plaintext = cipher.update(ciphertext) + cipher.final
    
    File.binwrite(destination_path, plaintext)
    
    encrypted_data[:metadata]
  rescue OpenSSL::Cipher::CipherError => e
    raise "Decryption failed: #{e.message}"
  end
end

# Usage
master_key = OpenSSL::Cipher.new('AES-256-GCM').random_key
storage = EncryptedFileStorage.new(master_key)

# Encrypt file
storage.encrypt_file('sensitive_document.pdf', 'document.encrypted')

# Decrypt file
metadata = storage.decrypt_file('document.encrypted', 'restored_document.pdf')
# => {:original_size=>1024, :encrypted_at=>"2025-10-07T10:30:00Z"}

Database Field Encryption

Encrypting specific database fields protects sensitive information while maintaining database functionality for non-sensitive fields. This pattern encrypts data before storage and decrypts after retrieval.

require 'openssl'
require 'base64'

module EncryptedAttribute
  def self.included(base)
    base.extend(ClassMethods)
  end
  
  module ClassMethods
    def encrypt_attribute(attribute_name, key:)
      define_method("#{attribute_name}=") do |value|
        encrypted = encrypt_value(value, key)
        instance_variable_set("@#{attribute_name}", value)
        write_attribute("encrypted_#{attribute_name}", encrypted)
      end
      
      define_method(attribute_name) do
        encrypted_value = read_attribute("encrypted_#{attribute_name}")
        return nil unless encrypted_value
        
        cached = instance_variable_get("@#{attribute_name}")
        return cached if cached
        
        decrypted = decrypt_value(encrypted_value, key)
        instance_variable_set("@#{attribute_name}", decrypted)
        decrypted
      end
    end
  end
  
  private
  
  def encrypt_value(plaintext, key)
    return nil if plaintext.nil?
    
    cipher = OpenSSL::Cipher.new('AES-256-GCM')
    cipher.encrypt
    cipher.key = key
    iv = cipher.random_iv
    
    ciphertext = cipher.update(plaintext.to_s) + cipher.final
    
    # Pack IV, auth tag, and ciphertext together
    packed = [
      iv,
      cipher.auth_tag,
      ciphertext
    ].map { |data| Base64.strict_encode64(data) }.join(':')
    
    packed
  end
  
  def decrypt_value(encrypted_value, key)
    iv_b64, tag_b64, ciphertext_b64 = encrypted_value.split(':', 3)
    
    cipher = OpenSSL::Cipher.new('AES-256-GCM')
    cipher.decrypt
    cipher.key = key
    cipher.iv = Base64.strict_decode64(iv_b64)
    cipher.auth_tag = Base64.strict_decode64(tag_b64)
    
    ciphertext = Base64.strict_decode64(ciphertext_b64)
    cipher.update(ciphertext) + cipher.final
  end
end

# Example ActiveRecord model
class User
  include EncryptedAttribute
  
  ENCRYPTION_KEY = ENV['USER_ENCRYPTION_KEY']
  
  encrypt_attribute :ssn, key: ENCRYPTION_KEY
  encrypt_attribute :credit_card, key: ENCRYPTION_KEY
end

# Usage
user = User.new
user.ssn = "123-45-6789"
user.save

# Database stores encrypted value in encrypted_ssn column
# Accessing user.ssn automatically decrypts
retrieved_user = User.find(user.id)
puts retrieved_user.ssn  # => "123-45-6789"

Message Encryption with Key Exchange

Secure messaging between parties requires exchanging encryption keys without prior shared secrets. This implementation uses public key cryptography for key establishment.

class SecureMessaging
  def initialize
    @private_key = OpenSSL::PKey::RSA.new(2048)
    @public_key = @private_key.public_key
  end
  
  attr_reader :public_key
  
  def send_message(plaintext, recipient_public_key)
    # Generate ephemeral symmetric key
    cipher = OpenSSL::Cipher.new('AES-256-GCM')
    cipher.encrypt
    session_key = cipher.random_key
    
    # Encrypt message with symmetric key
    iv = cipher.random_iv
    cipher.key = session_key
    ciphertext = cipher.update(plaintext) + cipher.final
    auth_tag = cipher.auth_tag
    
    # Encrypt session key with recipient's public key
    encrypted_session_key = recipient_public_key.public_encrypt(
      session_key,
      OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
    )
    
    {
      encrypted_key: Base64.strict_encode64(encrypted_session_key),
      iv: Base64.strict_encode64(iv),
      ciphertext: Base64.strict_encode64(ciphertext),
      auth_tag: Base64.strict_encode64(auth_tag)
    }
  end
  
  def receive_message(encrypted_message)
    # Decrypt session key with private key
    encrypted_key = Base64.strict_decode64(encrypted_message[:encrypted_key])
    session_key = @private_key.private_decrypt(
      encrypted_key,
      OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
    )
    
    # Decrypt message with session key
    cipher = OpenSSL::Cipher.new('AES-256-GCM')
    cipher.decrypt
    cipher.key = session_key
    cipher.iv = Base64.strict_decode64(encrypted_message[:iv])
    cipher.auth_tag = Base64.strict_decode64(encrypted_message[:auth_tag])
    
    ciphertext = Base64.strict_decode64(encrypted_message[:ciphertext])
    plaintext = cipher.update(ciphertext) + cipher.final
    
    # Clear session key from memory
    session_key.replace("\0" * session_key.length)
    
    plaintext
  end
end

# Usage
alice = SecureMessaging.new
bob = SecureMessaging.new

# Alice sends encrypted message to Bob
message = "Confidential communication"
encrypted = alice.send_message(message, bob.public_key)

# Bob decrypts message
decrypted = bob.receive_message(encrypted)
# => "Confidential communication"

Common Pitfalls

Encryption implementations frequently contain subtle errors that compromise security despite using strong algorithms. Understanding common mistakes helps developers avoid these vulnerabilities.

Using ECB Mode

ECB (Electronic Codebook) mode encrypts each block independently, leaking information about repeated plaintext patterns. Identical plaintext blocks produce identical ciphertext blocks, revealing data structure. ECB mode should never be used except for single-block encryption.

# Dangerous - ECB mode reveals patterns
cipher = OpenSSL::Cipher.new('AES-256-ECB')
cipher.encrypt
key = cipher.random_key

# Repeated data produces visible patterns in ciphertext
plaintext = "AAAAAAAAAAAAAAAA" * 10  # Repeated pattern
ciphertext = cipher.update(plaintext) + cipher.final
# Ciphertext contains repeating blocks

# Safe - CBC mode with random IV
cipher = OpenSSL::Cipher.new('AES-256-CBC')
cipher.encrypt
cipher.key = key
cipher.iv = cipher.random_iv  # Random IV prevents patterns

ciphertext = cipher.update(plaintext) + cipher.final
# Ciphertext appears random despite repeated plaintext

Ignoring Authentication

Encryption without authentication allows attackers to modify ciphertext undetected. Bit-flipping attacks can alter decrypted plaintext predictably without knowing keys. Always use authenticated encryption (AEAD) modes or combine encryption with HMAC.

# Vulnerable - no authentication
cipher = OpenSSL::Cipher.new('AES-256-CBC')
cipher.encrypt
key = cipher.random_key
iv = cipher.random_iv

ciphertext = cipher.update("amount:1000") + cipher.final
# Attacker can modify ciphertext bits
# Decryption succeeds but produces altered plaintext

# Protected - authenticated encryption
cipher = OpenSSL::Cipher.new('AES-256-GCM')
cipher.encrypt
cipher.key = key
iv = cipher.random_iv

ciphertext = cipher.update("amount:1000") + cipher.final
auth_tag = cipher.auth_tag

# Any modification detected during decryption
cipher.decrypt
cipher.key = key
cipher.iv = iv
cipher.auth_tag = auth_tag

begin
  modified_ciphertext = ciphertext.dup
  modified_ciphertext[0] = modified_ciphertext[0].ord ^ 1  # Flip one bit
  plaintext = cipher.update(modified_ciphertext) + cipher.final
rescue OpenSSL::Cipher::CipherError
  # Authentication fails - modification detected
end

Weak Password-Based Keys

Directly using passwords as encryption keys creates vulnerability to dictionary attacks. Passwords lack sufficient entropy and predictability. Key derivation functions with high iteration counts increase computational cost for attackers.

# Weak - password used directly as key
password = "user_password"
weak_key = OpenSSL::Digest::SHA256.digest(password)[0...32]

# Strong - PBKDF2 with high iteration count
salt = OpenSSL::Random.random_bytes(16)
strong_key = OpenSSL::PKCS5.pbkdf2_hmac(
  password,
  salt,
  100_000,  # High iteration count
  32,
  OpenSSL::Digest::SHA256.new
)

# Store salt with encrypted data for decryption

Reusing Keys Across Contexts

Single keys used for multiple purposes create cross-context vulnerabilities. Encryption keys should not sign data. Keys for different data types should remain separate. Key derivation can generate context-specific keys from master keys.

# Risky - same key for multiple purposes
master_key = OpenSSL::Cipher.new('AES-256-GCM').random_key

# Better - derive context-specific keys
def derive_context_key(master_key, context)
  OpenSSL::PKCS5.pbkdf2_hmac(
    master_key,
    context,
    1,      # Single iteration - master key already strong
    32,
    OpenSSL::Digest::SHA256.new
  )
end

encryption_key = derive_context_key(master_key, "encryption")
signing_key = derive_context_key(master_key, "signing")
backup_key = derive_context_key(master_key, "backup")

Insufficient Random Data

Using non-cryptographic random sources for keys or IVs creates predictability. Standard Random or rand functions produce deterministic sequences unsuitable for cryptography. Always use OpenSSL::Random.random_bytes or cipher random methods.

# Insecure - predictable randomness
bad_key = Array.new(32) { rand(256) }.pack('C*')
bad_iv = Array.new(16) { Random.rand(256) }.pack('C*')

# Secure - cryptographic randomness
good_key = OpenSSL::Random.random_bytes(32)
good_iv = OpenSSL::Random.random_bytes(16)

# Using cipher methods (also secure)
cipher = OpenSSL::Cipher.new('AES-256-GCM')
cipher.encrypt
secure_key = cipher.random_key
secure_iv = cipher.random_iv

Storing Plaintext Keys

Keys stored in source code, logs, error messages, or insecure locations compromise all encrypted data. Keys require the same protection as the data they encrypt. Production systems need key management infrastructure.

Trusting Client-Side Encryption

Encryption performed in browser JavaScript provides minimal security since adversaries control the execution environment. Client-side encryption can prevent passive network observation but cannot protect against compromised clients or malicious JavaScript. Server-side encryption with proper key management provides meaningful security.

Reference

Symmetric Algorithms

Algorithm Key Size Block Size Mode Support Use Case
AES-128 128 bits 128 bits CBC, GCM, CTR, etc Fast, widely supported
AES-192 192 bits 128 bits CBC, GCM, CTR, etc Moderate security margin
AES-256 256 bits 128 bits CBC, GCM, CTR, etc Maximum security, compliance
ChaCha20 256 bits Stream Poly1305 (AEAD) Software performance, mobile
3DES 168 bits 64 bits CBC Legacy systems only

Asymmetric Algorithms

Algorithm Key Size Operation Use Case
RSA-2048 2048 bits Encrypt/Decrypt Key exchange, digital signatures
RSA-3072 3072 bits Encrypt/Decrypt Long-term security
RSA-4096 4096 bits Encrypt/Decrypt Maximum security, slower
ECC P-256 256 bits Key agreement Efficient key exchange
ECC P-384 384 bits Key agreement Government/compliance

Common Cipher Modes

Mode IV Required Authentication Parallelizable Use Case
CBC Yes No Decrypt only General purpose, legacy
CTR Yes No Yes Performance, random access
GCM Yes Yes Yes Modern authenticated encryption
CCM Yes Yes No Constrained environments
XTS Yes No Yes Disk encryption

Ruby OpenSSL Cipher Operations

Method Description Returns
OpenSSL::Cipher.new(name) Creates cipher instance Cipher object
cipher.encrypt Sets cipher to encryption mode nil
cipher.decrypt Sets cipher to decryption mode nil
cipher.random_key Generates random key Binary string
cipher.random_iv Generates random IV Binary string
cipher.key= Sets encryption key nil
cipher.iv= Sets initialization vector nil
cipher.auth_tag Gets authentication tag (GCM/CCM) Binary string
cipher.auth_tag= Sets authentication tag for verification nil
cipher.auth_data= Sets additional authenticated data nil
cipher.update(data) Encrypts or decrypts data chunk Binary string
cipher.final Completes operation, returns final block Binary string
cipher.reset Resets cipher to initial state nil

Key Derivation Parameters

Parameter Purpose Recommended Value
Salt Prevents rainbow table attacks 16+ bytes, unique per password
Iterations Increases computational cost 100,000+ for PBKDF2-SHA256
Key length Output key size 256 bits for AES-256
Hash function Underlying hash for KDF SHA-256 or SHA-512

Security Requirements

Component Requirement Implementation
Keys Cryptographically random OpenSSL::Random.random_bytes
IVs Unique per encryption cipher.random_iv for each operation
Salt Unique per password OpenSSL::Random.random_bytes(16)
Auth tags Verified before using plaintext Check before decryption with GCM
Key storage Protected, not in source code KMS, vault, secure key storage
Key rotation Regular key updates Depends on data sensitivity

OpenSSL Cipher Names

Cipher String Description
AES-128-CBC AES 128-bit key, CBC mode
AES-256-CBC AES 256-bit key, CBC mode
AES-128-GCM AES 128-bit key, GCM authenticated mode
AES-256-GCM AES 256-bit key, GCM authenticated mode
ChaCha20-Poly1305 ChaCha20 stream cipher with Poly1305 MAC
AES-128-CTR AES 128-bit key, counter mode
AES-256-CTR AES 256-bit key, counter mode

Error Handling

Error Cause Resolution
OpenSSL::Cipher::CipherError Authentication failure, wrong key, corrupted data Verify key, check data integrity, validate auth tag
ArgumentError Invalid key or IV length Use correct length for algorithm
TypeError Wrong data type for key/IV Ensure binary string format
RuntimeError Operation on uninitialized cipher Call encrypt or decrypt before operations