zf

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

key_generator.ex (3432B)


      1 defmodule Plug.Crypto.KeyGenerator do
      2   @moduledoc """
      3   `KeyGenerator` implements PBKDF2 (Password-Based Key Derivation Function 2),
      4   part of PKCS #5 v2.0 (Password-Based Cryptography Specification).
      5 
      6   It can be used to derive a number of keys for various purposes from a given
      7   secret. This lets applications have a single secure secret, but avoid reusing
      8   that key in multiple incompatible contexts.
      9 
     10   The returned key is a binary. You may invoke functions in the `Base` module,
     11   such as `Base.url_encode64/2`, to convert this binary into a textual
     12   representation.
     13 
     14   See http://tools.ietf.org/html/rfc2898#section-5.2
     15   """
     16 
     17   import Bitwise
     18   @max_length bsl(1, 32) - 1
     19 
     20   @doc """
     21   Returns a derived key suitable for use.
     22 
     23   ## Options
     24 
     25     * `:iterations` - defaults to 1000 (increase to at least 2^16 if used for passwords);
     26     * `:length`     - a length in octets for the derived key. Defaults to 32;
     27     * `:digest`     - an hmac function to use as the pseudo-random function. Defaults to `:sha256`;
     28     * `:cache`      - an ETS table name to be used as cache.
     29       Only use an ETS table as cache if the secret and salt is a bound set of values.
     30       For example: `:ets.new(:your_name, [:named_table, :public, read_concurrency: true])`
     31 
     32   """
     33   def generate(secret, salt, opts \\ []) do
     34     iterations = Keyword.get(opts, :iterations, 1000)
     35     length = Keyword.get(opts, :length, 32)
     36     digest = Keyword.get(opts, :digest, :sha256)
     37     cache = Keyword.get(opts, :cache)
     38     generate(secret, salt, iterations, length, digest, cache)
     39   end
     40 
     41   @doc false
     42   def generate(secret, salt, iterations, length, digest, cache) do
     43     cond do
     44       not is_integer(iterations) or iterations < 1 ->
     45         raise ArgumentError, "iterations must be an integer >= 1"
     46 
     47       length > @max_length ->
     48         raise ArgumentError, "length must be less than or equal to #{@max_length}"
     49 
     50       true ->
     51         with_cache(cache, {secret, salt, iterations, length, digest}, fn ->
     52           generate(hmac_fun(digest, secret), salt, iterations, length, 1, [], 0)
     53         end)
     54     end
     55   rescue
     56     e -> reraise e, Plug.Crypto.prune_args_from_stacktrace(__STACKTRACE__)
     57   end
     58 
     59   defp with_cache(nil, _key, fun), do: fun.()
     60 
     61   defp with_cache(ets, key, fun) do
     62     case :ets.lookup(ets, key) do
     63       [{_key, value}] ->
     64         value
     65 
     66       [] ->
     67         value = fun.()
     68         :ets.insert(ets, [{key, value}])
     69         value
     70     end
     71   end
     72 
     73   defp generate(_fun, _salt, _iterations, max_length, _block_index, acc, length)
     74        when length >= max_length do
     75     acc
     76     |> IO.iodata_to_binary()
     77     |> binary_part(0, max_length)
     78   end
     79 
     80   defp generate(fun, salt, iterations, max_length, block_index, acc, length) do
     81     initial = fun.(<<salt::binary, block_index::integer-size(32)>>)
     82     block = iterate(fun, iterations - 1, initial, initial)
     83     length = byte_size(block) + length
     84 
     85     generate(fun, salt, iterations, max_length, block_index + 1, [acc | block], length)
     86   end
     87 
     88   defp iterate(_fun, 0, _prev, acc), do: acc
     89 
     90   defp iterate(fun, iteration, prev, acc) do
     91     next = fun.(prev)
     92     iterate(fun, iteration - 1, next, :crypto.exor(next, acc))
     93   end
     94 
     95   # TODO: remove when we require OTP 22.1
     96   if Code.ensure_loaded?(:crypto) and function_exported?(:crypto, :mac, 4) do
     97     defp hmac_fun(digest, key), do: &:crypto.mac(:hmac, digest, key, &1)
     98   else
     99     defp hmac_fun(digest, key), do: &:crypto.hmac(digest, key, &1)
    100   end
    101 end