@kylethebaker,
Do you know of any working example on how to use Elliptic Curve keys to sign/verify messages?
I was tying to use GitHub - farao/elixir-ecc: An elixir module for elliptic curve cryptography repo but the code is outdated and does not work for newest Elixir/Erlang versions.
Thank you for the ErlangSslTest code!
Here is what I did:
$ openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 -out ec_privkey.pem
$ openssl ec -in ec_privkey.pem -pubout -out ec_pubkey.pem
$ openssl ecparam -in ec_privkey.pem -text -noout
ASN1 OID: prime256v1
NIST CURVE: P-256
======= lib/ecc.ex =========
defmodule ECC do
use GenServer
def init(pem) do
{:ok, %{
public: ECC.Crypto.parse_public_key(pem),
private: ECC.Crypto.parse_private_key(pem)
}}
end
def start(pem, register_name \ nil) do
if register_name do
GenServer.start(MODULE, pem, name: register_name)
else
GenServer.start(MODULE, pem)
end
end
def start_link(pem, register_name \ nil) do
if register_name do
GenServer.start(MODULE, pem, name: register_name)
else
GenServer.start(MODULE, pem)
end
end
def handle_call(:get_state, _from, state) do {:reply, {:ok, state}, state}
end
def handle_call(:get_public_key, _from, state) do if state.public do
{:reply, {:ok, state.public}, state} else
{:reply, {:error, :no_public_key}, state} end
end
def handle_call({:sign, msg, hash_type}, _from, keys) do {:reply, {:ok, ECC.Crypto.sign(msg, hash_type, keys.private)}, keys}
end
def handle_call({:verify_signature, msg, signature, public_key, hash_type}, _from, keys) do result = ECC.Crypto.verify_signature(msg, signature, hash_type, public_key)
{:reply, {:ok, result}, keys} end
end
defmodule ECC.Crypto do def encode_der(ans1_type, ans1_entity) do
:public_key.der_encode(ans1_type, ans1_entity)
end
def parse_public_key(pem) do
# Expected result:
# {#'ECPoint'{}, #'ECParameters'{} | {namedCurve, oid()}}
try do
pem_keys = :public_key.pem_decode(pem)
# pem_keys |> IO.inspect(label: "parse_public_key: ")
# 'ECParameters'{
# version, % integer()
# fieldID, % #'FieldID'{}
# curve, % #'Curve'{}
# base, % binary()
# order, % integer()
# cofactor % integer()
# }
ec_params =
Enum.find(pem_keys, fn(k) -> elem(k, 0) == :EcpkParameters end)
# |> IO.inspect(label: "EcpkParameters")
|> put_elem(0, :EcpkParameters)
|> :public_key.pem_entry_decode
|> IO.inspect(label: "ec_params")
pem_public =
Enum.find(pem_keys, fn(k) -> elem(k, 0) == :SubjectPublicKeyInfo end)
# |> IO.inspect(label: "pem_public: ", limit: :infinity)
|> elem(1)
# |> IO.inspect(label: "pem_public: ", limit: :infinity)
ec_point = :public_key.der_decode(:SubjectPublicKeyInfo, pem_public)
|> IO.inspect(label: "ec_point A", limit: :infinity)
|> elem(1)
|> IO.inspect(label: "ec_point B", limit: :infinity)
|> elem(2)
|> IO.inspect(label: "ec_point C", limit: :infinity)
# {cert_type, encode_der(cert_type, cert_entry)}
# {{:ECPoint, ec_point}, {:namedCurve, {1, 3, 132, 0, 35}}}
{{:ECPoint, ec_point}, ec_params}
rescue
_ -> nil
end
end
def parse_private_key(pem) do
try do
# Decodes PEM binary data and returns entries as ASN.1 DER encoded entities
:public_key.pem_decode(pem)
|> Enum.find(fn(k) -> elem(k,0) == :ECPrivateKey end)
|> :public_key.pem_entry_decode
rescue
_ -> nil
end
end
def sign(msg, hash_type, private_key) do
try do
# sign(Msg, DigestType, Key) -> binary()
:public_key.sign(msg, hash_type, private_key)
rescue
_ -> nil
end
end
def verify_signature(msg, signature, hash_type, public_key) do
try do
# verify(Msg, DigestType, Signature, Key) -> boolean()
# Msg = binary() | {digest,binary()}
# DigestType = rsa_digest_type() | dss_digest_type() | ecdsa_digest_type()
# Signature = binary()
# Key = rsa_public_key() | dsa_public_key() | ec_public_key()
# public_key |> IO.inspect(label: "\n -> verify_signature with: ")
:public_key.verify(msg, hash_type, signature, public_key)
rescue
_ -> nil
end
end
end
=== lib/demo.ex ===
defmodule Demo do
@msg “Elixir”
def run(pem_private_file, pem_public_file) do
# ec_private_key.pem
pem_private = File.read!(pem_private_file)
pem_public = File.read!(pem_public_file)
pem = Enum.join([pem_public, pem_private])
IO.puts("Loaded pub/private pair from: #{pem_public_file} - #{pem_private_file}")
{:ok, pid} = ECC.start_link(pem, :ecc)
pid |> IO.inspect(label: "ECC pid:")
{:ok, signature} = GenServer.call(:ecc, {:sign, @msg, :sha})
signature |> Base.encode64 |> IO.inspect(label: "\n ==> Signed messsage")
# {:ok, {point, params} = public_key} = GenServer.call(:ecc, :get_public_key)
{:ok, public_key} = GenServer.call(:ecc, :get_public_key)
public_key |> IO.inspect(label: "Server pubkey")
{:ok, result} = GenServer.call(
:ecc, {:verify_signature, @msg, signature, public_key, :sha})
IO.puts("#{@msg} == #{@msg}? #{result}" )
{:ok, pid}
end
end
============= output ==============
$ iex -S mix
Demo.run(“ec_privkey.pem”, “ec_pubkey.pem”)
Loaded pub/private pair from: ec_pubkey.pem - ec_privkey.pem
ec_params: {:namedCurve, {1, 2, 840, 10045, 3, 1, 7}}
ec_point A: {:SubjectPublicKeyInfo,
{:AlgorithmIdentifier, {1, 2, 840, 10045, 2, 1},
<<6, 8, 42, 134, 72, 206, 61, 3, 1, 7>>},
<<4, 111, 107, 121, 97, 249, 112, 77, 164, 104, 38, 231, 35, 40, 249, 220, 167,
154, 64, 43, 172, 135, 96, 228, 104, 95, 164, 150, 168, 149, 40, 249, 40, 6,
237, 228, 215, 250, 30, 137, 49, 233, 188, 218, 153, 0, 103, 142, 124, 0, 91,
217, 152, 177, 115, 159, 203, 172, 199, 85, 204, 121, 77, 222, 209>>}
ec_point B: {:AlgorithmIdentifier, {1, 2, 840, 10045, 2, 1},
<<6, 8, 42, 134, 72, 206, 61, 3, 1, 7>>}
ec_point C: <<6, 8, 42, 134, 72, 206, 61, 3, 1, 7>>
ECC pid:: #PID<0.172.0>
==> Signed messsage: “MEUCIQD1hPxulbeyYvEq7Zml0VBVwHxPbS+wYQ5k5RD2xfPOxgIgFNhyOJeCuQ5nRNViHgmk0iT4jbdsEvWyWqOidFATvr0=”
Server pubkey: {{:ECPoint, <<6, 8, 42, 134, 72, 206, 61, 3, 1, 7>>},
{:namedCurve, {1, 2, 840, 10045, 3, 1, 7}}}
Elixir == Elixir? falsePreformatted text