1
$\begingroup$

We are currently generating a secp521r1 key as follows using cryptography for Python:

private_key = ec.generate_private_key(ec.SECP521R1(), default_backend())
serialized_private = private_key.private_bytes(
    encoding=DER,
    format=TraditionalOpenSSL,
    encryption_algorithm=NoEncryption()
)

This turns up the following DER file:

3081DB02 01010441 1343B3C8 94ABD7D3 B2CD80C9 872667BB 45406FC4 E342CDAC
543C99AE 51382765 990DF5EF 1A0436BD 771E8F45 9B8DAE82 EBC101A7 DA084666
F1E718D2 F3F5722F A1A00706 052B8104 0023A181 89038186 000401B9 CB8E7F9F
C698CCAF 5E37A0A3 FF418566 6ABD9F9B ACF2B7F5 5F8785AD C3EA9848 FE29FD33
BC1DB969 158BC3AA 0F684AED BF542D19 B3BCB040 7EAAAD04 2ECA9B50 0109AC9E
F94383C3 CE2CCAFB 1F85289C C2C3153F 76018012 EC7B0E0E 8F29A375 01A3557B
EF5E77E0 24CC2A64 31506CAB E215A833 2E437348 64380564 98A8444A C5B6

From this, we get the following ASN.1 tree:

SEQUENCE (4 elem)
    INTEGER 1
    OCTET STRING (65 byte) 13 43 B3 C8 94 AB D7 D3… (PRIVATE KEY)
    [0] (1 elem)
        OBJECT IDENTIFIER 1.3.132.0.35
    [1] (1 elem)
        BIT STRING (1064 bit) 00 04 01 B9 CB 8E 7F 9F... (PUBLIC KEY)

The problem begins when I extract these two octet strings and present them to the Security APIs on OS X and iOS. These APIs expect the raw octet strings as a concatenation of the public, then the private key, with no extraneous markers. However, it seems like these public and private keys I'm getting from what I assume is OpenSSL under the hood are slightly malformed; the public key is prefixed with an extraneous 0x00, while the private key is missing a byte (every one I've generated so far that has the missing byte has that byte set to 0x01, but I can only guess what this value should be when the byte is missing… See (page 12 of SEC2)[http://www.secg.org/sec2-v2.pdf] for why I (probably incorrectly) assume the leading digit might be a 0x01).

From my (very limited) understanding of how SEC1 private keys are expected to be encoded, I would imagine that the private key must be 66 bytes long for 521-bit keys (521 bits == 65.125 bytes), while the public key must be 133 bytes long for 521-bit keys (same 66 bytes for x and y, plus a leading 0x04).

Assuming these keys are properly generated, is it safe to assume the leading 0x00 on the public key can be stripped, and the private key padded with a leading 0x01?

Ps. Maybe this is related? https://www.mail-archive.com/openssl-dev@openssl.org/msg37790.html

$\endgroup$
2
$\begingroup$

Yes, it seems related: the private key just misses a 00 value (not a 01 value) in front. The problem that the I2OS primitive seems to round down instead of up if the value is smaller than 520 bits (65 bytes).

Fortunately the public key seems to be OK. The next byte boundary from 521 is 528 (512 + 16 bits). 2 x 528 = 1056 bits. However, the first 8 bits of the bit string indicate the number of bits that are not used out of the next bits. They are always present, and always set to 00 whenever the bit string is used to represent bytes. So they always need to be skipped (check the bits themselves on the site you link to - you'll see they start with 00000100 which is 04 not 00!).

So basically you get to the following simple scheme: get the private key and add as many 00 bytes to the left until you get 528 bits (66 bytes). For the public key just use it straight away, starting at the 04 (uncompressed point). If it doesn't work then maybe iOS expects the X and Y coordinates directly so strip the 04 away.

$\endgroup$
7
  • $\begingroup$ Why, you ask, would an OCTET STRING not suffice for an octet string? And unfortunately I'd have to indicate that I don't know either. Maybe somebody knows about those murky historical details, but it isn't me. $\endgroup$ – Maarten Bodewes Apr 3 '17 at 20:16
  • $\begingroup$ I'm glad that someone with deeper knowledge can confirm that the leading private key bytes are in fact just 0's (turns out iOS was happy creating the keys with either 00 or 01, which really started the head scratching…) $\endgroup$ – Dimitri Bouniol Apr 3 '17 at 20:37
  • $\begingroup$ I'll try and confirm completely, but I guess you can always sign / verify something yourself as well. If verification succeeds then they key is valid. You could first convert to PKCS#8 using the openssl command line if that parses easier. $\endgroup$ – Maarten Bodewes Apr 3 '17 at 20:38
  • $\begingroup$ As for the public key, are you essentially saying that there will always be the leading 00 as long as it is encoded using ASN.1 because the bit string tag requires it? $\endgroup$ – Dimitri Bouniol Apr 3 '17 at 20:40
  • 1
    $\begingroup$ Funny trick: if you do openssl ec -inform DER -in ecc_key_pkcs1.der -outform DER -out ecc_key_pkcs1_fixed.der then the output will be one byte larger than the input (a byte with value 00 of course) :P $\endgroup$ – Maarten Bodewes Apr 3 '17 at 21:01

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