brightball

brightball

Has anyone used Erlang's :public_key.der_decode/2 function? I can't seem to get it working

I’ve been trying to figure this out for a few hours now and I’m running into a wall. I’m trying to decode a public key to get the bit strength. I’ve done this in other languages via libraries that interface with OpenSSL but every code example I see using Elixir defer to :public_key.der_decode/2. I’ve even resorted to ChatGPT to give me working example code with sample data and those examples give me the same error.

EDIT: Originally posted this with a sample key that I couldn’t even get working with the OpenSSL code, so I’ve updated it to one that has been verified to work under OpenSSL. Updated error has been posted.

I’m using Erlang 27.1 and as I was researching this I saw that :public_key saw some deprecations for security reasons and I was wondering if it was affected, but I rolled back to OTP 26 and still get the same error.

Any ideas would be greatly appreciated.

Here is the error:

%MatchError{
      term:
        {:error,
         {:asn1,
          {{:wrong_tag,
            {{:expected, 2},
             {:got, 16, {16, [{6, <<42, 134, 72, 134, 247, 13, 1, 1, 1>>}, {5, ""}]}}}},
           [
             {:"OTP-PUB-KEY", :match_tags, 2, [file: ~c"../src/OTP-PUB-KEY.erl", line: 24244]},
             {:"OTP-PUB-KEY", :decode_integer, 2,
              [file: ~c"../src/OTP-PUB-KEY.erl", line: 23761]},
             {:"OTP-PUB-KEY", :dec_RSAPublicKey, 2,
              [file: ~c"../src/OTP-PUB-KEY.erl", line: 2976]},
             {:"OTP-PUB-KEY", :decode, 2, [file: ~c"../src/OTP-PUB-KEY.erl", line: 1239]},
             {:public_key, :der_decode, 2, [file: ~c"public_key.erl", line: 353]},
             {PublicKeyDecoder, :decode_rsa_public_key, 1,
              [file: ~c"lib/public_key_decoder.ex", line: 13]},
             {PublicKeyDecoder, :example_usage, 0,
              [file: ~c"lib/public_key_decoder.ex", line: 39]},
             {:elixir, :eval_external_handler, 3, [file: ~c"src/elixir.erl", line: 386]},
             {:erl_eval, :do_apply, 7, [file: ~c"erl_eval.erl", line: 750]},
             {:elixir, :eval_forms, 4, [file: ~c"src/elixir.erl", line: 364]},
             {Module.ParallelChecker, :verify, 1,
              [file: ~c"lib/module/parallel_checker.ex", line: 120]},
             {IEx.Evaluator, :eval_and_inspect, 3, [file: ~c"lib/iex/evaluator.ex", line: 336]},
             {IEx.Evaluator, :eval_and_inspect_parsed, 3,
              [file: ~c"lib/iex/evaluator.ex", line: 310]},
             {IEx.Evaluator, :parse_eval_inspect, 4, [file: ~c"lib/iex/evaluator.ex", line: 299]},
             {IEx.Evaluator, :loop, 1, [file: ~c"lib/iex/evaluator.ex", line: 189]},
             {IEx.Evaluator, :init, 5, [file: ~c"lib/iex/evaluator.ex", line: 34]},
             {:proc_lib, :init_p_do_apply, 3, [file: ~c"proc_lib.erl", line: 241]}
           ]}}}
    }

Below I’ll provide some sample code from ChatGPT that’s giving the same errors as my code if you’d like to reproduce it.

Just call PublicKeyDecoder.example_usage() to try it.

