after creating CSR using openSSL with secp256r1 curve
inspecting the file using openssl asn1parse -i -in ecTest.csr
the size of the signature is 73 bytes, why?
I think the ASN.1 of the signature is


and for secp256r1 that r and s are 32 bytes each which means

   1+1    +  1+1+32   +  1+1+32  = 70bytes  

The "extra" octet is needed because ASN.1 uses two's complement notation for integers, per section 8.3.3 of X.690:

The contents octets shall be a two's complement binary number equal to the integer value

In two's complement, the highest bit indicates a negative number. Since none of our numbers are actually negative, the correct notation needs to have leading zeros.

We can see this in action on the following signature:

$ openssl asn1parse -i -in foo.csr -strparse 126 -out raw
    0:d=0  hl=2 l=  69 cons: SEQUENCE          
    2:d=1  hl=2 l=  32 prim:  INTEGER           :1937197C689C3E511CCB830E5F3677D3FA18F045EBA969739B622B95FEB619C1
   36:d=1  hl=2 l=  33 prim:  INTEGER           :BA1B75CEE9714860639A20B50125C20224CBBEBE01F4CCBFA46731706F55D171

You'll notice that my first integer has length 32 but the second has length 33.

Dumping the raw contents, we get:

$ hd raw
00000000  30 45 02 20 19 37 19 7c  68 9c 3e 51 1c cb 83 0e  |0E. .7.|h.>Q....|
00000010  5f 36 77 d3 fa 18 f0 45  eb a9 69 73 9b 62 2b 95  |_6w....E..is.b+.|
00000020  fe b6 19 c1 02 21 00 ba  1b 75 ce e9 71 48 60 63  |.....!...u..qH`c|
00000030  9a 20 b5 01 25 c2 02 24  cb be be 01 f4 cc bf a4  |. ..%..$........|
00000040  67 31 70 6f 55 d1 71                              |g1poU.q|                   

Let's break it down into the various components:

  • 30 45: SEQUENCE of length 69
  • 02 20: INTEGER of length 32
  • 19 37 19 7c ... 19 c1: the INTEGER contents (32 bytes)
  • 02 21: INTEGER of length 33
  • 00 ba 1b 75 ... d1 71: the INTEGER contents (33 bytes)

This matches exactly what asn1parse tells us. Notice the difference between the first integer (encoded to a length of 32 bytes) and the second (encoded to a length of 33 bytes).

The first one begins with 19 which is 00011001 in binary. Because it does not have a high bit set, this is indeed a positive number.

But the second one begins with BA which is 10111010 in binary. The high bit is set which would be negative if we decoded it as is. Since it isn't negative, the correct two's complement notation has leading zeros.

In your case, both numbers have the high bit set, so they both get 00 prepended.

You end up with a total size of: $2 + 2 + 32+1 + 2 + 32+1 = 72$.

Oops, we're still one short of 73. This is part of the BITSTRING encoding which is defined in the spec, section as:

The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit, the number of unused bits in the final subsequent octet. The number shall be in the range zero to seven.

This is because a BITSTRING can represent a number of bits that is not a multiple of 8. In our case, we're making use of all bits, so the initial octet for the bitstring is 00.

We now have the right length: $72 + 1 = 73$.

| improve this answer | |
  • $\begingroup$ how did you find the reference connections from RFC2986 which defines CSR into X.690, what are those exact links... this references this, then this references that... and so on until reaching X.690 $\endgroup$ – porente Sep 16 at 16:54
  • $\begingroup$ RFC 2986 mentions DER-encoded ASN.1 data multiple times and points to X.690 as the standard for DER encoding of ASN.1 data. I guess I'm not sure what you're asking exactly. $\endgroup$ – Marc Sep 16 at 17:12
  • $\begingroup$ you just answered my comment, I missed reading that RFC2986 part where it defines DER and points to X.690. I always assumed that the conversion of integers into ASN.1 was straight $\endgroup$ – porente Sep 16 at 17:36

I don't have enough reputation to add a comment on the excellent answer my @marc above but you also need to be aware that ASN.1 removes leading zero bytes apart from a single zero byte if required for sign extension.

In the example above, "1937197C689C3E511CCB830E5F3677D3FA18F045EBA969739B622B95FEB619C1" is encoded as 32 bytes.

If it was "001937197C689C3E511CCB830E5F3677D3FA18F045EBA969739B622B95FEB619" then it would be encoded as 31 bytes because the leading zero byte is removed.

Similarly, "00001937197C689C3E511CCB830E5F3677D3FA18F045EBA969739B622B95FEB6" would be would be encoded as 30 bytes, etc.

If you are unpacking ASN.1 data into a fixed buffer then you need to remember this and handle the alignment and sign extension as you unpack it into the fixed-size buffer.

The above leading byte removal means that in your example above, the offset of the data for R and S will vary according to the values of R and S. You must decode the ASN.1 structure properly taking account of the field sizes to correctly determine the position and size of the R and S data.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.