Right way to use :crypto.verify(...)

I have some problems with :crypto.verify.

message = 'whatever'
sign = 'zFuf7bRH4RHwyktaqHQwmX5rn3LfSb4dKo'
public_key = 'MIGJAoGBAM1fmNUvezts3yglTdhXuqG7OhHxQtDFA'
:crypto.verify(:rsa, :sha256, message, sign, public_key)

This is give me an ArgumentError, like:

(crypto) :crypto.pkey_verify_nif(:rsa, :sha256, 'whatever', 'zFuf7bRH4RHwyktaqHQwmX5rn3LfSb4dKo', ["M", "I", "G", "J", "A", "o", "G", "B", "A", "M", "1", "f", "m", "N", "U", "v", "e", "z", "t", "s", "3", "y", "g", "l", "T", "d", "h", "X", "u", "q", "G", "7", "O", "h", "H", "x", "Q", "t", "D", "F", "A"], [])
(crypto) crypto.erl:874: :crypto.verify/6

Method :crypto.verify/5 exist, but some reason it’s gave me error about …verify/6
I don’t get it. What should I do for correct working of this?..

:wave:

According to http://erlang.org/doc/man/crypto.html#verify-5, the message needs to be a binary, not a charlist. As well as signature and it shouldn’t be encoded either. And public key needs to be in the format erlang understands.

1 Like

The function signature of :crypto.verify/5 does not match with your params.

verify(Algorithm, DigestType, Msg, Signature, Key) -> Result
verify(Algorithm, DigestType, Msg, Signature, Key, Options) ->
          Result
Types
Algorithm = pk_sign_verify_algs()
DigestType =
    rsa_digest_type() | dss_digest_type() | ecdsa_digest_type()
Msg = binary() | {digest, binary()}
Signature = binary()
Key =
    rsa_public() |
    dss_public() |
    [ecdsa_public() | ecdsa_params()] |
    [eddsa_public() | eddsa_params()] |
    engine_key_ref()
Options = pk_sign_verify_opts()
Result = boolean()

First 2 parameters seem to match but your message seems not to be “cleartext” so you’d need to pass it wrapped in a tuple. Your public key also does not match the typespec.

Here is a StackOverflow example: https://stackoverflow.com/a/20509599/1397594

Also note your parameters are passing charlists and not binaries.

1 Like

The public_key variable appears to contains a truncated Base64 fragment of a 1024-bit RSA public key. Assuming you actually have the full value you can convert it to an rsa_public() type like this:

public_key_base64 = "MIGJAoGBAM1fmNUvezts3yglTdhXuqG7OhHxQtDFA..." # truncated
public_key = :public_key.der_decode(:RSAPublicKey, Base.decode64!(public_key_base64))

The signature also appears to be truncated. Given the full Base64 encoded signature you can verify the signature with :crypto.verify:

message = "whatever"
signature_base64 = "zFuf7bRH4RHwyktaqHQwmX5rn3LfSb4dKo..." # truncated
signature = Base.decode64!(signature_base64)
:crypto.verify(:rsa, :sha256, message, signature, public_key)

Note the use of binary strings as opposed to chartists (single quotes), as others have pointed out.

6 Likes

Hi all,

catching up on that topic, I am currently facing an issue when I try to sign / verify on a msg that is already a digest.

I generate the secp256k1 keypair, sign a message with it and verify it. so far so good:

iex>  {public, secret} = :crypto.generate_key(:ecdh, :secp256k1)
{<<4, 108, 222, 220, 9, 10, 7, 227, 52, 162, 49, 185, 119, 228, 116, 89, 21,
   210, 55, 68, 13, 11, 70, 113, 3, 194, 158, 157, 245, 222, 92, 60, 221, 50,
   90, 180, 241, 14, 23, 202, 116, 170, 41, 90, 117, 191, 83, 57, 78, 99, 125,
   17, 181, 181, 81, 250, 239, 169, 40, 86, 24, 202, 204, 221, 70>>,
 <<114, 167, 169, 229, 133, 167, 126, 185, 69, 244, 173, 52, 202, 173, 193, 33,
   246, 242, 111, 23, 76, 108, 22, 196, 39, 170, 154, 65, 184, 139, 220, 87>>}

iex> sig = :crypto.sign(:ecdsa, :sha256, "toto", [secret, :secp256k1])
<<48, 69, 2, 32, 4, 143, 152, 145, 255, 133, 228, 8, 216, 31, 4, 212, 212, 85,
  37, 182, 66, 127, 147, 128, 251, 44, 104, 192, 145, 110, 0, 198, 133, 201,
  175, 4, 2, 33, 0, 178, 100, 197, 149, 49, 160, 214, 171, 191, 151, 190, 17,
  107, 165, 213, 7, 123, 122, 37, 95, 46, 31, 8, 217, 8, 14, 7, 157, 40, 51, 88,
  205>>

