Class: Rack::Session::Encryptor::V2
- Inherits:
-
Object
- Object
- Rack::Session::Encryptor::V2
- Includes:
- Serializable
- Defined in:
- lib/rack/session/encryptor.rb
Instance Method Summary collapse
- #decrypt(base64_data) ⇒ Object
- #encrypt(message) ⇒ Object
-
#initialize(secret, opts = {}) ⇒ V2
constructor
The secret String must be at least 32 bytes in size.
Constructor Details
#initialize(secret, opts = {}) ⇒ V2
The secret String must be at least 32 bytes in size.
Options may include:
- :pad_size
Pad encrypted message data, to a multiple of this many bytes
(default: 32). This can be between 2-4096 bytes, or
nilto 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 + salt + IV + authentication tag + ciphertext)
Where:
- version - 1 byte with value 0x02
- salt - 32 bytes used for generating the per-message secret
- IV - 12 bytes random initialization vector
- authentication tag - 16 bytes authentication tag generated by the GCM mode, covering version and salt
Considerations about V2:
- It uses URL-safe Base64 encoding (RFC 4648) to ensure cookie values are not corrupted by Rack's cookie parser, which applies URI.decode_www_form_component to cookie values and converts '+' to space. Standard Base64 (strict_encode64) can produce '+' characters, which would corrupt the cookie value and cause decryption to fail.
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/rack/session/encryptor.rb', line 231 def initialize(secret, opts = {}) raise ArgumentError, 'secret must be a String' unless secret.is_a?(String) unless secret.bytesize >= 32 raise ArgumentError, "invalid secret: it's #{secret.bytesize}-byte long, must be >=32" end 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) @cipher_secret = secret.dup.force_encoding(Encoding::BINARY).slice!(0, 32) @cipher_secret.freeze end |
Instance Method Details
#decrypt(base64_data) ⇒ Object
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
# File 'lib/rack/session/encryptor.rb', line 255 def decrypt(base64_data) data = Base64.urlsafe_decode64(base64_data) if data.bytesize <= 61 # version + salt + iv + auth_tag = 61 byte (and we also need some ciphertext :) raise InvalidMessage, 'invalid message' end version = data[0] raise InvalidMessage, 'invalid message' unless version == "\2" ciphertext = data.slice!(61..-1) auth_tag = data.slice!(45, 16) cipher_iv = data.slice!(33, 12) cipher = new_cipher cipher.decrypt salt = data.slice(1, 32) set_cipher_key(cipher, (salt)) cipher.iv = cipher_iv cipher.auth_tag = auth_tag cipher.auth_data = (purpose = @options[:purpose]) ? data + purpose : data plaintext = cipher.update(ciphertext) << cipher.final plaintext rescue ArgumentError, OpenSSL::Cipher::CipherError raise InvalidSignature, 'invalid message' end |
#encrypt(message) ⇒ Object
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 |
# File 'lib/rack/session/encryptor.rb', line 283 def encrypt() version = "\2" serialized_payload = serialize_payload() cipher = new_cipher cipher.encrypt salt, = set_cipher_key(cipher, ) cipher.iv_len = 12 cipher_iv = cipher.random_iv data = String.new data << version data << salt cipher.auth_data = (purpose = @options[:purpose]) ? data + purpose : data encrypted_data = cipher.update(serialized_payload) << cipher.final data << cipher_iv data << auth_tag_from(cipher) data << encrypted_data Base64.urlsafe_encode64(data) end |