class Rack::Session::Encryptor::V1
Public Class Methods
Source
# File lib/rack/session/encryptor.rb, line 89 def initialize(secret, opts = {}) raise ArgumentError, 'secret must be a String' unless secret.is_a?(String) raise ArgumentError, "invalid secret: #{secret.bytesize}, must be >=64" unless secret.bytesize >= 64 case opts[:pad_size] when nil # padding is disabled when Integer raise ArgumentError, "invalid pad_size: #{opts[:pad_size]}" unless (2..4096).include? opts[:pad_size] else raise ArgumentError, "invalid pad_size: #{opts[:pad_size]}; must be Integer or nil" end @options = { serialize_json: false, pad_size: 32, purpose: nil }.update(opts) @hmac_secret = secret.dup.force_encoding(Encoding::BINARY) @cipher_secret = @hmac_secret.slice!(0, 32) @hmac_secret.freeze @cipher_secret.freeze end
The secret String must be at least 64 bytes in size. The first 32 bytes will be used for the encryption cipher key. The remainder will be used for an HMAC key.
Options may include:
-
:serialize_json
Use JSON for message serialization instead of Marshal. This can be viewed as a security enhancement.
-
:pad_size
Pad encrypted message data, to a multiple of this many bytes (default: 32). This can be between 2-4096 bytes, or +nil+ to disable padding.
-
:purpose
Limit messages to a specific purpose. This can be viewed as a security enhancement to prevent message reuse from different contexts if keys are reused.
Cryptography and Output Format:
urlsafe_encode64(version + random_data + IV + encrypted data + HMAC) Where: * version - 1 byte with value 0x01 * random_data - 32 bytes used for generating the per-message secret * IV - 16 bytes random initialization vector * HMAC - 32 bytes HMAC-SHA-256 of all preceding data, plus the purpose value
Public Instance Methods
Source
# File lib/rack/session/encryptor.rb, line 113 def decrypt(base64_data) data = Base64.urlsafe_decode64(base64_data) signature = data.slice!(-32..-1) verify_authenticity!(data, signature) version = data.slice!(0, 1) raise InvalidMessage, 'wrong version' unless version == "\1" message_secret = data.slice!(0, 32) cipher_iv = data.slice!(0, 16) cipher = new_cipher cipher.decrypt set_cipher_key(cipher, cipher_secret_from_message_secret(message_secret)) cipher.iv = cipher_iv data = cipher.update(data) << cipher.final deserialized_message data rescue ArgumentError raise InvalidSignature, 'Message invalid' end
Source
# File lib/rack/session/encryptor.rb, line 138 def encrypt(message) version = "\1" serialized_payload = serialize_payload(message) message_secret, cipher_secret = new_message_and_cipher_secret cipher = new_cipher cipher.encrypt set_cipher_key(cipher, cipher_secret) cipher_iv = cipher.random_iv encrypted_data = cipher.update(serialized_payload) << cipher.final data = String.new data << version data << message_secret data << cipher_iv data << encrypted_data data << compute_signature(data) Base64.urlsafe_encode64(data) end
Private Instance Methods
Source
# File lib/rack/session/encryptor.rb, line 175 def cipher_secret_from_message_secret(message_secret) OpenSSL::HMAC.digest(OpenSSL::Digest.new('SHA256'), @cipher_secret, message_secret) end
Source
# File lib/rack/session/encryptor.rb, line 183 def compute_signature(data) signing_data = data signing_data += @options[:purpose] if @options[:purpose] OpenSSL::HMAC.digest(OpenSSL::Digest.new('SHA256'), @hmac_secret, signing_data) end
Source
# File lib/rack/session/encryptor.rb, line 165 def new_cipher OpenSSL::Cipher.new('aes-256-ctr') end
Source
# File lib/rack/session/encryptor.rb, line 169 def new_message_and_cipher_secret message_secret = SecureRandom.random_bytes(32) [message_secret, cipher_secret_from_message_secret(message_secret)] end
Source
# File lib/rack/session/encryptor.rb, line 179 def set_cipher_key(cipher, key) cipher.key = key end
Source
# File lib/rack/session/encryptor.rb, line 190 def verify_authenticity!(data, signature) raise InvalidMessage, 'Message is invalid' if data.nil? || signature.nil? unless Rack::Utils.secure_compare(signature, compute_signature(data)) raise InvalidSignature, 'HMAC is invalid' end end