I am trying to verify a ECDSA signature created with OpenSSL on an embedded device using BearSSL.
First I created a private key using OpenSSL and extracted the public key:
$ openssl ecparam -name secp256r1 -genkey -noout -out private.pem
$ openssl ec -in private.pem -pubout -out public.pem
Then I extracted the raw public key:
$ openssl ec -noout -text -inform PEM -in public.pem -pubin
read EC key
Public-Key: (256 bit)
pub:
04:28:4b:54:a4:d4:92:6c:82:2d:da:8a:e1:be:4b:
49:61:5d:91:2b:2d:f5:f2:66:f8:9b:d1:be:cb:fb:
db:fc:4f:68:cf:52:68:55:36:53:0f:8e:8d:69:3f:
40:3a:06:62:ad:5b:5a:66:e6:1d:31:c6:13:08:f3:
4f:94:7b:59:7a
I have a text.txt
that contains nothing but hello world
and sign this using the following command:
$ cat text.txt
hello world
$ openssl dgst -sha256 -sign private.pem text.txt > signature
This now contains the signature in ASN1 format and is 72 bytes long as to be expected:
$ hexdump signature
0000000 4530 2002 ac54 51af 8ac0 cee8 dc74 4120
0000010 105c b65d a085 06c5 8e9f 1527 12f5 8e50
0000020 9d19 9b30 2102 a900 d2a5 343e 3a10 0bdd
0000030 e0a8 82f8 de2a 4f2d 51bf a775 bc42 2d2e
0000040 19c0 874f d85e 004b
Now to the embedded part. I include the data, signature and the public key first:
uint8_t text[] = "hello world\n";
size_t textlen = 12;
uint8_t signature[] = {
0x45, 0x30, 0x20, 0x02, 0xac, 0x54, 0x51, 0xaf, 0x8a, 0xc0, 0xce, 0xe8, 0xdc, 0x74, 0x41, 0x20,
0x10, 0x5c, 0xb6, 0x5d, 0xa0, 0x85, 0x06, 0xc5, 0x8e, 0x9f, 0x15, 0x27, 0x12, 0xf5, 0x8e, 0x50,
0x9d, 0x19, 0x9b, 0x30, 0x21, 0x02, 0xa9, 0x00, 0xd2, 0xa5, 0x34, 0x3e, 0x3a, 0x10, 0x0b, 0xdd,
0xe0, 0xa8, 0x82, 0xf8, 0xde, 0x2a, 0x4f, 0x2d, 0x51, 0xbf, 0xa7, 0x75, 0xbc, 0x42, 0x2d, 0x2e,
0x19, 0xc0, 0x87, 0x4f, 0xd8, 0x5e, 0x00, 0x4b };
static const uint8_t public_bytes[] = {
0x04, 0x28, 0x4b, 0x54, 0xa4, 0xd4, 0x92, 0x6c, 0x82, 0x2d, 0xda, 0x8a, 0xe1, 0xbe, 0x4b,
0x49, 0x61, 0x5d, 0x91, 0x2b, 0x2d, 0xf5, 0xf2, 0x66, 0xf8, 0x9b, 0xd1, 0xbe, 0xcb, 0xfb,
0xdb, 0xfc, 0x4f, 0x68, 0xcf, 0x52, 0x68, 0x55, 0x36, 0x53, 0x0f, 0x8e, 0x8d, 0x69, 0x3f,
0x40, 0x3a, 0x06, 0x62, 0xad, 0x5b, 0x5a, 0x66, 0xe6, 0x1d, 0x31, 0xc6, 0x13, 0x08, 0xf3,
0x4f, 0x94, 0x7b, 0x59, 0x7a };
static const br_ec_public_key public_key = {
.curve = BR_EC_secp256r1,
.q = (void *)public_bytes,
.qlen = sizeof(public_bytes)
};
Also, out of paranoia, I compared the md5 sum of both the text.txt
and my string before going any further as a quick check:
$ md5sum text.txt
6f5902ac237024bdd0c176cb93063dc4 text.txt
uint8_t sum[br_md5_SIZE];
br_md5_context md5ctx;
br_md5_init(&md5ctx);
br_md5_update(&md5ctx, text, textlen);
br_md5_out(&md5ctx, sum); // sum is the same as md5sum command output
I told OpenSSL to use sha256
previously to hash the payload for the signing process using ECDSA, so I am doing the same in BearSSL now and try to verify the signature using secp256r1
:
br_sha256_context sha256ctx;
br_sha256_init(&sha256ctx);
br_sha256_update(&sha256ctx, text, textlen);
br_sha256_out(&sha256ctx, hash);
uint32_t result = br_ecdsa_i15_vrfy_asn1(&br_ec_prime_i15, hash, sizeof(hash), &public_key, signature, sizeof(signature));
My expectation would be that the verify works, as I give it the same hash, hash function, curve, the corresponding public key and the signature. However, this doesn't work. I am probably missing something obvious but can't work it out.
The signature file contents shown as
$ hexdump signature
0000000 4530 2002 ac54 51af 8ac0 cee8 dc74 4120
...
is displayed as 16-bit values.
The signature in the C program is defined as an array of 8-bit values
uint8_t signature[] = {
0x45, 0x30, 0x20, 0x02, 0xac, 0x54, 0x51, 0xaf, 0x8a, 0xc0, 0xce, 0xe8, 0xdc, 0x74, 0x41, 0x20,
...
};
Depending on the byte order this may or may not be correct. Does 4530
correspond to 45
, 30
or 30
, 45
?
With little-endian byte-order, the hex dump
4530 2002 ...
would correspond to (*)
uint8_t signature[] = {
0x30, 0x45, 0x02, 0x20, ...
};
I suggest to display the hex dump as 8-bit values, e.g. by using
od -t x1 signature
and, if necessary, fix the array initialization in the C code.
According to dave_thompson_085's comment, the correct byte order is 0x30, 0x45, so the proposed fix (*) is the solution.
And an ECDSA signature on a 256-bit group definitely starts with first tag=SEQUENCE+constructed (always 0x30) then body length usually 68 to 70 (0x44 to 0x46)