defmodule PublicKeyDecoder do
  @moduledoc """
  A module to demonstrate decoding a DER-encoded RSA public key using :public_key.der_decode.
  """

  @doc """
  Decodes a DER-encoded RSA public key and extracts its modulus and exponent.

  - `der_encoded_key` should be the binary representation of the public key.
  """
  def decode_rsa_public_key(der_encoded_key) do
    try do
      {:RSAPublicKey, modulus, exponent} = :public_key.der_decode(:RSAPublicKey, der_encoded_key)
      {:ok, %{modulus: modulus, exponent: exponent}}
    rescue
      e -> {:error, "Failed to decode RSA public key: #{inspect(e)}"}
    end
  end

  @doc """
  Calculate the bit strength of the modulus.

  - `modulus` is the integer modulus extracted from the decoded public key.
  """
  def calculate_bit_strength(modulus) do
    :erlang.bit_size(modulus)
  end

  def example_usage() do
    # Example Usage

    # This is a sample Base64-encoded DER-encoded RSA public key.
   base64_public_key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDW8q5LtUKpUOLpWqiGfDzbUMjP+MEBzfYOq8q1hCST/wyoBqJznRhhKLERfWQ7GKK8X/6hQotoPBEFF2PfZaIXvahalOs7Q40EdCtooCb0Vt/sGH5DIeTWSTFTwHlINHFKBKnH/0oW24XrjxW3jcBmgIxQNBFoOQFHhmjshjLjbQIDAQAB"


    # Decode the Base64 key into binary
    {:ok, der_encoded_key} = Base.decode64(base64_public_key)

    # Decode the RSA public key
    case PublicKeyDecoder.decode_rsa_public_key(der_encoded_key) do
      {:ok, %{modulus: modulus, exponent: exponent}} ->
        IO.puts("Modulus: #{inspect(modulus)}")
        IO.puts("Exponent: #{exponent}")
        bit_strength = PublicKeyDecoder.calculate_bit_strength(modulus)
        IO.puts("Bit strength: #{bit_strength} bits")

      {:error, reason} ->
        IO.puts("Error: #{reason}")
    end
  end
end

Marked As Solved

al2o3cr

al2o3cr

I don’t see the corresponding BN_num_bits function from OpenSSL exposed anywhere in OTP’s implementation - it’s used in a few places but only from C.

A simple alternative would be :binary.encode_unsigned/1 followed by bit_size:

{_, n, _} =
  :public_key.pem_decode("-----BEGIN PUBLIC KEY-----\n" <> base64_public_key <> "\n-----END PUBLIC KEY-----\n")
  |> hd()
  |> :public_key.pem_entry_decode()

bits = n |> :binary.encode_unsigned() |> bit_size()

# bits will be 1024

Also Liked

al2o3cr

al2o3cr

There are two different formats for an “RSA public key”, and der_decode expects one while the input is the opposite.

Backstory:

TLDR:

There are two different ASN.1 payloads for an RSA public key:

  • a “simple” one that’s just the modulus and exponent. Used with the -----BEGIN RSA PUBLiC KEY----- PEM header
  • a “generic” one that uses SubjectPublicKeyInfo and then nests the modulus + exponent inside

Your input is the latter, but der_decode is expecting the former.

The PEM machinery can handle this correctly:

:public_key.pem_decode("-----BEGIN PUBLIC KEY-----\n" <> base64_public_key <> "\n-----END PUBLIC KEY-----\n")
|> hd()
|> :public_key.pem_entry_decode()

# gives:
{:RSAPublicKey,
 150941599098518570126874683465281552058004089482812534451212203604139744439191153555431393200565235808092997600710339447131887772472013591195906357127812043735918563281687598329506755792366482304448753019436841738444187832077672451367879120396964808983355846518616258864015758142866753194926761306968328168301,
 65537}
brightball

brightball

Thanks! This worked like a charm. I’ll share the final function that resulted from it.

Still need to add some error checking probably, but this worked great. Tested it on an assortment of keys and it worked everytime.

  def calculate_key_strength(%{"k" => "ed25519"}), do: 256
  def calculate_key_strength(%{"k" => "rsa", "p" => key}) do

    { _key_type, modulus, _exponent} =
      :public_key.pem_decode("-----BEGIN PUBLIC KEY-----\n" <> key <> "\n-----END PUBLIC KEY-----\n")
      |> hd()
      |> :public_key.pem_entry_decode()

      modulus |> :binary.encode_unsigned() |> bit_size()
  end

Where Next?

Popular in Questions Top

LegitStack
I’m hoping you guys can give me some general advice and perhaps code examples if you’re feeling up to it. I’m very interested in Elixir,...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New

Other popular topics Top

vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 45766 226
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
AstonJ
by Lance Halvorsen Elixir and Phoenix are generating tremendous excitement as an unbeatable platform for building modern web application...
460 27162 124
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement