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 +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:
strict_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:
1) It serializes messages in JSON, period.
2) It uses non URL-safe Base64 encoding as it’s faster than its
URL-safe counterpart - as of Ruby 3.2, Base64.urlsafe_encode64 is
roughly equivalent to do Base64.strict_encode64(data).tr("-_",
"+/") - and cookie values don't need to be URL-safe.
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
# File 'lib/rack/session/encryptor.rb', line 232 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 = { pad_size: 32, purpose: nil }.update(opts) @options[:serialize_json] = true # Enforce JSON serialization @cipher_secret = secret.dup.force_encoding(Encoding::BINARY).slice!(0, 32) @cipher_secret.freeze end |
Instance Method Details
#decrypt(base64_data) ⇒ Object
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 282 283 |
# File 'lib/rack/session/encryptor.rb', line 257 def decrypt(base64_data) data = Base64.strict_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
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 |
# File 'lib/rack/session/encryptor.rb', line 285 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.strict_encode64(data) end |