Skip to content

Instantly share code, notes, and snippets.

@humphreyja
Created November 17, 2016 07:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save humphreyja/b0bba5fe13ce381880add2d44fa5a935 to your computer and use it in GitHub Desktop.
Save humphreyja/b0bba5fe13ce381880add2d44fa5a935 to your computer and use it in GitHub Desktop.
Decrypting the Rails session with Elixir
@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
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