Overview
Hash-based Message Authentication Code (HMAC) provides cryptographic authentication for messages using a secret key and a hash function. Ruby implements HMAC through the OpenSSL::HMAC
class in the standard library, supporting various hash algorithms including SHA-256, SHA-512, and MD5.
The HMAC algorithm combines a secret key with message data through a specific construction that prevents length extension attacks and provides strong authentication guarantees. Ruby's implementation wraps OpenSSL's HMAC functionality, providing both one-shot computation methods and streaming interfaces for large datasets.
HMAC authentication appears frequently in API authentication, webhook verification, session token generation, and secure communication protocols. The algorithm's security depends on the hash function strength and key secrecy, making proper key management essential.
require 'openssl'
# Basic HMAC computation
key = 'secret_key'
message = 'Hello, World!'
hmac = OpenSSL::HMAC.hexdigest('SHA256', key, message)
# => "4f8b42c22dd3729b519ba6f68d2da7cc5b2d606d05daed5ad5128cc03e6c6358"
Ruby provides multiple interfaces for HMAC computation: class methods for simple operations, instance methods for complex scenarios, and streaming methods for large data processing. The digest output format includes both binary and hexadecimal representations.
# Binary digest output
binary_hmac = OpenSSL::HMAC.digest('SHA256', key, message)
# => "\x4F\x8B\x42\xC2..."
# Instance-based computation
hmac_instance = OpenSSL::HMAC.new(key, 'SHA256')
hmac_instance.update(message)
result = hmac_instance.hexdigest
# => "4f8b42c22dd3729b519ba6f68d2da7cc5b2d606d05daed5ad5128cc03e6c6358"
The OpenSSL::HMAC class supports all hash algorithms available in the underlying OpenSSL installation. Common algorithms include SHA-1, SHA-224, SHA-256, SHA-384, SHA-512, and MD5, though SHA-256 or stronger algorithms are recommended for security applications.
Basic Usage
The OpenSSL::HMAC
class provides both class methods for immediate computation and instance methods for incremental processing. Class methods handle simple scenarios where the entire message is available, while instance methods support streaming operations and complex message construction.
The primary class method OpenSSL::HMAC.hexdigest
computes HMAC values directly from key, algorithm, and message parameters. This method returns hexadecimal-encoded strings suitable for storage, comparison, and transmission.
require 'openssl'
# Standard HMAC computation with different algorithms
key = 'my_secret_key'
message = 'authenticate this message'
sha256_hmac = OpenSSL::HMAC.hexdigest('SHA256', key, message)
sha512_hmac = OpenSSL::HMAC.hexdigest('SHA512', key, message)
md5_hmac = OpenSSL::HMAC.hexdigest('MD5', key, message)
puts sha256_hmac
# => "b8f5b4c0eab0b3c6a2d5e4f3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3"
For binary output requirements, OpenSSL::HMAC.digest
returns raw bytes instead of hexadecimal strings. Binary output provides space efficiency and direct compatibility with protocols expecting binary HMAC values.
# Binary HMAC output
binary_hmac = OpenSSL::HMAC.digest('SHA256', key, message)
puts binary_hmac.length
# => 32 (SHA-256 produces 32-byte output)
# Convert binary to base64 for transport
base64_hmac = [binary_hmac].pack('m0')
# => "uPW0wOqws8ai1eT...="
Instance methods provide incremental HMAC computation for scenarios involving large messages, streaming data, or complex message construction. The instance approach allows building messages piece by piece while maintaining authentication state.
# Incremental HMAC computation
hmac = OpenSSL::HMAC.new(key, 'SHA256')
hmac.update('Part one: ')
hmac.update('Part two: ')
hmac.update('Final part.')
final_digest = hmac.hexdigest
# => "c5d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3"
String encoding affects HMAC computation, particularly when processing international text or binary data. Ruby converts strings to bytes using their current encoding, making encoding consistency important for reproducible results.
# Encoding affects HMAC results
utf8_message = 'café'.encode('UTF-8')
latin1_message = 'café'.encode('ISO-8859-1')
utf8_hmac = OpenSSL::HMAC.hexdigest('SHA256', key, utf8_message)
latin1_hmac = OpenSSL::HMAC.hexdigest('SHA256', key, latin1_message)
puts utf8_hmac == latin1_hmac
# => false (different byte representations produce different HMACs)
Key material can include any binary data, though security considerations apply to key selection and management. Keys shorter than the hash output length get padded internally, while longer keys get hashed to the appropriate length.
# Different key types and lengths
short_key = 'key'
long_key = 'very_long_key_material_that_exceeds_block_size' * 10
binary_key = Random.bytes(32)
short_hmac = OpenSSL::HMAC.hexdigest('SHA256', short_key, message)
long_hmac = OpenSSL::HMAC.hexdigest('SHA256', long_key, message)
binary_hmac = OpenSSL::HMAC.hexdigest('SHA256', binary_key, message)
# All produce valid HMACs with different security properties
Error Handling & Debugging
HMAC operations can fail due to invalid algorithms, encoding issues, or OpenSSL library problems. Ruby raises specific exception types that applications should handle appropriately to maintain security and provide meaningful error messages.
The most common error occurs when specifying unsupported digest algorithms. OpenSSL installations may not include all algorithms, and algorithm names are case-sensitive. Ruby raises RuntimeError
exceptions when encountering unknown digest types.
require 'openssl'
key = 'test_key'
message = 'test message'
begin
# Invalid algorithm name
hmac = OpenSSL::HMAC.hexdigest('INVALID_ALGO', key, message)
rescue RuntimeError => e
puts "Algorithm error: #{e.message}"
# => "Algorithm error: Unsupported digest algorithm (INVALID_ALGO)"
end
begin
# Case-sensitive algorithm names
hmac = OpenSSL::HMAC.hexdigest('sha256', key, message) # lowercase
rescue RuntimeError => e
puts "Case sensitivity error: #{e.message}"
# Use 'SHA256' instead
end
String encoding problems manifest as inconsistent HMAC values across different environments or unexpected authentication failures. These issues often appear in applications processing user input, file data, or network protocols with varying encoding assumptions.
# Debugging encoding-related HMAC inconsistencies
def debug_hmac_encoding(key, message)
puts "Message encoding: #{message.encoding}"
puts "Message bytes: #{message.bytes.inspect}"
# Force UTF-8 encoding for consistency
normalized_message = message.encode('UTF-8')
hmac = OpenSSL::HMAC.hexdigest('SHA256', key, normalized_message)
puts "HMAC: #{hmac}"
hmac
rescue Encoding::UndefinedConversionError => e
puts "Encoding conversion failed: #{e.message}"
# Handle binary data or invalid characters
binary_hmac = OpenSSL::HMAC.hexdigest('SHA256', key, message.force_encoding('BINARY'))
puts "Binary HMAC: #{binary_hmac}"
binary_hmac
end
# Test with problematic input
problematic_input = "caf\xE9".force_encoding('ASCII-8BIT')
debug_hmac_encoding('key', problematic_input)
Nil or empty values require careful handling to prevent security vulnerabilities and application crashes. HMAC computation with nil keys or messages produces different behaviors depending on Ruby's type conversion rules.
# Handling nil and empty values safely
def safe_hmac(algorithm, key, message)
# Validate inputs before computation
raise ArgumentError, "Key cannot be nil" if key.nil?
raise ArgumentError, "Message cannot be nil" if message.nil?
raise ArgumentError, "Algorithm cannot be empty" if algorithm.to_s.empty?
# Convert non-string keys to strings with explicit encoding
key = key.to_s.encode('UTF-8') unless key.is_a?(String)
message = message.to_s.encode('UTF-8') unless message.is_a?(String)
OpenSSL::HMAC.hexdigest(algorithm, key, message)
rescue => e
# Log error details for debugging
puts "HMAC computation failed: #{e.class}: #{e.message}"
puts "Algorithm: #{algorithm.inspect}"
puts "Key length: #{key&.length || 'nil'}"
puts "Message length: #{message&.length || 'nil'}"
raise
end
# Test error handling
begin
safe_hmac('SHA256', nil, 'message')
rescue ArgumentError => e
puts e.message
# => "Key cannot be nil"
end
Instance method errors often occur during incremental updates, particularly when calling digest methods multiple times or updating after finalization. Ruby's HMAC instances become unusable after calling digest methods, requiring fresh instances for additional computations.
# Instance lifecycle error handling
def demonstrate_instance_errors
key = 'test_key'
hmac = OpenSSL::HMAC.new(key, 'SHA256')
hmac.update('first part')
first_digest = hmac.hexdigest
begin
# Attempting to use instance after finalization
hmac.update('second part')
second_digest = hmac.hexdigest
rescue RuntimeError => e
puts "Instance reuse error: #{e.message}"
# Create new instance for additional computations
new_hmac = OpenSSL::HMAC.new(key, 'SHA256')
new_hmac.update('first part')
new_hmac.update('second part')
second_digest = new_hmac.hexdigest
end
[first_digest, second_digest]
end
Memory and resource constraints can affect HMAC operations with very large messages or high-frequency computations. Monitor memory usage and implement appropriate resource management for production applications.
# Resource monitoring for large HMAC operations
def monitor_hmac_resources(key, large_data_source)
start_memory = `ps -o rss= -p #{Process.pid}`.to_i
start_time = Time.now
hmac = OpenSSL::HMAC.new(key, 'SHA256')
large_data_source.each_chunk do |chunk|
hmac.update(chunk)
# Monitor memory growth
current_memory = `ps -o rss= -p #{Process.pid}`.to_i
if current_memory > start_memory * 2
puts "Memory usage doubled: #{current_memory - start_memory} KB growth"
end
end
result = hmac.hexdigest
end_time = Time.now
puts "HMAC computation completed in #{end_time - start_time} seconds"
result
end
Production Patterns
Production HMAC usage requires careful consideration of security, performance, and integration patterns. Applications typically use HMAC for API authentication, webhook verification, session management, and secure token generation, each with specific implementation requirements.
API authentication commonly employs HMAC signatures to verify request integrity and authenticate clients. This pattern involves signing request components including method, URL, headers, and body content to prevent tampering and replay attacks.
class APIAuthenticator
def initialize(secret_key)
@secret_key = secret_key
end
def sign_request(method, url, headers, body)
# Construct canonical request string
canonical_headers = headers.sort.map { |k, v| "#{k.downcase}:#{v}" }.join("\n")
string_to_sign = [
method.upcase,
url,
canonical_headers,
Digest::SHA256.hexdigest(body || '')
].join("\n")
# Generate HMAC signature
signature = OpenSSL::HMAC.hexdigest('SHA256', @secret_key, string_to_sign)
# Return authorization header value
"HMAC-SHA256 Credential=#{@secret_key[0..7]}, Signature=#{signature}"
end
def verify_request(method, url, headers, body, provided_signature)
expected_signature = sign_request(method, url, headers, body)
secure_compare(expected_signature, provided_signature)
end
private
# Constant-time comparison to prevent timing attacks
def secure_compare(a, b)
return false unless a.length == b.length
result = 0
a.bytes.zip(b.bytes) { |x, y| result |= x ^ y }
result == 0
end
end
# Usage in Rails controller
authenticator = APIAuthenticator.new(ENV['API_SECRET'])
auth_header = request.headers['Authorization']
signature = auth_header&.match(/Signature=([a-f0-9]+)/)&.captures&.first
if authenticator.verify_request(request.method, request.url, request.headers, request.raw_post, signature)
# Process authenticated request
else
render json: { error: 'Invalid signature' }, status: 401
end
Webhook verification protects applications from forged webhook payloads by validating signatures provided by webhook services. Different services use varying signature formats and algorithms, requiring flexible verification implementations.
class WebhookVerifier
GITHUB_SIGNATURE_PREFIX = 'sha256='
STRIPE_SIGNATURE_PREFIX = 't='
def self.verify_github(payload, signature, secret)
expected_signature = GITHUB_SIGNATURE_PREFIX +
OpenSSL::HMAC.hexdigest('SHA256', secret, payload)
secure_compare(signature, expected_signature)
end
def self.verify_stripe(payload, signature_header, secret)
# Parse Stripe's signature format: t=timestamp,v1=signature
elements = signature_header.split(',').map { |pair| pair.split('=', 2) }.to_h
timestamp = elements['t']
signature = elements['v1']
return false unless timestamp && signature
# Stripe signs: timestamp.payload
signed_payload = "#{timestamp}.#{payload}"
expected_signature = OpenSSL::HMAC.hexdigest('SHA256', secret, signed_payload)
secure_compare(signature, expected_signature)
end
def self.verify_generic(payload, signature, secret, algorithm = 'SHA256')
expected_signature = OpenSSL::HMAC.hexdigest(algorithm, secret, payload)
secure_compare(signature, expected_signature)
end
private
def self.secure_compare(a, b)
Rack::Utils.secure_compare(a, b) if defined?(Rack::Utils)
# Fallback implementation for environments without Rack
return false unless a.bytesize == b.bytesize
OpenSSL.fixed_length_secure_compare(a, b)
end
end
# Rails webhook controller
class WebhooksController < ApplicationController
skip_before_action :verify_authenticity_token
def github
signature = request.headers['X-Hub-Signature-256']
payload = request.raw_post
if WebhookVerifier.verify_github(payload, signature, ENV['GITHUB_WEBHOOK_SECRET'])
process_github_webhook(JSON.parse(payload))
else
head :unauthorized
end
end
def stripe
signature = request.headers['Stripe-Signature']
payload = request.raw_post
if WebhookVerifier.verify_stripe(payload, signature, ENV['STRIPE_WEBHOOK_SECRET'])
process_stripe_webhook(JSON.parse(payload))
else
head :unauthorized
end
end
end
Session token generation uses HMAC to create tamper-evident tokens containing user data, expiration times, and security flags. This pattern enables stateless session management while maintaining security guarantees.
class SecureTokenManager
TOKEN_SEPARATOR = '.'
def initialize(secret_key, algorithm = 'SHA256')
@secret_key = secret_key
@algorithm = algorithm
end
def generate_token(payload, expires_at = nil)
# Add timestamp and expiration
token_data = {
payload: payload,
issued_at: Time.now.to_i,
expires_at: expires_at&.to_i
}
# Encode payload as base64
encoded_payload = Base64.urlsafe_encode64(JSON.generate(token_data))
# Generate HMAC signature
signature = OpenSSL::HMAC.hexdigest(@algorithm, @secret_key, encoded_payload)
# Combine payload and signature
"#{encoded_payload}#{TOKEN_SEPARATOR}#{signature}"
end
def verify_token(token)
parts = token.split(TOKEN_SEPARATOR, 2)
return nil unless parts.length == 2
encoded_payload, provided_signature = parts
# Verify signature
expected_signature = OpenSSL::HMAC.hexdigest(@algorithm, @secret_key, encoded_payload)
return nil unless secure_compare(provided_signature, expected_signature)
# Decode and parse payload
begin
decoded_payload = Base64.urlsafe_decode64(encoded_payload)
token_data = JSON.parse(decoded_payload, symbolize_names: true)
rescue JSON::ParserError, ArgumentError
return nil
end
# Check expiration
if token_data[:expires_at] && Time.now.to_i > token_data[:expires_at]
return nil
end
token_data[:payload]
end
private
def secure_compare(a, b)
return false unless a.length == b.length
result = 0
a.bytes.zip(b.bytes) { |x, y| result |= x ^ y }
result == 0
end
end
# Usage in application
token_manager = SecureTokenManager.new(ENV['SESSION_SECRET'])
# Generate token with 1 hour expiration
user_token = token_manager.generate_token(
{ user_id: 123, role: 'admin' },
Time.now + 3600
)
# Verify token in middleware
user_data = token_manager.verify_token(user_token)
if user_data
# Token valid, proceed with user data
puts "Authenticated user: #{user_data[:user_id]}"
else
# Token invalid or expired
puts "Authentication failed"
end
Common Pitfalls
HMAC implementations often contain subtle security vulnerabilities and logic errors that compromise authentication effectiveness. These issues typically involve timing attacks, weak key management, encoding problems, or incorrect signature verification logic.
Timing attacks exploit variations in comparison time to gradually discover valid signatures. Standard string comparison methods return immediately upon finding the first different character, leaking information about signature correctness through execution time measurement.
# VULNERABLE: Timing attack susceptible comparison
def vulnerable_verify(expected_hmac, provided_hmac)
expected_hmac == provided_hmac # Returns immediately on first difference
end
# SECURE: Constant-time comparison
def secure_verify(expected_hmac, provided_hmac)
return false unless expected_hmac.length == provided_hmac.length
result = 0
expected_hmac.bytes.zip(provided_hmac.bytes) do |a, b|
result |= a ^ b
end
result == 0
end
# Demonstration of timing difference
require 'benchmark'
correct_hmac = 'a' * 64
almost_correct = 'b' + 'a' * 63 # First character wrong
completely_wrong = 'b' * 64 # All characters wrong
# Time vulnerable comparison
time_almost = Benchmark.realtime { 1000.times { vulnerable_verify(correct_hmac, almost_correct) } }
time_completely = Benchmark.realtime { 1000.times { vulnerable_verify(correct_hmac, completely_wrong) } }
puts "Almost correct: #{time_almost}s"
puts "Completely wrong: #{time_completely}s"
# Shows timing difference revealing signature structure
Key management errors include using weak keys, hardcoded secrets, or predictable key generation. Applications must use cryptographically strong random keys and implement secure key rotation procedures.
# BAD: Weak and predictable keys
weak_key = 'password123'
hardcoded_key = 'my_secret_key_do_not_change'
predictable_key = "#{Time.now.to_i}" # Timestamp-based
# GOOD: Strong random key generation
require 'securerandom'
strong_key = SecureRandom.hex(32) # 64-character hex string
binary_key = SecureRandom.bytes(32) # 32 random bytes
uuid_key = SecureRandom.uuid # UUID format
# Key rotation implementation
class RotatingKeyManager
def initialize(current_key, previous_key = nil)
@current_key = current_key
@previous_key = previous_key
end
def sign(message)
OpenSSL::HMAC.hexdigest('SHA256', @current_key, message)
end
def verify(message, signature)
# Try current key first
current_signature = OpenSSL::HMAC.hexdigest('SHA256', @current_key, message)
return true if secure_compare(signature, current_signature)
# Fall back to previous key during rotation period
if @previous_key
previous_signature = OpenSSL::HMAC.hexdigest('SHA256', @previous_key, message)
return true if secure_compare(signature, previous_signature)
end
false
end
def rotate_key(new_key)
@previous_key = @current_key
@current_key = new_key
end
private
def secure_compare(a, b)
return false unless a.length == b.length
result = 0
a.bytes.zip(b.bytes) { |x, y| result |= x ^ y }
result == 0
end
end
Encoding inconsistencies create authentication failures when different parts of an application handle string encoding differently. This problem frequently occurs in applications processing international text, binary data, or data from multiple sources.
# Problematic encoding handling
def problematic_hmac_generation
user_input = "café" # UTF-8 encoded
file_data = File.read('binary_file.dat', encoding: 'ASCII-8BIT')
# These produce different HMACs due to encoding differences
hmac1 = OpenSSL::HMAC.hexdigest('SHA256', 'key', user_input)
hmac2 = OpenSSL::HMAC.hexdigest('SHA256', 'key', user_input.encode('ISO-8859-1'))
puts hmac1 == hmac2 # => false
end
# Consistent encoding approach
class EncodingConsistentHMAC
def initialize(key, algorithm = 'SHA256', encoding = 'UTF-8')
@key = key.encode(encoding)
@algorithm = algorithm
@encoding = encoding
end
def compute(message)
normalized_message = case message.encoding
when Encoding::ASCII_8BIT
# Handle binary data without conversion
message
else
# Convert text to consistent encoding
message.encode(@encoding)
end
OpenSSL::HMAC.hexdigest(@algorithm, @key, normalized_message)
rescue Encoding::UndefinedConversionError
# Fall back to binary handling for problematic characters
binary_message = message.force_encoding('ASCII-8BIT')
OpenSSL::HMAC.hexdigest(@algorithm, @key, binary_message)
end
end
# Usage with consistent encoding
hmac_generator = EncodingConsistentHMAC.new('secret_key')
result1 = hmac_generator.compute("café")
result2 = hmac_generator.compute("café".encode('UTF-8'))
puts result1 == result2 # => true
Replay attack vulnerabilities occur when applications accept the same HMAC signature multiple times without implementing proper nonce or timestamp validation. Attackers can capture and replay valid requests to perform unauthorized actions.
# VULNERABLE: No replay protection
class VulnerableAPIEndpoint
def process_request(payload, signature)
expected_signature = OpenSSL::HMAC.hexdigest('SHA256', secret_key, payload)
if secure_compare(signature, expected_signature)
# Process request - vulnerable to replay attacks
execute_action(payload)
else
reject_request
end
end
end
# SECURE: Replay protection with timestamps and nonces
class ReplayProtectedEndpoint
TIMESTAMP_TOLERANCE = 300 # 5 minutes
def initialize
@used_nonces = Set.new
@nonce_timestamps = {}
cleanup_old_nonces
end
def process_request(payload, signature, timestamp, nonce)
# Validate timestamp freshness
current_time = Time.now.to_i
request_time = timestamp.to_i
if (current_time - request_time).abs > TIMESTAMP_TOLERANCE
return reject_request("Request too old or from future")
end
# Check nonce uniqueness
if @used_nonces.include?(nonce)
return reject_request("Nonce already used")
end
# Construct signed message including timestamp and nonce
signed_data = "#{timestamp}:#{nonce}:#{payload}"
expected_signature = OpenSSL::HMAC.hexdigest('SHA256', secret_key, signed_data)
if secure_compare(signature, expected_signature)
@used_nonces.add(nonce)
@nonce_timestamps[nonce] = current_time
execute_action(payload)
else
reject_request("Invalid signature")
end
end
private
def cleanup_old_nonces
# Periodically remove old nonces to prevent memory growth
Thread.new do
loop do
sleep 300 # Clean up every 5 minutes
cutoff_time = Time.now.to_i - TIMESTAMP_TOLERANCE
@nonce_timestamps.each do |nonce, timestamp|
if timestamp < cutoff_time
@used_nonces.delete(nonce)
@nonce_timestamps.delete(nonce)
end
end
end
end
end
def secure_compare(a, b)
return false unless a.length == b.length
result = 0
a.bytes.zip(b.bytes) { |x, y| result |= x ^ y }
result == 0
end
end
Algorithm downgrade attacks exploit applications that accept multiple HMAC algorithms without proper validation. Attackers can force the use of weaker algorithms or exploit implementation differences between algorithms.
# VULNERABLE: Accepts any algorithm from user input
def vulnerable_verify(algorithm, key, message, signature)
expected = OpenSSL::HMAC.hexdigest(algorithm, key, message)
expected == signature # Also vulnerable to timing attacks
end
# SECURE: Algorithm whitelist and validation
class SecureHMACValidator
ALLOWED_ALGORITHMS = %w[SHA256 SHA384 SHA512].freeze
MINIMUM_KEY_LENGTH = 32
def initialize(allowed_algorithms = ALLOWED_ALGORITHMS)
@allowed_algorithms = allowed_algorithms
end
def validate_and_compute(algorithm, key, message)
# Validate algorithm
unless @allowed_algorithms.include?(algorithm)
raise ArgumentError, "Algorithm #{algorithm} not allowed. Use: #{@allowed_algorithms.join(', ')}"
end
# Validate key strength
if key.length < MINIMUM_KEY_LENGTH
raise ArgumentError, "Key too short. Minimum length: #{MINIMUM_KEY_LENGTH} characters"
end
# Compute HMAC with validated inputs
OpenSSL::HMAC.hexdigest(algorithm, key, message)
rescue RuntimeError => e
raise ArgumentError, "Invalid algorithm or OpenSSL error: #{e.message}"
end
def verify(algorithm, key, message, provided_signature)
expected_signature = validate_and_compute(algorithm, key, message)
secure_compare(expected_signature, provided_signature)
end
private
def secure_compare(a, b)
return false unless a.length == b.length
result = 0
a.bytes.zip(b.bytes) { |x, y| result |= x ^ y }
result == 0
end
end
# Usage with algorithm restrictions
validator = SecureHMACValidator.new(['SHA256', 'SHA512'])
begin
# This will be rejected
validator.verify('MD5', strong_key, message, signature)
rescue ArgumentError => e
puts "Algorithm rejected: #{e.message}"
# => "Algorithm MD5 not allowed. Use: SHA256, SHA512"
end
Reference
Class Methods
Method | Parameters | Returns | Description |
---|---|---|---|
OpenSSL::HMAC.hexdigest(algorithm, key, data) |
algorithm (String), key (String), data (String) |
String |
Computes HMAC and returns hexadecimal string |
OpenSSL::HMAC.digest(algorithm, key, data) |
algorithm (String), key (String), data (String) |
String |
Computes HMAC and returns binary string |
OpenSSL::HMAC.base64digest(algorithm, key, data) |
algorithm (String), key (String), data (String) |
String |
Computes HMAC and returns Base64-encoded string |
Instance Methods
Method | Parameters | Returns | Description |
---|---|---|---|
OpenSSL::HMAC.new(key, algorithm) |
key (String), algorithm (String) |
OpenSSL::HMAC |
Creates new HMAC instance |
#update(data) |
data (String) |
self |
Adds data to HMAC computation |
#<<(data) |
data (String) |
self |
Alias for update method |
#hexdigest |
None | String |
Returns hexadecimal digest and finalizes |
#digest |
None | String |
Returns binary digest and finalizes |
#base64digest |
None | String |
Returns Base64 digest and finalizes |
#reset |
None | self |
Resets HMAC state to initial condition |
Supported Hash Algorithms
Algorithm | Output Size (bytes) | Security Status | Recommended Use |
---|---|---|---|
SHA256 |
32 | Strong | General purpose, API authentication |
SHA384 |
48 | Strong | High security applications |
SHA512 |
64 | Strong | Maximum security requirements |
SHA224 |
28 | Adequate | Legacy compatibility |
SHA1 |
20 | Weak | Deprecated, avoid for new applications |
MD5 |
16 | Broken | Legacy only, not recommended |
Common Digest Output Formats
Format | Method | Example Output | Use Case |
---|---|---|---|
Hexadecimal | hexdigest |
a1b2c3d4e5f6... |
Web APIs, debugging, storage |
Binary | digest |
\xA1\xB2\xC3\xD4... |
Network protocols, space efficiency |
Base64 | base64digest |
obLD1OX2... |
HTTP headers, JSON payloads |
Error Types and Causes
Exception | Common Causes | Prevention Strategy |
---|---|---|
RuntimeError |
Invalid algorithm name, OpenSSL errors | Validate algorithms against whitelist |
ArgumentError |
Nil parameters, wrong types | Input validation and type checking |
Encoding::UndefinedConversionError |
Incompatible character encodings | Normalize encoding or handle binary data |
NoMemoryError |
Extremely large data processing | Implement streaming for large datasets |
Key Length Recommendations
Algorithm | Minimum Key Length | Recommended Length | Maximum Effective Length |
---|---|---|---|
SHA256 | 32 bytes | 32 bytes | 64 bytes (block size) |
SHA384 | 48 bytes | 48 bytes | 128 bytes (block size) |
SHA512 | 64 bytes | 64 bytes | 128 bytes (block size) |
SHA1 | 20 bytes | Not recommended | 64 bytes (block size) |
Performance Characteristics
Operation Type | Performance Order | Memory Usage | Streaming Support |
---|---|---|---|
Class methods | Fastest | Low | No |
Instance methods | Moderate | Low to moderate | Yes |
Large data (>1MB) | Use streaming | Constant with streaming | Yes |
Multiple operations | Instance reuse | Higher with reuse | Yes |