How to Parse ASN.1 DER — Distinguished Encoding Rules Walkthrough
ASN.1 DER (Distinguished Encoding Rules) is the binary encoding behind X.509 certificates, PKCS keys, FIDO2 attestation signatures, SNMP, LDAP, and GlobalPlatform secure messaging. Every DER structure is a TLV (Tag-Length-Value) triple with very specific encoding rules. This guide covers tag class decoding, constructed vs primitive, length encoding (short/long/indefinite), OID parsing, and practical X.509 certificate dissection.
1. DER Tag Byte — First Byte Encoding
Tag byte (8 bits): Bits 7-6: Tag Class 00 = UNIVERSAL (built-in types: INTEGER, OCTET STRING, SEQUENCE...) 01 = APPLICATION (protocol-specific) 10 = CONTEXT-SPECIFIC (tagged types) 11 = PRIVATE (organization-specific) Bit 5: Constructed flag 0 = Primitive (value IS the data) 1 = Constructed (value CONTAINS more TLV structures) Bits 4-0: Tag Number 0-30: Direct encoding 31 (0x1F): Tag number continues in subsequent bytes
2. Common UNIVERSAL Tag Numbers
| Tag (hex) | Name | Constructed? | Used In |
|---|---|---|---|
| 0x01 | BOOLEAN | Primitive | True/False fields |
| 0x02 | INTEGER | Primitive | Serial numbers, RSA moduli, version numbers |
| 0x03 | BIT STRING | Prim/Constr | Public keys, signatures, flags |
| 0x04 | OCTET STRING | Prim/Constr | Raw byte arrays, encrypted data |
| 0x05 | NULL | Primitive | Algorithm parameters = NULL |
| 0x06 | OBJECT IDENTIFIER | Primitive | OIDs: 1.2.840.113549.1.1.1 (RSA) |
| 0x0A | ENUMERATED | Primitive | Named integer values |
| 0x0C | UTF8String | Primitive | Human-readable text |
| 0x13 | PrintableString | Primitive | DN components (CN, O, C) |
| 0x16 | IA5String | Primitive | ASCII strings (email, URL) |
| 0x17 | UTCTime | Primitive | YYMMDDHHMMSSZ format |
| 0x18 | GeneralizedTime | Primitive | YYYYMMDDHHMMSSZ format |
| 0x30 | SEQUENCE | Constructed | X.509 certs, PKCS structures |
| 0x31 | SET | Constructed | Attribute sets, RDNs |
3. Length Encoding — Short, Long, and Indefinite
# Short form: length byte bit 7 = 0, bits 6-0 = length (0-127) 0x0A → length = 10 bytes 0x7F → length = 127 bytes # Long form: length byte bit 7 = 1, bits 6-0 = number of subsequent length bytes 0x81 0xA0 → 1 byte follows, length = 160 0x82 0x01 0x00 → 2 bytes follow, length = 256 0x83 0x01 0x00 0x00 → 3 bytes follow, length = 65536 # Indefinite form (BER only, not DER): length byte = 0x80 # Terminated by two zero bytes (0x00 0x00). DER forbids indefinite length.
4. DER vs BER vs CER
| Rule | BER | DER | CER |
|---|---|---|---|
| Length form | Any | Shortest possible | Shortest possible |
| Indefinite length | Allowed | Forbidden | Allowed (constructed only) |
| BOOLEAN encoding | Any non-zero | 0x00 or 0xFF only | 0x00 or 0xFF only |
| BIT STRING padding | Any | Minimum required | Minimum required |
| SET ordering | Any | DER-sorted | CER-sorted |
| Use case | Streaming protocols | Digital signatures, certs | Large data (CMS, S/MIME) |
5. OID — Object Identifier Decoding
# OID encoding: first two components encoded in first byte
# byte 0 = (OID_component_1 * 40) + OID_component_2
# Subsequent components: base-128 with high bit as continuation flag
def decode_oid(data):
"""Decode OID from DER bytes."""
oid = []
# First byte: component 1 and 2
oid.append(data[0] // 40)
oid.append(data[0] % 40)
i = 1
while i < len(data):
value = 0
while True:
value = (value << 7) | (data[i] & 0x7F)
if data[i] & 0x80 == 0:
break
i += 1
oid.append(value)
i += 1
return '.'.join(str(v) for v in oid)
# Example: 2A 86 48 86 F7 0D 01 01 01
# → 1.2.840.113549.1.1.1 (rsaEncryption)
6. Dissecting an X.509 Certificate
# X.509 cert = SEQUENCE {
# TBSCertificate SEQUENCE { version, serial, signature algo, issuer, validity, subject, public key, extensions }
# signatureAlgorithm SEQUENCE { OID, params }
# signatureValue BIT STRING
# }
# DER dump (hex):
# 30 82 03 21 ← SEQUENCE, length=0x0321 (801 bytes) — the whole certificate
# 30 82 02 09 ← SEQUENCE — TBSCertificate
# A0 03 ← [0] tagged (version), length=3
# 02 01 02 ← INTEGER v3 (0x02)
# 02 09 ← INTEGER (serial number, 9 bytes)
# 00 C0 FF EE ... 08
# 30 0D ← SEQUENCE (signature algo)
# 06 09 2A 86 48 86 F7 0D 01 01 0B ← OID: sha256WithRSAEncryption
# 05 00 ← NULL
# 30 19 ← SEQUENCE (issuer DN)
# 31 0B 30 09 06 03 55 04 06 13 02 55 53 ← SET { SEQUENCE { OID=countryName, "US" } }
# ...
Test this yourself: Our ASN.1 DER Parser decodes any DER hex dump into a tree view — paste raw DER bytes and see each tag, class, length, and nested structure. Works for X.509, PKCS, FIDO2 sigs, and proprietary smart card BER-TLV.
7. Common ASN.1 Parsing Pitfalls
| Issue | Cause | Fix |
|---|---|---|
| Tag byte misread | High tag numbers use multiple bytes | Check bits 4-0 = 0x1F → subsequent bytes are tag continuation |
| Length byte > 127 decoded as short form | Long form used for length > 127 | Check bit 7 — if set, number of subsequent bytes = value & 0x7F |
| Indefinite length in DER | BER data fed to DER parser | DER forbids indefinite length; use BER parser for streaming data |
| Constructed OCTET STRING not recursed | Bit 5=1 means value contains nested TLVs | Always check constructed bit before treating value as raw bytes |
Related Tools
ASN.1 DER Parser — Decode DER hex to tree view | FIDO2 Parser — Parse attestation signatures (DER-encoded) | Checksum Verifier — Verify DER structure integrity | PC/SC Programming Guide