iex> :crypto.verify(:ecdsa, :sha256, "toto", sig, [public, :secp256k1])
true

Now my issue is when instead of passing a plaintext message and a hash algorithm, i want to pass the digest directly, I followed the doc and tried the following:

iex> hash = :crypto.hash(:sha256, "toto")
<<49, 247, 166, 94, 49, 85, 134, 172, 25, 139, 215, 152, 182, 98, 156, 228, 144,
  61, 8, 153, 71, 109, 87, 65, 169, 243, 46, 46, 82, 27, 106, 102>>
iex> hash_hex = hash |> Base.encode16(case: :lower)
"31f7a65e315586ac198bd798b6629ce4903d0899476d5741a9f32e2e521b6a66"

and i am now trying to sign with the {:digest, hash_hex} as a parameter, I get the following error:

iex> :crypto.sign(:ecdsa, nil, {:digest, hash_hex}, [secret, :secp256k1])
** (ArgumentError) argument error
    (crypto 4.7) :crypto.pkey_sign_nif(:ecdsa, nil, {:digest, "31f7a65e315586ac198bd798b6629ce4903d0899476d5741a9f32e2e521b6a66"}, {{{:prime_field, <<255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 255, 255, 252, 47>>}, {<<0>>, "\a", :none}, <<4, 121, 190, 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, 155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152, 72, 58, 218, 119, 38, 163, 196, 101, 93, 164, 251, 252, 14, ...>>, <<255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 186, 174, 220, 230, 175, 72, 160, 59, 191, 210, 94, 140, 208, 54, 65, 65>>, <<1>>}, <<114, 167, 169, 229, 133, 167, 126, 185, 69, 244, 173, 52, 202, 173, 193, 33, 246, 242, 111, 23, 76, 108, 22, 196, 39, 170, 154, 65, 184, 139, 220, 87>>}, [])
    (crypto 4.7) crypto.erl:1907: :crypto.sign/5

I tried to check if this was my syntax that was wrong by running the same kind of operation with and ed25519 keypair (I used OTP 23 RC3 for that experiment):

iex> {pk, sk} = :crypto.generate_key(:eddsa, :ed25519)
{<<7, 84, 240, 17, 249, 10, 185, 55, 19, 229, 253, 30, 71, 155, 132, 115, 59,
   35, 231, 100, 64, 72, 246, 213, 49, 254, 59, 190, 56, 228, 216, 80>>,
 <<127, 237, 190, 166, 201, 23, 218, 16, 218, 181, 201, 87, 97, 164, 101, 252,
   227, 204, 98, 90, 139, 52, 26, 14, 108, 88, 218, 62, 195, 205, 188, 87>>}

iex> sig = :crypto.sign(:eddsa, nil, {:digest, hash_hex}, [sk, :ed25519])
<<197, 9, 223, 65, 1, 186, 44, 246, 4, 86, 6, 90, 170, 125, 190, 240, 113, 71,
  90, 57, 10, 61, 77, 144, 27, 150, 22, 245, 85, 144, 119, 159, 213, 116, 146,
  70, 27, 170, 202, 92, 130, 26, 73, 51, 59, 201, 90, 250, 242, 250, 3, 120,
  102, 109, 226, 189, 160, 191, 101, 252, 138, 213, 204, 11>>

iex> :crypto.verify(:eddsa, nil, {:digest, hash_hex}, sig, [pk, :ed25519])
true

so it works as expected for eddsa but not for ecdsa, anyone knows what is going on? what I am missing?

thanks

I’m not sure why the API for EdDSA behaves differently, but:

  1. nil is not a thing in Erlang; according to the type spec you can set the second param to :none, though ECDSA requires that you pass in a valid digest type; so set this to :sha256
  2. The value in the :digest tuple should be the binary digest, not Hex encoded

So this will work: :crypto.verify(:ecdsa, :sha256, {:digest, hash}, sig, [public, :secp256k1]) to verify the original signature using a SHA256 rather than the original message. And to generate a signature: :crypto.sign(:ecdsa, :sha256, {:digest, hash}, [secret, :secp256k1])

1 Like

perfect the verification worked indeed, the doc is a bit confusing.

Thanks for your help!

I don’t think I did verification when I used it, but these kinds of unclear argument errors are the main motivation behind https://github.com/ntrepid8/ex_crypto which I contributed a few additional methods to 3 or 4 years back… I’d encourage anyone doing Crypto in Elixir to consider using it, even though it’s easy to use the erlang crypto directly, because it will give much more helpful error messages when things go wrong.