Error message
asn1: structure error: tags don't match
The full message appends the expected and found tags plus field context, e.g. "tags don't match (16 vs {class:0 tag:2 ...}) ... AlgorithmIdentifier @2". Truncated inputs produce the sibling "asn1: syntax error: data truncated".
Go encoding/asn1 — surfaced through crypto/x509 parsers
What it means
Go's DER decoder found a different ASN.1 tag than the target structure requires — you are parsing valid-ish DER into the wrong Go structure, or parsing something that is not DER at all. The numbers in the message are ASN.1 universal tags (16 = SEQUENCE, 2 = INTEGER, 19 = PrintableString), which usually identify the mixup precisely.
Why it happens
PEM bytes passed to a DER parser
commonx509.ParseCertificate, ParsePKIXPublicKey and friends take raw DER. Handing them PEM text (dashes and base64) without pem.Decode first is the single most common trigger.
Wrong parser for the key container
commonPKCS#8 bytes fed to ParseECPrivateKey (which expects SEC1), SEC1 fed to ParsePKCS8PrivateKey, a private key fed to ParsePKIXPublicKey — each container has a distinct structure, and the tag check catches the swap at the first divergent field.
Not a certificate/key at all
occasionalA raw 64-byte signature, a JWT, or protocol framing bytes parsed as ASN.1. If the input starts with anything but 0x30 (SEQUENCE), no X.509-family parser will accept it.
How to fix it
- 1.
Always pem.Decode first
Extract the DER block (and check the label) before calling any parser.
go block, _ := pem.Decode(pemBytes) if block == nil { return fmt.Errorf("input is not PEM") } cert, err := x509.ParseCertificate(block.Bytes) // block.Type == "CERTIFICATE" - 2.
Try the container parsers in order
When the private-key container is unknown, attempt PKCS#8, then SEC1, then PKCS#1 — the standard triage for keys from mixed sources.
go if k, err := x509.ParsePKCS8PrivateKey(der); err == nil { return k, nil } if k, err := x509.ParseECPrivateKey(der); err == nil { return k, nil } if k, err := x509.ParsePKCS1PrivateKey(der); err == nil { return k, nil } return nil, errors.New("unrecognized private key format") - 3.
Look at the first bytes
One hexdump line tells you whether you even hold DER, and openssl asn1parse shows the structure to match against the parser you chose.
bash head -c 16 key.der | xxd # 30 81/82 ... → SEQUENCE, plausibly DER openssl asn1parse -inform der -in key.der | head -3