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
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
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
SubjectPublicKeyInfoand 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
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
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance










