Distinguished Encoding Rules (DER)

Since this subject is quite complicated, this post only describes the minimum required to implement a personal project. ASN.1 defines a set of rules to specify abstract objects that’s readable by an human. As opposed to BER which defines how to transform the ASN.1 notation into a binary representation. DER is a subset of BER which only gives an unique encoding for a given ASN.1 value.

Encoding

BER encoding is a “type-length-value” encoding like Protocol Buffers or Thrift. This is different to “delimited encoding” format such as JSON which read until they hit an expected delimiter.

For example, to encode the integer 65537 :

typelengthvalue
020301 00 01

DER format

Algorithms usually encode signatures using the DER format, the principal example is X.509 certificates signatures. This section uses an ECDSA signature to illustrate how it works.

Signature ::= SEQUENCE {
	r INTEGER
	s INTEGER
}

So for example, a signature with both r and s equal to 0:

30 06 02 01 00 02 01 00

For a more realistic example, the following snippet uses Java to sign a payload using SHA256WITHECDSAINP1363FORMAT. The signature is first encoded using the P1363 format which simply means that the signature is the concatenation of (r,s). Then the code manually transforms the signature to its DER equivalent and, finally validate the signature. If the signature is valid, it means that the transformation has worked.

One caveat, this code only works most of the time. It sometimes generate an invalid DER signature but, it works often enough to be an usable example. Nimbus’s BigIntegerUtils is also use to reduce the snippet length.

The exact dependency used is com.nimbusds:nimbus-jose-jwt:9.16.1.

import com.nimbusds.jose.util.BigIntegerUtils;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;

public class Der {

  public static void main(String[] args) throws Exception {
    KeyPair keyPair = KeyPairGenerator.getInstance("EC").generateKeyPair();

    var signer = Signature.getInstance("SHA256WITHECDSAINP1363FORMAT");
    signer.initSign(keyPair.getPrivate());
    signer.update("hello world".getBytes(StandardCharsets.UTF_8));
    byte[] signature = signer.sign();

    // Signature using SHA-256 always are 64 bytes length
    byte[] buffer = new byte[32];
    System.arraycopy(signature, 0, buffer, 0, 32);
    var r = BigIntegerUtils.toBytesUnsigned(new BigInteger(buffer));
    System.arraycopy(signature, 32, buffer, 0, 32);
    var s = BigIntegerUtils.toBytesUnsigned(new BigInteger(buffer));

    int derSignatureLength = 6 + s.length + r.length;
    byte[] derSignature = new byte[derSignatureLength];
    derSignature[0] = 0x30; // SEQUENCE
    derSignature[1] = Integer.valueOf(derSignatureLength - 2).byteValue(); // SEQUENCE length
    derSignature[2] = 0x02; // INTEGER
    derSignature[3] = Integer.valueOf(r.length).byteValue(); // INTEGER length
    System.arraycopy(r, 0, derSignature, 4, r.length);
    derSignature[4 + r.length] = 0x02; // INTEGER
    derSignature[5 + r.length] = Integer.valueOf(s.length).byteValue(); // INTEGER length
    System.arraycopy(s, 0, derSignature, 6 + r.length, s.length);

    var signVerify = Signature.getInstance("SHA256WITHECDSA");
    signVerify.initVerify(keyPair.getPublic());
    signVerify.update("hello world".getBytes(StandardCharsets.UTF_8));
    boolean valid = signVerify.verify(derSignature);

    if (valid) {
      System.out.println("Valid");
    } else {
      System.out.println("Invalid");
    }
  }
}

Misc

It’s also interesting to use OpenSSL to see a real example on the whole DER encoding.

openssl req -x509 \
    -newkey rsa:4096 \
    -keyout key.pem \
    -outform der \
    -out cert.crt \
    -sha256 \
    -days 3650 \
    -nodes \
    -subj "/C=XX/ST=State/L=Locality/O=Organization/OU=OrganizationUnit/CN=CommonName"
openssl asn1parse -in cert.crt -inform DER
openssl x509 -in cert.crt -inform DER -text

References