ASN.1 (Abstract Syntax Notation One) is a schema language used to describe structured data. DER (Distinguished Encoding Rules) is a strict, canonical binary encoding for ASN.1 values. DER shows up everywhere in security and networking—X.509 certificates, PKCS#8 private keys, CMS/PKCS#7, OCSP, CRLs, and many other formats.
Decoding DER becomes straightforward once you understand its core model: TLV (Tag–Length–Value), parsed recursively for nested structures.
1) ASN.1 vs DER: What You’re Actually Decoding
- ASN.1 defines types (e.g., INTEGER, SEQUENCE, OBJECT IDENTIFIER) and structures (e.g., “a certificate is a SEQUENCE of fields…”).
- DER defines exactly how an ASN.1 value becomes bytes, using TLV and additional canonical constraints.
DER is a subset of BER (Basic Encoding Rules). BER allows multiple encodings for the same value; DER eliminates ambiguity by enforcing a single canonical encoding. That’s why DER is used for cryptographic signatures—if multiple encodings were valid, two parties might “sign different bytes” while representing the “same data.”
2) DER Is TLV: Tag, Length, Value
Every DER element is:
- Identifier octet(s) (Tag)
- Length octet(s)
- Content octet(s) (Value)
For constructed types (like SEQUENCE), the “Value” is itself a concatenation of inner TLVs.
3) Step 1: Parse the Tag (Identifier Octets)
The first octet (and sometimes more) encodes:
3.1 Tag Class (2 bits)
00= Universal (standard ASN.1 types)01= Application10= Context-specific (very common in X.509)11= Private
3.2 Primitive vs Constructed (1 bit)
0= Primitive (content is raw bytes for the type)1= Constructed (content contains nested TLVs)
3.3 Tag Number (5 bits, or more)
- If the lower 5 bits are
0..30, that is the tag number. - If they are
31(0x1F), the tag number continues in “high-tag-number form” across additional octets (base-128 continuation format).
Common Universal tag numbers
0x02INTEGER0x03BIT STRING0x04OCTET STRING0x05NULL0x06OBJECT IDENTIFIER0x0CUTF8String0x10SEQUENCE (constructed)0x11SET (constructed)0x13PrintableString0x17UTCTime (UTC indicated by trailingZ)0x18GeneralizedTime (UTC indicated by trailingZ)
Example: 0x30
Binary 0011 0000:
- Class
00= Universal - Constructed bit
1 - Tag number
10000(16) = SEQUENCE
So0x30is “SEQUENCE (constructed)”.
4) Step 2: Parse the Length
DER length has two forms:
4.1 Short form
If the first length octet is < 0x80, that octet is the length.
Example: 0x0A means 10 bytes of content follow.
4.2 Long form
If the first length octet is >= 0x80, the low 7 bits say how many subsequent length bytes follow.
0x81 nn→ length isnn0x82 nn nn→ length is two bytes (big-endian)- and so on…
Example: 0x82 0x01 0xF4 → length = 0x01F4 = 500 bytes.
DER constraints (important when validating)
- Indefinite length is not allowed in DER (that’s BER/CER territory).
- Length must be encoded in the shortest possible way (no leading zero length bytes).
5) Step 3: Parse the Value (Content Octets)
How you interpret the content depends on (class, tag number, primitive/constructed). For constructed types, you recursively decode child TLVs until you consume exactly the stated length.
6) Worked Example: Decode a Simple DER Structure by Hand
Let’s decode this hex:
30 0A 02 01 05 04 05 68 65 6C 6C 6F
6.1 Outer element
30→ SEQUENCE (constructed)0A→ length = 10 bytes
So the SEQUENCE content is the next 10 bytes:
02 01 05 04 05 68 65 6C 6C 6F
6.2 First child
02→ INTEGER (primitive)01→ length = 105→ value bytes
INTEGER in DER is two’s-complement big-endian. 0x05 = 5.
So child #1 is: INTEGER 5.
6.3 Second child
04→ OCTET STRING (primitive)05→ length = 568 65 6C 6C 6F→ ASCII “hello”
So child #2 is: OCTET STRING "hello".
Decoded structure
- SEQUENCE
- INTEGER: 5
- OCTET STRING: “hello”
This “read tag → read length → read value” approach is the universal recipe.
7) Decoding Key Types Correctly (Where People Trip Up)
7.1 INTEGER (0x02)
- Two’s complement, big-endian.
- DER requires the minimal number of bytes:
- Positive values must not have unnecessary leading
0x00. - But if the highest bit would be 1 (making it look negative), a single leading
0x00is required.
- Positive values must not have unnecessary leading
Example: value 128 (0x80) must be encoded as 00 80 (so it remains positive).
7.2 BIT STRING (0x03)
Content format:
- First content byte = number of unused bits in the last byte (0–7)
- Remaining bytes = the bitstring payload
This matters in certificates (e.g., SubjectPublicKeyInfo contains a BIT STRING).
7.3 OBJECT IDENTIFIER (0x06)
OID encoding rules:
- First byte encodes the first two arcs:
40 * arc0 + arc1- Valid arc0 is 0, 1, or 2
- arc1 range depends on arc0
- Remaining arcs are base-128 encoded with continuation bits.
You don’t need to memorize; just remember it’s variable-length base-128 and decode each arc.
7.4 UTCTime (0x17) and GeneralizedTime (0x18)
- UTCTime typically:
YYMMDDHHMMSSZ(Z indicates UTC) - GeneralizedTime:
YYYYMMDDHHMMSSZ
DER has strict rules about formatting (including timezone indication and allowed fractions), so a decoder that validates DER must enforce those constraints.
7.5 Context-specific tags (very common)
In X.509, you’ll see tags like A0, A1, A2…:
- Class
10(context-specific) - Constructed bit often set
- Tag number in the low bits
Example: A0 = context-specific, constructed, tag 0
A decoder must treat these according to the schema (e.g., Certificate’s version field is [0] EXPLICIT Version).
8) Practical Decoding with Tools
8.1 OpenSSL: quick inspection
If you have a DER file (e.g., cert.der):
openssl asn1parse -inform DER -in cert.der -i
Helpful flags:
-iindents nested structures-dumpdumps raw content (can be large)
If you have PEM, OpenSSL can often detect or you can convert:
openssl x509 -in cert.pem -outform DER -out cert.der
8.2 dumpasn1: very readable trees
dumpasn1 (Peter Gutmann) is popular because it prints a clear tree and can use known templates for common structures.
8.3 Wireshark
For protocols embedding ASN.1 (LDAP, SNMP, some telecom stacks), Wireshark can dissect and show ASN.1 fields directly.
9) Programmatic Decoding (Safe, Repeatable)
9.1 Python (pyasn1): decode raw DER
from pyasn1.codec.der import decoderfrom pyasn1.type import univdata = bytes.fromhex("300A020105040568656C6C6F")value, rest = decoder.decode(data, asn1Spec=univ.Sequence())print(value) # parsed sequenceprint(rest) # should be b'' if fully consumed
Notes:
- If you know the schema, provide an
asn1Specmatching your structure for meaningful field decoding. - Always check
restto ensure you consumed exactly one DER object.
9.2 Python (asn1crypto): great for X.509 and PKCS*
For certificates/keys, schema-aware libraries are easier than generic TLV parsing.
from asn1crypto import x509with open("cert.der", "rb") as f: cert = x509.Certificate.load(f.read())print(cert.subject.human_friendly)print(cert.issuer.human_friendly)
9.3 Go (encoding/asn1): strong for simple schemas
package mainimport ( "encoding/asn1" "fmt")type Example struct { N int S []byte}func main() { data := []byte{0x30, 0x0A, 0x02, 0x01, 0x05, 0x04, 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F} var ex Example _, err := asn1.Unmarshal(data, &ex) if err != nil { panic(err) } fmt.Printf("%+v\n", ex)}
10) Security and Robustness: Don’t Just “Parse,” Validate
ASN.1 decoders have historically been a rich source of vulnerabilities. When decoding untrusted DER:
- Enforce maximum length limits (avoid allocating huge buffers).
- Enforce maximum recursion depth (avoid stack exhaustion).
- Reject indefinite length (not DER).
- Enforce DER minimality rules if you’re verifying signatures or comparing canonical data.
- Ensure you consume the entire object (no trailing bytes unless your format explicitly allows concatenation).
For cryptographic workflows, canonical DER is not a nicety—it’s essential to avoid signature malleability and parsing ambiguities.
11) A Repeatable Manual Decoding Checklist
- Read identifier octet(s):
- class, constructed/primitive, tag number
- Read length:
- short vs long form
- Slice out exactly that many content bytes
- If constructed:
- decode children until content is exhausted
- Interpret primitive types according to tag rules
- Validate DER constraints if required (no indefinite, minimal lengths, canonical forms)
Conclusion
Decoding ASN.1 DER is mainly disciplined TLV parsing plus strict canonical rules. Once you can confidently parse tag and length, everything else is either recursion (constructed types) or type-specific interpretation (INTEGER, OID, BIT STRING, times, strings). For real-world objects like certificates and keys, pair your TLV understanding with schema-aware tooling (OpenSSL, asn1parse, asn1crypto, pyasn1) so you can validate and interpret fields safely and correctly.