Class: Rack::Session::Encryptor::V1

Inherits:
Object
  • Object
show all
Includes:
Serializable
Defined in:
lib/rack/session/encryptor.rb

Instance Method Summary collapse

Constructor Details

#initialize(secret, opts = {}) ⇒ V1

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

Raises:

  • (ArgumentError)


89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# 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

Instance Method Details

#decrypt(base64_data) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# 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

#encrypt(message) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# 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