How to decrypt a Rails 5 session cookie manually?

后端 未结 2 754
隐瞒了意图╮
隐瞒了意图╮ 2020-12-19 00:35

I have access to

  • config.action_dispatch.encrypted_cookie_salt
  • config.action_dispatch.encrypted_signed_cookie_salt
  • <
相关标签:
2条回答
  • 2020-12-19 00:55

    Here's a Rails 5.2 variant of @matb's answer, which handles the revised configuration, encryption and serialization:

    require 'cgi'
    require 'active_support'
    
    def verify_and_decrypt_session_cookie(cookie, secret_key_base = Rails.application.secret_key_base)
      cookie = CGI::unescape(cookie)
      salt   = 'authenticated encrypted cookie'
      encrypted_cookie_cipher = 'aes-256-gcm'
      serializer = ActiveSupport::MessageEncryptor::NullSerializer
    
      key_generator = ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000)
      key_len = ActiveSupport::MessageEncryptor.key_len(encrypted_cookie_cipher)
      secret = key_generator.generate_key(salt, key_len)
      encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: encrypted_cookie_cipher, serializer: serializer)
    
      encryptor.decrypt_and_verify(cookie)
    end
    

    Also up at https://gist.github.com/inopinatus/e523f36b468f94cf6d34410b73fef15e.

    0 讨论(0)
  • 2020-12-19 01:02

    I have had the same problem the other day and figured out that the generated secret was 64 bytes long (on my mac), but Rails ensures that the key is 32 bytes long (source).

    This has worked for me:

    require 'cgi'
    require 'json'
    require 'active_support'
    
    def verify_and_decrypt_session_cookie(cookie, secret_key_base)
    
    
    
    cookie = CGI::unescape(cookie)
      salt         = 'encrypted cookie'
      signed_salt  = 'signed encrypted cookie'
      key_generator = ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000)
      secret = key_generator.generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len]
      sign_secret = key_generator.generate_key(signed_salt)
      encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON)
    
      encryptor.decrypt_and_verify(cookie)
    end
    

    Or without ActiveSupport:

    require 'openssl'
    require 'base64'
    require 'cgi'
    require 'json'
    
    def verify_and_decrypt_session_cookie(cookie, secret_key_base)
      cookie = CGI.unescape(cookie)
    
      #################
      # generate keys #
      #################
      encrypted_cookie_salt = 'encrypted cookie' # default: Rails.application.config.action_dispatch.encrypted_cookie_salt
      encrypted_signed_cookie_salt = 'signed encrypted cookie' # default: Rails.application.config.action_dispatch.encrypted_signed_cookie_salt
      iterations = 1000
      key_size = 64
      secret = OpenSSL::PKCS5.pbkdf2_hmac_sha1(secret_key_base, encrypted_cookie_salt, iterations, key_size)[0, OpenSSL::Cipher.new('aes-256-cbc').key_len]
      sign_secret = OpenSSL::PKCS5.pbkdf2_hmac_sha1(secret_key_base, encrypted_signed_cookie_salt, iterations, key_size)
    
      ##########
      # Verify #
      ##########
      data, digest = cookie.split('--')
      raise 'invalid message' unless digest == OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, sign_secret, data)
      # you better use secure compare instead of `==` to prevent time based attact,
      # ref: ActiveSupport::SecurityUtils.secure_compare
    
      ###########
      # Decrypt #
      ###########
      encrypted_message = Base64.strict_decode64(data)
      encrypted_data, iv = encrypted_message.split('--').map{|v| Base64.strict_decode64(v) }
      cipher = OpenSSL::Cipher.new('aes-256-cbc')
      cipher.decrypt
      cipher.key = secret
      cipher.iv  = iv
      decrypted_data = cipher.update(encrypted_data)
      decrypted_data << cipher.final
    
      JSON.load(decrypted_data)
    end
    

    Feel free to comment on the gist: https://gist.github.com/mbyczkowski/34fb691b4d7a100c32148705f244d028

    0 讨论(0)
提交回复
热议问题