# How are ECC keys stored?

I am trying to figure out what typical formats and sizes, etc. are, for storing ECC public and private keys.

Some quick research turned up X9.62 (to which I don't have access) and SEC1. But then there is also X.690 which includes DER and of course ASN.1. And let's not forget PEM.

If somebody could shed some light onto the relations of these things and what is commonly used I think it would be useful.

Broadly, the question that I am trying to get answered is: What do I need to implement to decently support ECC. That would include ECDH and transferring public keys over the wire to maybe a TLS or SSH implementation but also importing and exporting of keys generated using other tools.

Somewhat related: Public-key format for ECDSA as in FIPS 186-4?

• The format will vary depending on the application, curve, and whether it's a public or private key. EG ECDH Secp256r1 public keys need both X and Y coordinates, while X25519 public keys need only X coordinates. TLS uses ASN.1, SSH doesn't. Libsodium has its own structures. Etc. – SAI Peregrinus Mar 1 at 16:15
• For some applications only a HSM is good enough, for others such as ECDHE as used in TLS, you don't want to store the private key at all; you want to temporarily keep it in RAM and then destroy it at the earliest convenience (to achieve forward security). For the private key the format is somewhat inconsequential as you don't need to communicate it to another party, but you do want to keep it secure (e.g. in a keystore). For the public key you might want to use SubjectPubilcKeyInfo structure. Please make sure parties can trust the public key, or it is useless. – Maarten Bodewes Mar 2 at 0:45
• @SAIPeregrinus I agree, which is basically why I am asking the question. It's hard to get any kind of overview of when to use what. – Elias Mar 2 at 9:35
• Maybe for clarification: I am not interested in volatile or local storage. Only about formats that need to be supported for some kind of interoperability. – Elias Mar 2 at 10:40

transferring public keys over the wire to maybe a TLS or SSH implementation but also importing and exporting of keys generated using other tools.

TLS has its own wire formats for ephemeral publickeys, following the encoding style used in the rest of TLS. For (updated) 1.2 and below see rfc8422 5.4 (and 5.4.1) which has a now-obsolete 1-byte format specifier, a 1-byte curve identifier, and the point in X9.62 uncompressed format for X9/NIST curves (definition restated in 5.4.1) or the fixed formats from rfc7748 for X25519 and X448. For 1.3 this is effectively the same except that format is gone and the 'curve' identifier has been broadened to a 'group' identifier; see rfc8446 4.2.8 and especially 4.2.8.2. For static keys in practice TLS (all versions) uses X.509/PKIX certificates containing SubjectPublicKeyInfo in ASN.1 as described by Conrado. (Technically TLS doesn't require X.509 but in practice no one uses anything else.)

For privatekey, OpenSSL (and lots of things built on it like nodejs PHP python) can use either the SEC1 ASN.1 format (also copied in rfc5915) or PKCS8, usually in PEM only (libcrypto supports both PEM and DER, but libssl makes using PEM easier); most other implementations I know of use PKCS8/rfc5208 and/or PKCS12/rfc7292, the former often DER but sometimes PEM, and the latter always DER. NSS and Windows (schannl) each has its own keystore format but can import and export PKCS12 for 'own' cert-and-privatekey and X.509 for 'other' cert, and Java can do this and until 2017 recommended it.

SSH similarly has its own wire formats for both ephemeral (keyexchange) and static (authentication) publickeys, using SSH's XDR-like encoding. For the 3 most-popular X9/NIST curves, see rfc5656 section 3; after the authentication 'algorithm' name which actually includes the curve name there is one value which is described as the SEC1 publickey (which is the same as X9.62 point with the unnecessary hybrid option removed) and in section 4 similarly each keyexchange method specifies a curve so once a particular method is negotiated the keyexchange messages simply contain the points Q_S and Q_C. For EdDSA authentication rfc8709 again the curve identifier is included in the 'algorithm' name and its single value is the point in the fixed format for Edwards from rfc8032; for Bernstein-X keyexchange rfc8731 is similar but uses the fixed format for Montgomery from rfc7748.

OpenSSH can handle privatekey for X9/NIST as either OpenSSL 'traditional' (i.e. SEC1) or PKCS8 in PEM, or OpenSSH's own 'new' format which looks PEM-like but internally is XDR-style not ASN.1; for Ed25519 it can only use the last (there is no OpenSSL traditional format for Bernstein algorithms). Some other things written to be compatible with OpenSSH can usually do the same, but are not always exactly in sync. One notable exception is Putty (and thus WinSCP and FileZilla), which has its own different 'PPK' format, but the puttygen utility can convert between OpenSSH formats and PPK. I don't know about Tectia, although puttygen can export files it claims to be Tectia-compatible that you might look at.

OpenPGP has its own formats for both static keys (in publication/transport form) and ephemeral publickeys in encrypted messages, using PGP's packet encoding style; for NIST/brainpool see rfc6637 mostly section 9 and 10. It uses an OID (embedded in a PGP field) to identify the curve used and PGP-format 'numbers' for both the public SEC1/X9.62 point and private scalar. GnuPG also supports Ed25519 and X25519, but I have not found any documentation on them; --list-packets shows a public field presumably the point which is 263 bits or 33 bytes, one more than the rfc7748 and rfc8032 formats.

S/MIME (and CMS) uses X.509 certificates for publickeys; implementations may vary as to privatekeys.

XMLDSIG now (v1.1 and v2) defines an explicit representation for ECDSA publickey as an XML structure containing a URI for the curve OID and base64 of what is actually the X9.62/SEC1 uncompressed point, though not identified as such, but an earlier version rfc4050 used XML to represent X,Y separately as numbers for $$F_p$$ curves (and hex for $$F_{2^m}$$ curves, which were initially thought to be a good idea but have now been dropped by nearly everyone). XMLENC v1.1 reuses this for ECDH. However both of these standards support several other ways of providing or referencing a key, including transmitting an actual X.509 cert or providing a reference that allows obtaining or selecting one, which IME are more used (or were some years ago). Privatekeys used in an endpoint can vary.

JSON Web Algorithms (JWA) rfc7518 section 6.2 defines a JSON-based representation for EC publickeys on selected X9/NIST curves, which can be used in JSON Web Keys (JWK) rfc7517 which in turn is used in JSON Web Signature, Encryption, and Tokens. JWK (and thus JWS JWE JWT) can instead use X.509 cert, and here I don't have a feel for which options are chosen in practice, or what endpoints do for privatekeys.

PKCS11 mostly uses the X9.62 encodings for keys, but other hardware devices may vary; however all of them are designed to be local-only interfaces and so may not fall under your criteria.

libsodium (as commented) supports only Ed25519 and X25519 and uses the 'bare' key formats for those; similarly Signal uses only bare X25519. Bitcoin originally used bare X9.62 point and SEC1 number (I2OSP) since it doesn't need any metadata to identify either EC or the curve (always secp256k1); it also created a tweaked-base58-with-checksum encoding for user interfaces, which when applied to a SEC1 privatekey (i.e. an unsigned bigendian number) is called Wallet Import Format (WIF). After the earliest days Bitcoin transitioned to mostly using an 'address' which is a hash of the publickey instead of the actual publickey, and can't be reversed so it's not interesting as a representation. Over time it has also developed more complicated key management schemes which use several different representations to combine an actual EC key with various related data. Other cryptocurrencies I don't know.

Is that enough to get you started?

• No, that's not enough dave! What a terrible answer. Did you invest any time to research this? </irony> 😂 – Elias Mar 3 at 13:37

For the private key, it's common to use PKCS#8, which is a container format - a header specifying the type of the key (RSA? ECC? Which ECC curve?) and the key itself. The key itself is specified in section C.4 of SEC 1. Everything here is encoded in ASN.1 DER (DER is a concrete encoding of the abstract ASN.1 encoding), which can be saved in raw binary format to disk, or in an ASCII format called PEM which has the DER encoded in Base64 and a header/footer. Which one to use (DER/PEM) is up to the application.

You can generate a key in this format by using the openssl genpkey command, e.g. openssl genpkey -algorithm EC -out eckey.pem -pkeyopt ec_paramgen_curve:P-256 -pkeyopt ec_param_enc:named_curve. The PEM header will be "PRIVATE KEY".

Other possible formats are:

• "The key itself" mentioned above directly, without the PKCS#8 header. There is no official name for this format. You can generate a key in this format with the openssl ecparam command. The PEM header will be "EC PRIVATE KEY".
• PKCS#12/PFX which is a container format like PKCS#8, but allows bundling the matching certificate.

The public key usually follows the "Subject Public Key Info" structure (which is a ASN.1 structured used in certificates to include the subject's public key). This is also specified in SEC 1, and you will have the same choice between DER and PEM. It's worth nothing that it's not very usual to store the public key by itself, it's either included with the private key or inside a certificate.

For Edwards and Montgomery curves everything is similar, but the "key itself" is encoded per RFC 8410.

• There is a difference between the PKCS#8 containing a wrapped key and the inner (unencrypted) PKCS#8 structure. PEM may also contain encryption parameters. The PKCS#12 key store can be used to wrap keys in several formats (yay) but it seems mostly a PKCS8ShroudedKeyBag is used, which means the encrypted form of PKCS#8 is inside. I know because I just created such a PKCS#12 structure (yeez, what a mess) :) – Maarten Bodewes Mar 2 at 13:54