zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

message_encryptor.ex (8463B)


      1 defmodule Plug.Crypto.MessageEncryptor do
      2   @moduledoc ~S"""
      3   `MessageEncryptor` is a simple way to encrypt values which get stored
      4   somewhere you don't trust.
      5 
      6   The encrypted key, initialization vector, cipher text, and cipher tag
      7   are base64url encoded and returned to you.
      8 
      9   This can be used in situations similar to the `Plug.Crypto.MessageVerifier`,
     10   but where you don't want users to be able to determine the value of the payload.
     11 
     12   The current algorithm used is AES-GCM-128.
     13 
     14   ## Example
     15 
     16       iex> secret_key_base = "072d1e0157c008193fe48a670cce031faa4e..."
     17       ...> encrypted_cookie_salt = "encrypted cookie"
     18       ...> encrypted_signed_cookie_salt = "signed encrypted cookie"
     19       ...>
     20       ...> secret = KeyGenerator.generate(secret_key_base, encrypted_cookie_salt)
     21       ...> sign_secret = KeyGenerator.generate(secret_key_base, encrypted_signed_cookie_salt)
     22       ...>
     23       ...> data = "José"
     24       ...> encrypted = MessageEncryptor.encrypt(data, secret, sign_secret)
     25       ...> MessageEncryptor.decrypt(encrypted, secret, sign_secret)
     26       {:ok, "José"}
     27 
     28   """
     29 
     30   @doc """
     31   Encrypts a message using authenticated encryption.
     32   """
     33   def encrypt(message, secret, sign_secret)
     34       when is_binary(message) and byte_size(secret) > 0 and is_binary(sign_secret) do
     35     aes128_gcm_encrypt(message, secret, sign_secret)
     36   rescue
     37     e -> reraise e, Plug.Crypto.prune_args_from_stacktrace(__STACKTRACE__)
     38   end
     39 
     40   @doc """
     41   Decrypts a message using authenticated encryption.
     42   """
     43   def decrypt(encrypted, secret, sign_secret)
     44       when is_binary(encrypted) and byte_size(secret) > 0 and is_binary(sign_secret) do
     45     aes128_gcm_decrypt(encrypted, secret, sign_secret)
     46   rescue
     47     e -> reraise e, Plug.Crypto.prune_args_from_stacktrace(__STACKTRACE__)
     48   end
     49 
     50   # Encrypts and authenticates a message using AES128-GCM mode.
     51   #
     52   # A random 128-bit content encryption key (CEK) is generated for
     53   # every message which is then encrypted with `aes_gcm_key_wrap/3`.
     54   defp aes128_gcm_encrypt(plain_text, secret, sign_secret) when bit_size(secret) > 256 do
     55     aes128_gcm_encrypt(plain_text, binary_part(secret, 0, 32), sign_secret)
     56   end
     57 
     58   defp aes128_gcm_encrypt(plain_text, secret, sign_secret)
     59        when is_binary(plain_text) and bit_size(secret) in [128, 192, 256] and
     60               is_binary(sign_secret) do
     61     key = :crypto.strong_rand_bytes(16)
     62     iv = :crypto.strong_rand_bytes(12)
     63     aad = "A128GCM"
     64     {cipher_text, cipher_tag} = block_encrypt(:aes_gcm, key, iv, {aad, plain_text})
     65     encrypted_key = aes_gcm_key_wrap(key, secret, sign_secret)
     66     encode_token(aad, encrypted_key, iv, cipher_text, cipher_tag)
     67   end
     68 
     69   # Verifies and decrypts a message using AES128-GCM mode.
     70   #
     71   # Decryption will never be performed prior to verification.
     72   #
     73   # The encrypted content encryption key (CEK) is decrypted
     74   # with `aes_gcm_key_unwrap/3`.
     75   defp aes128_gcm_decrypt(cipher_text, secret, sign_secret) when bit_size(secret) > 256 do
     76     aes128_gcm_decrypt(cipher_text, binary_part(secret, 0, 32), sign_secret)
     77   end
     78 
     79   defp aes128_gcm_decrypt(cipher_text, secret, sign_secret)
     80        when is_binary(cipher_text) and bit_size(secret) in [128, 192, 256] and
     81               is_binary(sign_secret) do
     82     case decode_token(cipher_text) do
     83       {aad = "A128GCM", encrypted_key, iv, cipher_text, cipher_tag}
     84       when bit_size(iv) === 96 and bit_size(cipher_tag) === 128 ->
     85         encrypted_key
     86         |> aes_gcm_key_unwrap(secret, sign_secret)
     87         |> case do
     88           {:ok, key} ->
     89             block_decrypt(:aes_gcm, key, iv, {aad, cipher_text, cipher_tag})
     90 
     91           _ ->
     92             :error
     93         end
     94         |> case do
     95           plain_text when is_binary(plain_text) ->
     96             {:ok, plain_text}
     97 
     98           _ ->
     99             :error
    100         end
    101 
    102       _ ->
    103         :error
    104     end
    105   end
    106 
    107   # TODO: remove when we require OTP 22
    108   if Code.ensure_loaded?(:crypto) and function_exported?(:crypto, :crypto_one_time_aead, 6) do
    109     defp block_encrypt(cipher, key, iv, {aad, payload}) do
    110       cipher = cipher_alias(cipher, bit_size(key))
    111       :crypto.crypto_one_time_aead(cipher, key, iv, payload, aad, true)
    112     catch
    113       :error, :notsup -> raise_notsup(cipher)
    114     end
    115 
    116     defp block_decrypt(cipher, key, iv, {aad, payload, tag}) do
    117       cipher = cipher_alias(cipher, bit_size(key))
    118       :crypto.crypto_one_time_aead(cipher, key, iv, payload, aad, tag, false)
    119     catch
    120       :error, :notsup -> raise_notsup(cipher)
    121     end
    122 
    123     # TODO: remove when we reqwuire OTP 24 (since it has similar alias handling)
    124     defp cipher_alias(:aes_gcm, 128), do: :aes_128_gcm
    125     defp cipher_alias(:aes_gcm, 192), do: :aes_192_gcm
    126     defp cipher_alias(:aes_gcm, 256), do: :aes_256_gcm
    127     defp cipher_alias(other, _), do: other
    128   else
    129     defp block_encrypt(cipher, key, iv, payload) do
    130       :crypto.block_encrypt(cipher, key, iv, payload)
    131     catch
    132       :error, :notsup -> raise_notsup(cipher)
    133     end
    134 
    135     defp block_decrypt(cipher, key, iv, payload) do
    136       :crypto.block_decrypt(cipher, key, iv, payload)
    137     catch
    138       :error, :notsup -> raise_notsup(cipher)
    139     end
    140   end
    141 
    142   defp raise_notsup(algo) do
    143     raise "the algorithm #{inspect(algo)} is not supported by your Erlang/OTP installation. " <>
    144             "Please make sure it was compiled with the correct OpenSSL/BoringSSL bindings"
    145   end
    146 
    147   # Wraps a decrypted content encryption key (CEK) with secret and
    148   # sign_secret using AES GCM mode. Accepts keys of 128, 192, or 
    149   # 256 bits based on the length of the secret key.
    150   #
    151   # See: https://tools.ietf.org/html/rfc7518#section-4.7
    152   defp aes_gcm_key_wrap(cek, secret, sign_secret) when bit_size(secret) > 256 do
    153     aes_gcm_key_wrap(cek, binary_part(secret, 0, 32), sign_secret)
    154   end
    155 
    156   defp aes_gcm_key_wrap(cek, secret, sign_secret)
    157        when bit_size(cek) in [128, 192, 256] and bit_size(secret) in [128, 192, 256] and
    158               is_binary(sign_secret) do
    159     iv = :crypto.strong_rand_bytes(12)
    160     {cipher_text, cipher_tag} = block_encrypt(:aes_gcm, secret, iv, {sign_secret, cek})
    161     cipher_text <> cipher_tag <> iv
    162   end
    163 
    164   # Unwraps an encrypted content encryption key (CEK) with secret and
    165   # sign_secret using AES GCM mode. Accepts keys of 128, 192, or 256 
    166   # bits based on the length of the secret key.
    167   #
    168   # See: https://tools.ietf.org/html/rfc7518#section-4.7
    169   defp aes_gcm_key_unwrap(wrapped_cek, secret, sign_secret) when bit_size(secret) > 256 do
    170     aes_gcm_key_unwrap(wrapped_cek, binary_part(secret, 0, 32), sign_secret)
    171   end
    172 
    173   defp aes_gcm_key_unwrap(wrapped_cek, secret, sign_secret)
    174        when bit_size(secret) in [128, 192, 256] and is_binary(sign_secret) do
    175     wrapped_cek
    176     |> case do
    177       <<cipher_text::128-bitstring, cipher_tag::128-bitstring, iv::96-bitstring>> ->
    178         block_decrypt(:aes_gcm, secret, iv, {sign_secret, cipher_text, cipher_tag})
    179 
    180       <<cipher_text::192-bitstring, cipher_tag::128-bitstring, iv::96-bitstring>> ->
    181         block_decrypt(:aes_gcm, secret, iv, {sign_secret, cipher_text, cipher_tag})
    182 
    183       <<cipher_text::256-bitstring, cipher_tag::128-bitstring, iv::96-bitstring>> ->
    184         block_decrypt(:aes_gcm, secret, iv, {sign_secret, cipher_text, cipher_tag})
    185 
    186       _ ->
    187         :error
    188     end
    189     |> case do
    190       cek when bit_size(cek) in [128, 192, 256] ->
    191         {:ok, cek}
    192 
    193       _ ->
    194         :error
    195     end
    196   end
    197 
    198   defp encode_token(protected, encrypted_key, iv, cipher_text, cipher_tag) do
    199     Base.url_encode64(protected, padding: false)
    200     |> Kernel.<>(".")
    201     |> Kernel.<>(Base.url_encode64(encrypted_key, padding: false))
    202     |> Kernel.<>(".")
    203     |> Kernel.<>(Base.url_encode64(iv, padding: false))
    204     |> Kernel.<>(".")
    205     |> Kernel.<>(Base.url_encode64(cipher_text, padding: false))
    206     |> Kernel.<>(".")
    207     |> Kernel.<>(Base.url_encode64(cipher_tag, padding: false))
    208   end
    209 
    210   defp decode_token(token) do
    211     with [protected, encrypted_key, iv, cipher_text, cipher_tag] <-
    212            String.split(token, ".", parts: 5),
    213          {:ok, protected} <- Base.url_decode64(protected, padding: false),
    214          {:ok, encrypted_key} <- Base.url_decode64(encrypted_key, padding: false),
    215          {:ok, iv} <- Base.url_decode64(iv, padding: false),
    216          {:ok, cipher_text} <- Base.url_decode64(cipher_text, padding: false),
    217          {:ok, cipher_tag} <- Base.url_decode64(cipher_tag, padding: false) do
    218       {protected, encrypted_key, iv, cipher_text, cipher_tag}
    219     else
    220       _ -> :error
    221     end
    222   end
    223 end