Overview
Cryptographic best practices define the standards and techniques for implementing secure cryptographic operations in software applications. These practices address the proper use of encryption algorithms, secure key generation and storage, authenticated encryption, and protection against timing attacks and other cryptographic vulnerabilities.
Cryptography serves multiple purposes in software systems: protecting data confidentiality through encryption, ensuring data integrity through hashing and message authentication codes, verifying identity through digital signatures, and establishing secure communication channels. Incorrect implementation of cryptographic operations introduces severe security vulnerabilities that attackers can exploit to compromise sensitive data.
The field distinguishes between several cryptographic primitives. Symmetric encryption uses the same key for encryption and decryption, providing fast operations for bulk data protection. Asymmetric encryption uses key pairs (public and private keys) for operations where the encryption key differs from the decryption key. Hash functions create fixed-size digests from variable-length input, supporting integrity verification. Message authentication codes combine hashing with secret keys to provide authenticated integrity checking.
Modern cryptographic implementations must address multiple threat vectors. Side-channel attacks extract secret information by analyzing timing variations, power consumption, or electromagnetic emissions. Padding oracle attacks exploit error messages to decrypt data without knowing the key. Weak random number generation compromises key security and makes cryptographic operations predictable.
# Insecure: Using deprecated algorithm
require 'digest'
insecure_hash = Digest::MD5.hexdigest('sensitive_data')
# Secure: Using modern algorithm
secure_hash = Digest::SHA256.hexdigest('sensitive_data')
The National Institute of Standards and Technology (NIST) and other standards bodies publish recommendations for cryptographic algorithm selection and implementation. These recommendations change as computing power increases and cryptanalytic techniques advance. Algorithms considered secure a decade ago may become vulnerable, requiring systems to support algorithm agility and key rotation.
Key Principles
Algorithm Selection: Choose cryptographic algorithms that current security research considers secure. For symmetric encryption, AES with 256-bit keys provides strong security. For hashing, SHA-256 or SHA-3 resist collision attacks. For public key cryptography, RSA with minimum 2048-bit keys or Elliptic Curve Cryptography (ECC) with 256-bit curves offers sufficient security. Avoid deprecated algorithms including DES, 3DES, MD5, SHA-1, and RSA with keys shorter than 2048 bits.
Key Management: Generate cryptographic keys using cryptographically secure random number generators. Never hardcode keys in source code or configuration files. Store keys in secure key management systems, hardware security modules, or encrypted key stores. Implement key rotation policies that periodically replace cryptographic keys. Separate key management from application logic to limit exposure if the application becomes compromised.
Authenticated Encryption: Use authenticated encryption modes that provide both confidentiality and integrity protection. GCM (Galois/Counter Mode) and ChaCha20-Poly1305 detect tampering and prevent attacks that modify ciphertext. Avoid unauthenticated modes like ECB (Electronic Codebook) or CBC (Cipher Block Chaining) without MAC verification, which allow attackers to manipulate encrypted data without detection.
Initialization Vectors and Nonces: Generate unique, unpredictable initialization vectors (IVs) or nonces for each encryption operation. Reusing IVs with the same key in certain modes compromises security and can reveal plaintext patterns. Store IVs alongside ciphertext since they need not remain secret, only unique and unpredictable.
Random Number Generation: Use cryptographically secure random number generators (CSRNGs) for all security-sensitive operations including key generation, IV creation, salt generation, and nonce selection. Standard pseudo-random number generators lack the entropy and unpredictability required for cryptographic security. Operating system-provided random number generators (/dev/urandom on Unix systems) or language-specific secure random APIs provide adequate entropy.
Timing Attack Resistance: Implement constant-time comparison functions when verifying MACs, signatures, or password hashes. Variable-time comparisons that short-circuit on the first differing byte leak information about secret values through timing analysis. Even microsecond differences in execution time can reveal secrets when attackers make thousands of carefully timed requests.
Salt and Iteration Counts: When deriving keys from passwords, use unique salts for each password and high iteration counts to slow brute-force attacks. Password-based key derivation functions like PBKDF2, bcrypt, scrypt, or Argon2 intentionally consume significant computational resources to make password cracking expensive. Increase iteration counts over time as hardware improves.
Key Derivation: Derive multiple keys from a master secret using proper key derivation functions rather than reusing the same key for different purposes. HKDF (HMAC-based Key Derivation Function) expands a master key into multiple independent keys with different context labels, preventing key reuse vulnerabilities.
require 'openssl'
# Derive multiple keys from master secret
master_key = OpenSSL::Random.random_bytes(32)
salt = OpenSSL::Random.random_bytes(16)
# Derive encryption key
encryption_key = OpenSSL::PKCS5.pbkdf2_hmac(
master_key,
salt,
100_000,
32,
OpenSSL::Digest::SHA256.new
)
# Derive MAC key with different salt
mac_salt = OpenSSL::Random.random_bytes(16)
mac_key = OpenSSL::PKCS5.pbkdf2_hmac(
master_key,
mac_salt,
100_000,
32,
OpenSSL::Digest::SHA256.new
)
Certificate Validation: When using TLS/SSL, validate server certificates against trusted certificate authorities. Verify certificate chains, check certificate revocation status, and validate that the certificate common name matches the server hostname. Disable insecure SSL/TLS versions (SSLv2, SSLv3, TLS 1.0, TLS 1.1) and weak cipher suites.
Forward Secrecy: Implement forward secrecy in communication protocols so that compromise of long-term keys does not compromise past session keys. Ephemeral Diffie-Hellman key exchange generates temporary session keys that cannot be recovered from long-term private keys, protecting historical communications even if the server becomes compromised later.
Security Implications
Cryptographic vulnerabilities create cascading security failures across entire systems. Unlike typical software bugs that might cause crashes or incorrect output, cryptographic flaws often operate silently, allowing attackers to decrypt data, forge signatures, or bypass authentication without triggering obvious errors.
Algorithm Vulnerability: Using deprecated or weak cryptographic algorithms exposes systems to known attacks. MD5 collisions allow attackers to create different documents with identical hashes, potentially forging digital signatures or bypassing integrity checks. SHA-1 collisions, demonstrated in practice, threaten systems still relying on SHA-1 for certificate validation or git commit signing. DES and 3DES suffer from small key spaces that modern hardware can brute force in hours or days.
Key Exposure: Hardcoded keys in source code become permanently compromised once the code ships. Decompiling applications or examining configuration files reveals these keys, allowing attackers to decrypt all data encrypted with them. Key material logged accidentally in debug output or error messages provides another exposure vector. Systems must treat cryptographic keys with the same sensitivity as passwords, implementing access controls and audit trails.
Cryptographic Timing Attacks: Implementations that take different execution paths based on secret data leak information through timing side channels. Comparing MACs byte-by-byte and returning on the first difference allows attackers to guess the correct MAC one byte at a time by measuring response times. Even remote timing attacks over networks succeed when attackers accumulate timing measurements across thousands of requests to overcome network jitter.
# Vulnerable to timing attack
def insecure_compare(expected_mac, provided_mac)
return false if expected_mac.length != provided_mac.length
expected_mac.bytes.each_with_index do |byte, i|
return false if byte != provided_mac.bytes[i] # Early exit leaks info
end
true
end
# Constant-time comparison
def secure_compare(expected_mac, provided_mac)
return false if expected_mac.length != provided_mac.length
result = 0
expected_mac.bytes.each_with_index do |byte, i|
result |= byte ^ provided_mac.bytes[i]
end
result == 0
end
Padding Oracle Attacks: Systems that return different error messages for padding errors versus MAC errors in CBC mode encryption allow attackers to decrypt data through chosen-ciphertext attacks. Attackers submit modified ciphertext and use the error responses to iteratively determine plaintext values without knowing the encryption key. Authenticated encryption modes prevent these attacks by verifying integrity before attempting decryption.
Random Number Predictability: Weak random number generation compromises all cryptographic operations. If attackers can predict IVs, nonces, or session tokens, they can potentially decrypt communications or forge authenticated messages. Systems must use CSRNGs seeded from high-entropy sources, not simple pseudo-random generators or time-based seeds.
IV Reuse Vulnerabilities: Reusing the same IV with the same key in CTR or GCM modes catastrophically compromises security. In CTR mode, reused IVs allow attackers to XOR two ciphertexts encrypted with the same key stream, canceling the encryption and revealing relationships between plaintexts. In GCM mode, IV reuse allows attackers to forge authentication tags.
Insufficient Key Derivation: Deriving keys from passwords without proper key derivation functions makes them vulnerable to dictionary and brute-force attacks. Simple hashing of passwords (even with SHA-256) completes billions of hash operations per second on modern hardware, allowing attackers to test huge password lists quickly. Purpose-built key derivation functions with high iteration counts or memory-hard algorithms slow attacks to manageable speeds.
Certificate Validation Failures: Accepting any certificate without validation allows man-in-the-middle attacks where attackers present their own certificates to impersonate legitimate servers. Disabling certificate validation for "convenience" during development often carries forward into production. Accepting expired certificates, certificates from untrusted authorities, or certificates with hostname mismatches undermines the entire TLS security model.
Cryptographic Library Misuse: Even secure cryptographic libraries become vulnerable when used incorrectly. Encrypting data in ECB mode with a secure algorithm like AES still leaks pattern information. Using authenticated encryption but failing to verify the authentication tag before decryption negates the protection. Implementing custom cryptographic protocols without peer review introduces vulnerabilities that standard protocols avoid.
Ruby Implementation
Ruby provides cryptographic capabilities primarily through the OpenSSL library binding, offering access to industry-standard cryptographic primitives. The openssl standard library wraps the OpenSSL C library, providing encryption, hashing, signatures, and certificate operations.
Symmetric Encryption: The OpenSSL::Cipher class implements symmetric encryption algorithms. AES in GCM mode provides authenticated encryption suitable for most applications.
require 'openssl'
def encrypt_data(plaintext, key)
cipher = OpenSSL::Cipher.new('aes-256-gcm')
cipher.encrypt
cipher.key = key
# Generate random IV
iv = cipher.random_iv
# Encrypt and get authentication tag
ciphertext = cipher.update(plaintext) + cipher.final
auth_tag = cipher.auth_tag
# Return IV, ciphertext, and auth tag
{
iv: iv,
ciphertext: ciphertext,
auth_tag: auth_tag
}
end
def decrypt_data(encrypted_data, key)
cipher = OpenSSL::Cipher.new('aes-256-gcm')
cipher.decrypt
cipher.key = key
cipher.iv = encrypted_data[:iv]
cipher.auth_tag = encrypted_data[:auth_tag]
# Decrypt and verify authentication
cipher.update(encrypted_data[:ciphertext]) + cipher.final
rescue OpenSSL::Cipher::CipherError
raise 'Authentication failed or data corrupted'
end
# Usage
key = OpenSSL::Random.random_bytes(32)
encrypted = encrypt_data('sensitive information', key)
decrypted = decrypt_data(encrypted, key)
Secure Hashing: The Digest module provides cryptographic hash functions. SHA-256 offers strong collision resistance for most applications.
require 'digest'
# Simple hashing
hash = Digest::SHA256.hexdigest('data to hash')
# Hashing large files efficiently
def hash_file(filepath)
digest = Digest::SHA256.new
File.open(filepath, 'rb') do |file|
while chunk = file.read(8192)
digest.update(chunk)
end
end
digest.hexdigest
end
# HMAC for message authentication
def generate_hmac(message, secret_key)
OpenSSL::HMAC.hexdigest(
OpenSSL::Digest::SHA256.new,
secret_key,
message
)
end
Password Hashing: Ruby's bcrypt gem provides secure password hashing with automatic salt generation and configurable work factors.
require 'bcrypt'
# Hash password with default cost (currently 12)
password_hash = BCrypt::Password.create('user_password')
# Store password_hash in database
# Later, verify password:
if BCrypt::Password.new(stored_hash) == provided_password
# Authentication successful
end
# Increase cost for stronger security (higher cost = slower)
strong_hash = BCrypt::Password.create('password', cost: 14)
Secure Random Generation: The SecureRandom module provides cryptographically secure random number generation.
require 'securerandom'
# Generate random bytes
random_key = SecureRandom.random_bytes(32)
# Generate random hex string
token = SecureRandom.hex(32) # 64 character hex string
# Generate random URL-safe base64
session_id = SecureRandom.urlsafe_base64(32)
# Generate random UUID
request_id = SecureRandom.uuid
Public Key Cryptography: OpenSSL bindings support RSA and ECC operations for asymmetric encryption and digital signatures.
require 'openssl'
# Generate RSA key pair
rsa_key = OpenSSL::PKey::RSA.generate(2048)
private_key = rsa_key.to_pem
public_key = rsa_key.public_key.to_pem
# Sign data with private key
def sign_data(data, private_key_pem)
key = OpenSSL::PKey::RSA.new(private_key_pem)
key.sign(OpenSSL::Digest::SHA256.new, data)
end
# Verify signature with public key
def verify_signature(data, signature, public_key_pem)
key = OpenSSL::PKey::RSA.new(public_key_pem)
key.verify(OpenSSL::Digest::SHA256.new, signature, data)
end
# RSA encryption (for small data like symmetric keys)
def rsa_encrypt(plaintext, public_key_pem)
key = OpenSSL::PKey::RSA.new(public_key_pem)
key.public_encrypt(plaintext)
end
def rsa_decrypt(ciphertext, private_key_pem)
key = OpenSSL::PKey::RSA.new(private_key_pem)
key.private_decrypt(ciphertext)
end
Key Derivation from Passwords: OpenSSL provides PBKDF2 for deriving cryptographic keys from passwords.
require 'openssl'
def derive_key(password, salt, iterations = 100_000)
OpenSSL::PKCS5.pbkdf2_hmac(
password,
salt,
iterations,
32, # Output length in bytes
OpenSSL::Digest::SHA256.new
)
end
# Generate unique salt for each password
salt = SecureRandom.random_bytes(16)
derived_key = derive_key('user_password', salt)
# Store both salt and derived key
# Salt does not need to be secret, only unique per password
TLS/SSL Configuration: Ruby's OpenSSL bindings allow configuring secure network connections.
require 'net/http'
require 'openssl'
uri = URI('https://api.example.com/data')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
# Verify certificates
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
# Use strong TLS versions only
http.min_version = OpenSSL::SSL::TLS1_2_VERSION
# Configure cipher suites (optional, defaults are usually good)
http.ciphers = 'HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4'
response = http.get(uri.path)
Practical Examples
Encrypting User Data at Rest: Applications storing sensitive user data in databases should encrypt the data before storage.
require 'openssl'
require 'securerandom'
require 'base64'
class UserDataEncryptor
def initialize(master_key)
@master_key = master_key
end
def encrypt(plaintext)
cipher = OpenSSL::Cipher.new('aes-256-gcm')
cipher.encrypt
cipher.key = @master_key
iv = cipher.random_iv
ciphertext = cipher.update(plaintext) + cipher.final
auth_tag = cipher.auth_tag
# Encode for database storage
{
iv: Base64.strict_encode64(iv),
ciphertext: Base64.strict_encode64(ciphertext),
auth_tag: Base64.strict_encode64(auth_tag)
}
end
def decrypt(encrypted_data)
cipher = OpenSSL::Cipher.new('aes-256-gcm')
cipher.decrypt
cipher.key = @master_key
cipher.iv = Base64.strict_decode64(encrypted_data[:iv])
cipher.auth_tag = Base64.strict_decode64(encrypted_data[:auth_tag])
cipher.update(Base64.strict_decode64(encrypted_data[:ciphertext])) +
cipher.final
rescue OpenSSL::Cipher::CipherError
raise 'Decryption failed - data may be corrupted or tampered'
end
end
# Usage in application
master_key = ENV['ENCRYPTION_KEY'] || raise('ENCRYPTION_KEY not set')
encryptor = UserDataEncryptor.new(Base64.strict_decode64(master_key))
# Encrypt before saving
encrypted = encryptor.encrypt('sensitive user data')
# Save encrypted[:iv], encrypted[:ciphertext], encrypted[:auth_tag] to database
# Decrypt after loading
decrypted = encryptor.decrypt(encrypted)
Generating and Verifying API Tokens: Secure API authentication requires cryptographically signed tokens that prevent tampering.
require 'openssl'
require 'json'
require 'base64'
class APITokenManager
def initialize(secret_key)
@secret_key = secret_key
end
def generate_token(user_id, expires_at)
payload = {
user_id: user_id,
expires_at: expires_at.to_i,
jti: SecureRandom.uuid # Unique token ID
}
encoded_payload = Base64.urlsafe_encode64(
JSON.generate(payload),
padding: false
)
signature = OpenSSL::HMAC.digest(
OpenSSL::Digest::SHA256.new,
@secret_key,
encoded_payload
)
encoded_signature = Base64.urlsafe_encode64(signature, padding: false)
"#{encoded_payload}.#{encoded_signature}"
end
def verify_token(token)
encoded_payload, encoded_signature = token.split('.')
return nil unless encoded_payload && encoded_signature
# Compute expected signature
expected_signature = OpenSSL::HMAC.digest(
OpenSSL::Digest::SHA256.new,
@secret_key,
encoded_payload
)
provided_signature = Base64.urlsafe_decode64(encoded_signature)
# Constant-time comparison
return nil unless secure_compare(expected_signature, provided_signature)
payload = JSON.parse(
Base64.urlsafe_decode64(encoded_payload)
)
# Check expiration
return nil if Time.now.to_i > payload['expires_at']
payload
rescue StandardError
nil
end
private
def secure_compare(a, b)
return false unless a.bytesize == b.bytesize
result = 0
a.bytes.each_with_index do |byte, i|
result |= byte ^ b.bytes[i]
end
result == 0
end
end
# Usage
secret = SecureRandom.random_bytes(32)
token_manager = APITokenManager.new(secret)
# Generate token valid for 1 hour
token = token_manager.generate_token(
user_id: 123,
expires_at: Time.now + 3600
)
# Verify token
payload = token_manager.verify_token(token)
if payload
user_id = payload['user_id']
# Process authenticated request
else
# Invalid or expired token
end
Secure File Upload with Integrity Verification: Applications accepting file uploads should verify file integrity and optionally encrypt files for storage.
require 'openssl'
require 'securerandom'
class SecureFileHandler
def initialize(storage_path, encryption_key)
@storage_path = storage_path
@encryption_key = encryption_key
end
def store_file(file_path, original_filename)
# Generate unique file ID
file_id = SecureRandom.uuid
# Read and hash original file
content = File.binread(file_path)
content_hash = Digest::SHA256.hexdigest(content)
# Encrypt file
cipher = OpenSSL::Cipher.new('aes-256-gcm')
cipher.encrypt
cipher.key = @encryption_key
iv = cipher.random_iv
encrypted_content = cipher.update(content) + cipher.final
auth_tag = cipher.auth_tag
# Store encrypted file
storage_file = File.join(@storage_path, file_id)
File.binwrite(storage_file, encrypted_content)
# Return metadata for database
{
file_id: file_id,
original_filename: original_filename,
content_hash: content_hash,
iv: Base64.strict_encode64(iv),
auth_tag: Base64.strict_encode64(auth_tag),
size: content.bytesize
}
end
def retrieve_file(metadata)
storage_file = File.join(@storage_path, metadata[:file_id])
encrypted_content = File.binread(storage_file)
# Decrypt file
cipher = OpenSSL::Cipher.new('aes-256-gcm')
cipher.decrypt
cipher.key = @encryption_key
cipher.iv = Base64.strict_decode64(metadata[:iv])
cipher.auth_tag = Base64.strict_decode64(metadata[:auth_tag])
decrypted_content = cipher.update(encrypted_content) + cipher.final
# Verify integrity
computed_hash = Digest::SHA256.hexdigest(decrypted_content)
unless computed_hash == metadata[:content_hash]
raise 'File integrity check failed'
end
decrypted_content
rescue OpenSSL::Cipher::CipherError
raise 'File decryption failed - possible tampering detected'
end
end
Implementing Secure Session Management: Web applications require secure session token generation and validation.
require 'securerandom'
require 'openssl'
require 'json'
class SessionManager
SESSION_DURATION = 3600 # 1 hour
def initialize(signing_key)
@signing_key = signing_key
@sessions = {} # In production, use Redis or database
end
def create_session(user_id, user_data = {})
session_id = SecureRandom.urlsafe_base64(32)
session_token = SecureRandom.urlsafe_base64(32)
session_data = {
user_id: user_id,
created_at: Time.now.to_i,
expires_at: Time.now.to_i + SESSION_DURATION,
data: user_data
}
# Store session server-side
@sessions[session_id] = {
token_hash: Digest::SHA256.hexdigest(session_token),
data: session_data
}
# Return session ID and token to client
# Session ID can be in cookie, token can be in secure httpOnly cookie
{ session_id: session_id, session_token: session_token }
end
def validate_session(session_id, session_token)
session = @sessions[session_id]
return nil unless session
# Verify token with constant-time comparison
provided_hash = Digest::SHA256.hexdigest(session_token)
return nil unless secure_compare(session[:token_hash], provided_hash)
# Check expiration
return nil if Time.now.to_i > session[:data][:expires_at]
session[:data]
end
def destroy_session(session_id)
@sessions.delete(session_id)
end
private
def secure_compare(a, b)
return false unless a.bytesize == b.bytesize
result = 0
a.bytes.zip(b.bytes).each { |x, y| result |= x ^ y }
result == 0
end
end
Common Pitfalls
Using ECB Mode: Electronic Codebook (ECB) mode encrypts each block independently, revealing patterns in plaintext through identical ciphertext blocks.
# DANGEROUS: ECB mode leaks patterns
cipher = OpenSSL::Cipher.new('aes-256-ecb')
cipher.encrypt
cipher.key = key
# Identical plaintext blocks produce identical ciphertext blocks
# CORRECT: Use GCM or CBC mode
cipher = OpenSSL::Cipher.new('aes-256-gcm')
cipher.encrypt
cipher.key = key
iv = cipher.random_iv # Each encryption uses unique IV
Reusing Initialization Vectors: Using the same IV with the same key for multiple encryptions compromises security in CTR and GCM modes.
# DANGEROUS: Reusing IV
static_iv = "\x00" * 16 # Never do this
cipher.iv = static_iv # Same IV for all encryptions
# CORRECT: Generate new IV each time
cipher.iv = cipher.random_iv # Unique IV per encryption
Insufficient Password Hashing Iterations: Using low iteration counts makes password cracking feasible with modern hardware.
# WEAK: Too few iterations
weak_hash = OpenSSL::PKCS5.pbkdf2_hmac(
password,
salt,
1_000, # Only 1000 iterations
32,
OpenSSL::Digest::SHA256.new
)
# STRONG: Use at least 100,000 iterations
strong_hash = OpenSSL::PKCS5.pbkdf2_hmac(
password,
salt,
100_000, # NIST minimum recommendation
32,
OpenSSL::Digest::SHA256.new
)
# BETTER: Use bcrypt with appropriate cost
BCrypt::Password.create(password, cost: 12)
Ignoring Authentication Tags: Decrypting GCM ciphertext without verifying the authentication tag removes tampering protection.
# DANGEROUS: Not verifying auth tag
def insecure_decrypt(ciphertext, key, iv, auth_tag)
cipher = OpenSSL::Cipher.new('aes-256-gcm')
cipher.decrypt
cipher.key = key
cipher.iv = iv
# auth_tag not set - no integrity verification!
cipher.update(ciphertext) + cipher.final
end
# CORRECT: Always verify auth tag
def secure_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 # Set tag before decryption
cipher.update(ciphertext) + cipher.final
rescue OpenSSL::Cipher::CipherError
raise 'Authentication failed'
end
Using Non-Cryptographic Random Generators: Ruby's rand or Random classes lack cryptographic security for key generation.
# DANGEROUS: Not cryptographically secure
weak_key = Random.new.bytes(32)
weak_token = rand(1_000_000).to_s
# CORRECT: Use SecureRandom
strong_key = SecureRandom.random_bytes(32)
strong_token = SecureRandom.urlsafe_base64(32)
Hardcoded Cryptographic Keys: Embedding keys in source code exposes them permanently.
# DANGEROUS: Hardcoded key
ENCRYPTION_KEY = "hardcoded_secret_key_never_do_this"
# CORRECT: Load from secure configuration
encryption_key = ENV['ENCRYPTION_KEY'] ||
raise('ENCRYPTION_KEY environment variable not set')
# Even better: Use key management service
encryption_key = KeyManagementService.retrieve_key('app-encryption-key')
Variable-Time String Comparison: Using standard equality operators for MAC or token comparison leaks timing information.
# VULNERABLE: Early exit leaks timing
def timing_vulnerable(expected, provided)
expected == provided # Built-in comparison may short-circuit
end
# SECURE: Constant-time comparison
def timing_safe(expected, provided)
return false unless expected.bytesize == provided.bytesize
result = 0
expected.bytes.zip(provided.bytes).each do |a, b|
result |= a ^ b
end
result == 0
end
Disabling Certificate Verification: Accepting invalid certificates defeats TLS security.
# DANGEROUS: Disables all verification
http.verify_mode = OpenSSL::SSL::VERIFY_NONE # Never do this
# CORRECT: Always verify certificates
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.ca_file = '/path/to/ca-bundle.crt' # Or use system default
Incorrect Salt Usage: Reusing the same salt for multiple passwords or not storing the salt defeats its purpose.
# WRONG: Same salt for all passwords
GLOBAL_SALT = "same_for_everyone" # Allows precomputed attacks
# CORRECT: Unique salt per password
salt = SecureRandom.random_bytes(16)
password_hash = derive_key(password, salt)
# Store both salt and hash - salt doesn't need secrecy, only uniqueness
Encrypting Without Integrity Protection: Using encryption alone without MAC verification allows tampering attacks.
# VULNERABLE: No integrity protection
cipher = OpenSSL::Cipher.new('aes-256-cbc')
cipher.encrypt
# Attacker can modify ciphertext without detection
# SECURE: Use authenticated encryption
cipher = OpenSSL::Cipher.new('aes-256-gcm')
# GCM provides both confidentiality and integrity
Tools & Ecosystem
bcrypt-ruby: Purpose-built password hashing library implementing the bcrypt algorithm with automatic salt generation and configurable work factors.
require 'bcrypt'
# Hash password
password_hash = BCrypt::Password.create('user_password', cost: 12)
# Store password_hash.to_s in database
stored_hash = password_hash.to_s # "$2a$12$..."
# Verify password
if BCrypt::Password.new(stored_hash) == user_input
# Authentication successful
end
rbnacl: Ruby binding to libsodium, providing modern cryptographic primitives including XSalsa20, Poly1305, and Ed25519.
require 'rbnacl'
# Generate key pair
signing_key = RbNaCl::SigningKey.generate
verify_key = signing_key.verify_key
# Sign message
signature = signing_key.sign('message to sign')
# Verify signature
verify_key.verify(signature, 'message to sign') # Returns message if valid
openssl gem: Standard library providing comprehensive cryptographic operations through OpenSSL bindings. Supports symmetric and asymmetric encryption, hashing, digital signatures, and TLS.
jwt gem: JSON Web Token implementation for secure token-based authentication and authorization.
require 'jwt'
payload = { user_id: 123, exp: Time.now.to_i + 3600 }
secret_key = ENV['JWT_SECRET']
# Generate token
token = JWT.encode(payload, secret_key, 'HS256')
# Verify and decode token
decoded = JWT.decode(token, secret_key, true, algorithm: 'HS256')
# => [{"user_id"=>123, "exp"=>...}, {"alg"=>"HS256"}]
attr_encrypted: Transparently encrypts and decrypts model attributes in Rails applications.
class User < ApplicationRecord
attr_encrypted :ssn, key: ENV['ENCRYPTION_KEY']
attr_encrypted :credit_card, key: ENV['ENCRYPTION_KEY']
end
# Usage appears normal but data stored encrypted
user = User.create(ssn: '123-45-6789')
# Database contains encrypted value
# Access returns decrypted value
user.ssn # => "123-45-6789"
lockbox: Modern encryption library for database fields, files, and background jobs, supporting key rotation and deterministic encryption for queryable encrypted fields.
aws-sdk-kms: AWS Key Management Service client for managing encryption keys in the cloud with hardware security module backing.
vault-ruby: HashiCorp Vault client for centralized secret management, dynamic secret generation, and encryption as a service.
OpenSSL command line: System tool for testing cryptographic operations, generating keys, and examining certificates.
# Generate RSA key pair
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
# Generate self-signed certificate
openssl req -new -x509 -key private_key.pem -out certificate.pem -days 365
# Test TLS connection
openssl s_client -connect example.com:443
# Encrypt file with password
openssl enc -aes-256-cbc -salt -in file.txt -out file.enc
# Generate random bytes
openssl rand -hex 32
Reference
Recommended Algorithms
| Purpose | Algorithm | Key Size | Notes |
|---|---|---|---|
| Symmetric Encryption | AES-GCM | 256-bit | Authenticated encryption mode |
| Symmetric Encryption | ChaCha20-Poly1305 | 256-bit | Alternative to AES-GCM |
| Hashing | SHA-256 | N/A | General purpose hashing |
| Hashing | SHA-3 | N/A | Alternative to SHA-2 family |
| Password Hashing | bcrypt | N/A | Cost 12 or higher |
| Password Hashing | Argon2id | N/A | Modern memory-hard function |
| Public Key Encryption | RSA | 2048-bit minimum | 4096-bit for long-term security |
| Public Key Encryption | ECC | 256-bit curve | Smaller keys, equivalent security |
| Digital Signatures | RSA-PSS | 2048-bit minimum | Preferred over PKCS1 v1.5 |
| Digital Signatures | Ed25519 | 256-bit | Modern, fast signature scheme |
| Key Derivation | PBKDF2-HMAC-SHA256 | N/A | 100,000+ iterations |
| Key Derivation | HKDF | N/A | For deriving multiple keys |
| Message Authentication | HMAC-SHA256 | 256-bit key | Standard MAC construction |
Deprecated Algorithms
| Algorithm | Status | Replacement |
|---|---|---|
| DES | Broken | AES |
| 3DES | Deprecated | AES |
| RC4 | Broken | AES or ChaCha20 |
| MD5 | Broken | SHA-256 |
| SHA-1 | Deprecated | SHA-256 |
| RSA < 2048-bit | Weak | RSA 2048-bit minimum |
| ECB mode | Insecure | GCM or CBC with MAC |
Ruby Cipher Modes
| Mode | Security | Performance | Use Case |
|---|---|---|---|
| GCM | High | Fast | Authenticated encryption, recommended |
| CTR | Medium | Fast | Requires separate MAC, streaming data |
| CBC | Medium | Moderate | Requires separate MAC, legacy compatibility |
| ECB | None | Fast | Never use, leaks patterns |
| CFB | Medium | Moderate | Streaming, requires separate MAC |
| OFB | Medium | Moderate | Streaming, requires separate MAC |
Key Size Recommendations
| Security Level | Symmetric | RSA | ECC | Hash |
|---|---|---|---|---|
| 112-bit | 3DES (deprecated) | 2048-bit | 224-bit | SHA-224 |
| 128-bit | AES-128 | 3072-bit | 256-bit | SHA-256 |
| 192-bit | AES-192 | 7680-bit | 384-bit | SHA-384 |
| 256-bit | AES-256 | 15360-bit | 512-bit | SHA-512 |
Common OpenSSL Cipher Names
| Ruby Name | Description |
|---|---|
| aes-256-gcm | AES 256-bit in GCM mode |
| aes-256-cbc | AES 256-bit in CBC mode |
| aes-128-gcm | AES 128-bit in GCM mode |
| chacha20-poly1305 | ChaCha20 stream cipher with Poly1305 MAC |
| des-ede3-cbc | 3DES in CBC mode (deprecated) |
Iteration Count Guidelines
| Year | PBKDF2 Iterations | bcrypt Cost |
|---|---|---|
| 2025 | 100,000 minimum | 12 minimum |
| 2030 | 200,000 estimated | 13 estimated |
| High Security | 600,000+ | 14+ |
TLS Version Support
| Version | Status | Notes |
|---|---|---|
| SSLv2 | Broken | Disable completely |
| SSLv3 | Broken | Vulnerable to POODLE |
| TLS 1.0 | Deprecated | Weak cipher suites |
| TLS 1.1 | Deprecated | Insufficient security |
| TLS 1.2 | Current | Minimum acceptable version |
| TLS 1.3 | Current | Recommended, removes weak features |
SecureRandom Methods
| Method | Output | Use Case |
|---|---|---|
| random_bytes | Binary data | Encryption keys, IVs |
| hex | Hexadecimal string | Human-readable tokens |
| base64 | Base64 string | URL-unsafe tokens |
| urlsafe_base64 | URL-safe Base64 | Session tokens, API keys |
| uuid | UUID string | Unique identifiers |
| random_number | Integer | Random numbers in range |