The CVC key formats

Étienne Millon
Cryptosense tech blog
4 min readFeb 7, 2017

Most articles about RSA present public keys as a pair of numbers: a public exponent e, usually 65537, and a modulus n. But RSA keys are not sent as plain numbers: they are encoded as bytes using a given key format. A common one is described in RFC3447, appendix A.1 and is often called PKCS#1. There are other ones used for example by openssh (“ssh-rsa AAAAB3NzaC1yc2EAA…”).

There is a less common format called CVC, for Card Verifiable Certificates. It is used in electronic passports. The advantage is that it is easier to parse compared to PKCS#1. Information about this format is very scarce, so here is what we learned while adding support for CVC keys to the Cryptosense Analyzer. A parser for RSA and ECDSA public keys can be found in our open source library of key parsers on GitHub.

Structure and types

This format is based on “TLV” triplets: Type, Length, Value. Every piece of CVC keys is:

  • an identifier representing the type of value to expect
  • an encoded length
  • a payload

The payload can itself be a sequence of TLV elements. In the case of a RSA public key, the top level element is of type PUBLIC_KEY (7f 49), and contains a sequence of three elements of type OID (06), MODULUS (81) and EXPONENT (82). In such a sequence, elements are always ordered by tag number.

Length encoding

The length of each value is encoded using the following scheme (note that although this looks like ASN.1 DER, CVC keys are not encoded using DER!):

If the length is 128 or less, then the length field is a single byte representing this length. If the length is 255 or less, the length field is 81 following by the length (on one byte). If the length is 65535 or less, the length field is 82 following by the length (on two bytes, encoded as big endian), etc.

For example:

  • a length of 5 is encoded as 05
  • a length of 250 is encoded as 81 fa
  • a length of 2070 is encoded as 82 08 16

Value encoding

The encoding of a value itself depends on its type: OIDs are encoded as DER, and integers as big endian.

RSA public keys

A public key is a CVC object with a PUBLIC_KEY tag, which contains a sequence of three tags: OID, MODULUS and EXPONENT.

Let’s parse one. The number at the beginning of each column is the offset from the beginning of the key.

0000: PUBLIC_KEY tag         7f 49
0002: length (0x94) 81 94
0004: OID tag 06
0005: length (0x0a) 0a
0006: id-TA-RSA-PSS-SHA-256 04 00 7f 00 07 02 02 02 01 04
0010: MODULUS tag 81
0011: length (0x80) 81 80
0013: modulus value …
0093: EXPONENT tag 82
0094: length (0x03) 03
0095: value (0x010001) 01 00 01

That key is meant to be used for RSA-PSS signatures with the SHA-256 algorithm for hashing. Its public exponent is 65537 and its modulus (omitted here) is 128 bytes long, or 1024 bits. When seeing that such a key is used, the Cryptosense Analyzer would emit a medium severity warning because of the key length: 1024 is too short to use these days. The other parameters (the use of PSS, SHA-256, and the public exponent of 65537) are best practice.

ECDSA keys

Keys to use with ECDSA are a bit more complicated because they contain more fields, but the structure remains the same.

A ECDSA key contains a sequence of 8 tags: OID, MODULUS, COEFFICIENT_A (with the same tag value as EXPONENT), COEFFICIENT_B, BASE_POINT_G, BASE_POINT_R_ORDER, PUBLIC_POINT_Y, and COFACTOR_F.

That seems a lot, but all of this is necessary to define an elliptic curve. Usually, those parameters are not written in full, but rather referenced by name (such as “secp256r1”). More details about the mathematics of elliptic curves on this set of articles by Andrea Corbellini.

0000: PUBLIC_KEY tag 7f 49
0002: length (0x011d) 82 01 1d
0005: OID tag 06
0006: length (0x0a) 0a
0007: id-TA-ECDSA-SHA-256 04 00 7f 00 07 02 02 02 02 03
0011: MODULUS tag 81
0012: length (0x20) 20
0013: value …
0033: COEFFICIENT_A tag 82
0034: length (0x20) 20
0035: value …
0055: COEFFICIENT_B tag 83
0056: length (0x20) 20
0057: value …
0077: BASE_POINT_G tag 84
0078: length (0x41) 41
0079: value …
00ba: BASE_POINT_R_ORDER tag 85
00bb: length (0x20) 20
00bc: value …
00dc: PUBLIC_POINT_Y tag 86
00dd: length (0x41) 41
00de: value …
011f: COFACTOR_F tag 87
0120: length (0x01) 01
0121: value (1) 01

Similarly, this is a key used for ECDSA signatures with SHA-256 for hashing the plaintexts. All these parameters are good practice, but some care is necessary to use ECDSA properly. In particular, it is necessary to generate a random nonce for each signature. If one fails to do so, the private key can be recovered. This is one of the attacks that made it possible to run custom code on the PS3.

In our products we parse a large range of key formats to extract their parameters and rate them. Our key parsers, like most of our backend, are written in OCaml. You can find them on OPAM and on GitHub.

Interested in parsing weird key formats? Let’s talk!

--

--