RFC-5869 implementation: hkdf_erlang

I could not find an implementation of HKDF in Erlang on Hex.pm, so I figured why not make one myself. :slight_smile:
There is one on github, but it is
a) as noted, not published on Hex.pm
b) uses the old crypto api (actually so does the `hkdf`` library for Elixir I believe).

3 Likes

I think this is what I am looking for, but not sure what to do once I have the OKM.

Is it possible to use this to create a EC256 public private keypair?

I don’t know much about ECC yet to be quite honest, the best I can probably do is point towards this lovely book on cryptography that might give an answer.

It is certainly possible, though doing so in Erlang/Elixir may not be so easy.

A P-256 private key is essentially a random number d that satisfies 0 < d < n where n is the order of the curve. You can get the order of P256 from :crypto using:

{{:prime_field, p}, {a, b, s}, g, n, h} = :crypto.ec_curve(:secp256r1)

Since a random 256-bit number may or may not be within the required range, you’re going to have to either run the KDF multiple times (e.g. with a sequence number as part of the IKM) until you get a value you can use, or use the NIST FIPS-186-4 B.4.1 procedure of producing 64 bits extra for the input and then calculating:

d =
  input # 40 bytes pseudo random binary
  |> :binary.decode_unsigned()
  |> rem(:binary.decode_unsigned(n) - 1)
  |> Kernel.+(1)
  |> :binary.encode_unsigned()

You can then wrap this in a ECPrivateKey record:
{:ECPrivateKey, 1, d, {:namedCurve, : secp256r1}, public_key_goes_here}

To calculate the public key, however, requires calculation of dG, where G is the base point of the curve. Unfortunately Erlang’s :crypto does not provide a function to multiply points on a curve. The closest thing would be to use :public_key.compute_key/2 on d and G, which will give you the x coordinate of the point. You could then calculate the two candidates for y be evaluating the curve equation for x. I guess you could then use trial and error to see which of {x, y} and {x, p - y} is the valid public key for your private key…? Or you’d have to implement point multiplication in Elixir, which is not going to be terribly efficient :slight_smile:

Anyway, TL;DR, it is theoretically possible, but you should probably not do it in Erlang/Elixir with custom code (including the code shown above) because implementing crypto from scratch is a very bad idea™ for most of us…

1 Like

Just for fun, here’s a working module in pure Elixir, which works with HKDF:

iex(1)> priv = :hkdf.derive(:sha384, <<"Never gonna give you up">>, 42) |> EC.from_kdf(:secp256r1)
{:ECPrivateKey, 1,
 <<152, 241, 110, 3, 198, 241, 200, 2, 25, 201, 250, 119, 187, 21, 134, 203,
   162, 76, 191, 189, 65, 114, 109, 178, 62, 15, 94, 78, 182, 234, 125, 166>>,
 {:namedCurve, :secp256r1},
 <<4, 218, 207, 201, 92, 80, 255, 49, 227, 206, 196, 95, 97, 157, 68, 28, 156,
   56, 238, 125, 99, 119, 54, 64, 197, 154, 8, 210, 27, 211, 249, 114, 241, 102,
   48, 248, 183, 26, 218, 155, 3, 159, 251, 230, 229, ...>>}

iex(2)> pub = {{:ECPoint, elem(priv, 4)}, elem(priv, 3)}                                          
{{:ECPoint,
  <<4, 218, 207, 201, 92, 80, 255, 49, 227, 206, 196, 95, 97, 157, 68, 28, 156,
    56, 238, 125, 99, 119, 54, 64, 197, 154, 8, 210, 27, 211, 249, 114, 241,
    102, 48, 248, 183, 26, 218, 155, 3, 159, 251, 230, 229, 6, 156, ...>>},
 {:namedCurve, :secp256r1}}

iex(3)> :public_key.sign("Hello, world!", :sha256, priv)
<<48, 70, 2, 33, 0, 154, 43, 221, 141, 160, 193, 248, 50, 65, 153, 117, 251, 67,
  236, 144, 109, 211, 170, 249, 88, 160, 109, 126, 155, 194, 214, 28, 108, 237,
  109, 148, 232, 2, 33, 0, 163, 198, 59, 141, 205, 103, 159, 179, 180, 37, ...>>

iex(4)> :public_key.verify("Hello, world!", :sha256, v(), pub)
true

But don’t use it!

1 Like

I was looking at this post https://blog.lelonek.me/bitcoin-extended-keys-773ab100c59f Section: " Master private key and chain code"

Which suggested it was as simple as the following.

<<private_key::binary-32, chain_code::binary-32>> = hashed_seed
{private_key, chain_code}

{public_key, _} = :crypto.generate_key(:ecdh, :secp256k1, private_key)

I guess I’m missing some key details

Using :crypto.generate_key/3 to derive a public key from a private key would certainly save a lot of work. It seems to work for SECP 256k1, but not 256r1 (NIST P-256). Edit: actually, it does work, I’ll update the gist…

Also, it seems to assume the private key value passed in is indeed a valid private key, but a random 32-bit value would have to be clamped to the curve’s order. Failing to do so would result in a significant bias towards lower scalar values, as values larger than the order would effectively wrap around.