diff --git a/lib/keystore/decoder.ex b/lib/keystore/decoder.ex new file mode 100644 index 0000000..28c1669 --- /dev/null +++ b/lib/keystore/decoder.ex @@ -0,0 +1,56 @@ +defmodule ExthCrypto.Keystore.Decoder do + @moduledoc """ + Module to decode an encrypted JSON keystore file. + """ + import ExthCrypto.Math + + @spec unlock_file(String.t, String.t) :: {:ok, binary()} | {:error, String.t} + def unlock_file(filename, password) do + File.read!(filename) + |> Poison.decode! + |> Map.get("crypto") + |> unlock(password) + end + + @spec unlock(map(), String.t) :: {:ok, binary()} | {:error, String.t} + defp unlock(crypto_map, password) do + with {:ok, derived_key} <- password + |> kdf(crypto_map["kdf"], crypto_map["kdfparams"]) + |> split_and_verify(crypto_map["ciphertext"] |> hex_to_bin, crypto_map["mac"] |> hex_to_bin) do + decode_ciphertext(derived_key, crypto_map["cipher"], crypto_map["ciphertext"] |> hex_to_bin, crypto_map["cipherparams"]) + end + end + + @spec kdf(String.t, String.t, map()) :: binary() + defp kdf(password, "pbkdf2", kdf_params) do + Pbkdf2.Base.hash_password( + password, + kdf_params["salt"] |> hex_to_bin, + rounds: kdf_params["c"], + length: kdf_params["dklen"], + digest: get_digest(kdf_params["prf"]), + format: :hex + ) |> hex_to_bin + end + + @spec get_digest(String.t) :: atom() + defp get_digest("hmac-sha256"), do: :sha256 + + @spec decode_ciphertext(<<_::128>>, binary(), binary(), map()) :: {:ok, binary()} | {:error, String.t} + def decode_ciphertext(derived_key, "aes-128-ctr", ciphertext, cipher_params) do + {:ok, ExthCrypto.AES.decrypt(ciphertext, :ctr, derived_key, cipher_params["iv"] |> hex_to_bin)} + end + + @spec split_and_verify(<<_::256>>, binary(), binary()) :: {:ok, <<_::128>>} | {:error, String.t} + def split_and_verify(key, ciphertext, mac) do + <> = key + + # SHA3( derivedKey[16:32] || cipherText ) + if ExthCrypto.Hash.Keccak.kec(latter <> ciphertext) == mac do + {:ok, former} + else + {:erorr, "Invalid passphrase"} + end + end + +end \ No newline at end of file diff --git a/mix.exs b/mix.exs index 5878712..dc6ffe2 100644 --- a/mix.exs +++ b/mix.exs @@ -41,6 +41,8 @@ defmodule ExthCrypto.Mixfile do {:ex_doc, "~> 0.14", only: :dev, runtime: false}, {:dialyxir, "~> 0.5", only: [:dev], runtime: false}, {:binary, "~> 0.0.4"}, + {:pbkdf2_elixir, "~> 0.12"}, + {:poison, "~> 3.1"}, ] end end diff --git a/mix.lock b/mix.lock index 9254c52..11892e9 100644 --- a/mix.lock +++ b/mix.lock @@ -5,4 +5,6 @@ "earmark": {:hex, :earmark, "1.2.3", "206eb2e2ac1a794aa5256f3982de7a76bf4579ff91cb28d0e17ea2c9491e46a4", [], [], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.16.4", "4bf6b82d4f0a643b500366ed7134896e8cccdbab4d1a7a35524951b25b1ec9f0", [], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, "keccakf1600": {:hex, :keccakf1600_orig, "2.0.0", "0a7217ddb3ee8220d449bbf7575ec39d4e967099f220a91e3dfca4dbaef91963", [], [], "hexpm"}, - "libsecp256k1": {:hex, :libsecp256k1, "0.1.3", "57468b986af7c9633527875f71c7ca08bf4150b07b38a60d5bd48fba299ff6c1", [:rebar3], [], "hexpm"}} + "libsecp256k1": {:hex, :libsecp256k1, "0.1.3", "57468b986af7c9633527875f71c7ca08bf4150b07b38a60d5bd48fba299ff6c1", [:rebar3], [], "hexpm"}, + "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.3", "6706a148809a29c306062862c803406e88f048277f6e85b68faf73291e820b84", [], [], "hexpm"}, + "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [], [], "hexpm"}}