Exploring ANS.1 and Cryptography
2024-05-30

What are ASN.1, pkcs1, pkcs8, pem, and der? How and why are they used in cryptography.

Let us start with some example pseudo code:

// 1. generate cryptrographic key (ex. ecdhe)
let key = ecdhe_handshake();

// 2. generate a BIG_NUM
let big_num_key: BIG_NUM = BN_set_word(key);

// 3.
// asn1_obj = ASN.1: ()
//   - pkcs1: defines the RSA format
//   - pkcs8: defines the key format (RSA, EC, ..)
let asn1_obj = Pkcs8::new(private_key);

// 4: The der encoded ans1 object is simply the raw bytes
let der = asn1_obj.to_bytes();

// 5: The pem encoded asn1 object is the base64 encoding of the raw bytes
//
// pem = cat key.der | base64
let pem = der.to_base64();

In the snippet above we are attempting to encode a private key.

  1. key: secret key generate perhaps from a key exchange
  2. big_num_key: generate a bignum from the secret key
  3. create a pkcs8 encoded ASN.1 object: asn1_obj
  1. Represent the asn1_obj as either der or pem:
    1. der: the raw bytes of the ans1 object
    2. pem: base64 representation of the der ans1 object

What is ASN.1?

First, take a look at the ASN.1 object representation of the certificate of this site

For a detailed dive into ASN.1, I can highly recommend reading the post Warm Welcome to ASN.1 and DER. In summary, ASN.1 is an interface definition language (IDL) (it defines an interface to a system). An IDL needs to be able to do 2 things:

  1. defining data structures (ie. define structure of a certificate)
  2. serialization and deserialization of those data structures

The advantage of writing ASN.1 definitions instead of Go or C definitions is that they are language-independent.

While it is possible to represent a data structure in the language your application is written in (eg. Rust), the ASN.1 representation is language agnostic and can be used across multiple languages (ASN.1 parser implementations are available for multiple languages). I like to use the analogy of Java, which allows you to "write code once, run anywhere". ANS.1 allows your to "define object and serialize in one application, deserialize and reconstruct in any other application".

// C representation
struct point {
  int x, y;
  char label[10];
};

// Go representation
type point struct {
  x, y int
  label string
}

// ASN.1 representation is language agnostic
Point ::= SEQUENCE {
  x INTEGER,
  y INTEGER,
  label UTF8String
}

There are some other languages that do the same things as ASN.1. For instance, Protocol Buffers offer both a language for defining types and a serialization format for encoding objects of the types you’ve defined... ASN.1 (1984) had the significant advantage of already existing when certificates (1988) and HTTPS (1994) were invented.

ASN.1, while not perfect (a ASN.1 parser is complicated to implement), has become the standard in cryptography.

Resources: