Created
November 17, 2016 07:17
-
-
Save humphreyja/b0bba5fe13ce381880add2d44fa5a935 to your computer and use it in GitHub Desktop.
Decrypting the Rails session with Elixir
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@moduledoc """ | |
This uses the secret key base, the defined salt and signed salt from your Rails app to decrypt the Rails session. Checkout: https://github.com/cconstantin/plug_rails_cookie_session_store for a full session encrypter/decrypter for Phoenix. Below is how you would call these functions. | |
def get_session(conn) do | |
# Preload the cookies (If you are not in the browser pipeline | |
conn = Plug.Conn.fetch_cookies(conn) | |
# Get the cookies by the key defined in your Rails app 'config/initializers/session_store.rb' | |
cookie = Map.get(conn.cookies, "_yourappname_session") | |
# Get the config values for secret_key_base (same as your Rails app secret_key_base) | |
key = Application.get_env(:my_api, :secret_key_base) | |
# The salts are defined in Rails in 'config/initializers/session_store.rb', though often not used. | |
# They need to be defined so that Rails doesn't randomize the ecryption. You can do this with the following: | |
# | |
# Rails.application.config.action_dispatch.encrypted_cookie_salt = "some secure_random string" | |
# Rails.application.config.action_dispatch.encrypted_signed_cookie_salt = "another secure_random string" | |
# | |
salt = Application.get_env(:my_api, :cookie_salt) | |
signed_salt = Application.get_env(:my_api, :signed_cookie_salt) | |
# Decrypt the session and parse the JSON | |
case decrypt_session(cookie, key, signed_salt, salt) do | |
{:ok, session} -> | |
Poison.Parser.parse!(session) | |
_err -> %{} | |
end | |
end | |
""" | |
defmodule MyAPI.Encryption.MessageEncryptor do | |
alias MyAPI.Encryption.MessageVerifier | |
@doc """ | |
Decrypts and verifies a message. | |
We need to verify the message in order to avoid padding attacks. | |
Reference: http://www.limited-entropy.com/padding-oracle-attacks | |
""" | |
def verify_and_decrypt(encrypted, secret, sign_secret) when is_binary(encrypted) and is_binary(secret) and is_binary(sign_secret) do | |
cipher = :aes_cbc256 | |
case MessageVerifier.verify(encrypted, secret) do | |
{:ok, verified} -> | |
[encrypted, iv] = String.split(verified, "--") |> Enum.map(&Base.decode64!/1) | |
encrypted |> decrypt(cipher, sign_secret, iv) |> unpad_message | |
:error -> | |
:error | |
end | |
end | |
def verify_and_decrypt(_, _, _), do: :error | |
defp decrypt(encrypted, cipher, secret, iv) do | |
:crypto.block_decrypt(cipher, trim_secret(secret), iv, encrypted) | |
end | |
defp unpad_message(msg) do | |
padding_size = :binary.last(msg) | |
if padding_size <= 16 do | |
msg_size = byte_size(msg) | |
if binary_part(msg, msg_size, -padding_size) == :binary.copy(<<padding_size>>, padding_size) do | |
{:ok, binary_part(msg, 0, msg_size - padding_size)} | |
else | |
:error | |
end | |
else | |
:error | |
end | |
end | |
defp trim_secret(secret) do | |
case byte_size(secret) do | |
large when large > 32 -> :binary.part(secret, 0, 32) | |
_ -> secret | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule MyAPI.Encryption.MessageVerifier do | |
@doc """ | |
Decodes and verifies the encoded binary was not tampared with. | |
""" | |
def verify(binary, secret) when is_binary(binary) and is_binary(secret) do | |
case String.split(binary, "--") do | |
[content, digest] when content != "" and digest != "" -> | |
if Plug.Crypto.secure_compare(digest(secret, content), digest) do | |
{:ok, Base.decode64!(content)} | |
else | |
:error | |
end | |
_ -> | |
:error | |
end | |
end | |
defp digest(secret, data) do | |
:crypto.hmac(:sha, secret, data) |> Base.encode16(case: :lower) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment