Danger

This is a “Hazardous Materials” module. You should ONLY use it if you’re 100% absolutely sure that you know what you’re doing because this module is full of land mines, dragons, and dinosaurs with laser guns.

Tutorial

Note

While usable, these APIs should be considered unstable and not yet subject to our backwards compatibility policy.

The cryptography.hazmat.asn1 module provides a declarative API for working with ASN.1 data. ASN.1 structures are defined as Python classes with type annotations, and the module uses those definitions to serialize and deserialize instances to and from DER-encoded bytes.

Type mapping

The following table shows how ASN.1 types map to Python types:

ASN.1 type

Python type

BOOLEAN

bool

INTEGER

int

BIT STRING

BitString

OCTET STRING

bytes

NULL

Null

OBJECT IDENTIFIER

ObjectIdentifier

UTF8String

str

PrintableString

PrintableString

IA5String

IA5String

UTCTime

UTCTime

GeneralizedTime

GeneralizedTime

SEQUENCE

@sequence-decorated class

SEQUENCE OF

list[T]

SET OF

SetOf[T]

CHOICE

X | Y | ...

ANY

TLV

OPTIONAL

X | None

Defining a SEQUENCE

ASN.1 SEQUENCE types map to Python classes decorated with @sequence. Fields are defined as type annotations. For example, given the following ASN.1 definition:

Point ::= SEQUENCE {
     x      INTEGER,
     y      INTEGER }

The corresponding Python definition is:

>>> from cryptography.hazmat import asn1
>>> @asn1.sequence
... class Point:
...     x: int
...     y: int

The decorator adds an __init__ with keyword-only parameters:

>>> p = Point(x=3, y=7)
>>> p.x
3

Encoding and decoding

Use encode_der() to serialize an ASN.1 object to DER bytes, and decode_der() to deserialize:

>>> from cryptography.hazmat import asn1
>>> @asn1.sequence
... class Point:
...     x: int
...     y: int
>>> encoded = asn1.encode_der(Point(x=1, y=2))
>>> encoded
b'0\x06\x02\x01\x01\x02\x01\x02'
>>> point = asn1.decode_der(Point, encoded)
>>> point.x
1
>>> point.y
2

Primitive types can also be encoded and decoded directly, without wrapping them in a sequence:

>>> asn1.encode_der(42)
b'\x02\x01*'
>>> asn1.decode_der(int, b'\x02\x01*')
42

Nested sequences

Sequences can contain other sequences as field types:

>>> from cryptography.hazmat import asn1
>>> @asn1.sequence
... class Name:
...     value: str
>>> @asn1.sequence
... class Certificate:
...     version: int
...     subject: Name
>>> cert = Certificate(version=1, subject=Name(value="Alice"))
>>> decoded = asn1.decode_der(Certificate, asn1.encode_der(cert))
>>> decoded.subject.value
'Alice'

OPTIONAL fields

A field with a Union[X, None] (or X | None) type annotation is treated as ASN.1 OPTIONAL. When the value is None, the field is omitted from the encoding:

>>> import typing
>>> from cryptography.hazmat import asn1
>>> @asn1.sequence
... class Record:
...     required: int
...     optional: typing.Union[str, None]
>>> asn1.encode_der(Record(required=1, optional="hi"))
b'0\x07\x02\x01\x01\x0c\x02hi'
>>> asn1.encode_der(Record(required=1, optional=None))
b'0\x03\x02\x01\x01'

DEFAULT values

Use Default with typing.Annotated to specify a default value for a field. When encoding, if the field’s value equals the default, it is omitted. When decoding, if the field is absent, the default is used:

>>> from typing import Annotated
>>> from cryptography.hazmat import asn1
>>> @asn1.sequence
... class VersionedRecord:
...     version: Annotated[int, asn1.Default(0)]
...     data: bytes
>>> asn1.encode_der(VersionedRecord(version=1, data=b"\x01"))
b'0\x06\x02\x01\x01\x04\x01\x01'
>>> # version=0 equals the default, so it is omitted from the encoding
>>> asn1.encode_der(VersionedRecord(version=0, data=b"\x01"))
b'0\x03\x04\x01\x01'

CHOICE fields

A field with a Union of multiple non-None types is treated as an ASN.1 CHOICE. Each variant must have a distinct ASN.1 tag:

>>> import typing
>>> from cryptography.hazmat import asn1
>>> @asn1.sequence
... class Example:
...     value: typing.Union[int, bool, str]
>>> asn1.decode_der(Example, asn1.encode_der(Example(value=42))).value
42
>>> asn1.decode_der(Example, asn1.encode_der(Example(value=True))).value
True

When multiple alternatives share the same underlying type, a plain union can’t distinguish them (Union[int, int] is just int). Wrap the types with Variant and add Implicit tags to differentiate between them:

>>> import typing
>>> from typing import Annotated
>>> from cryptography.hazmat import asn1
>>> @asn1.sequence
... class Example:
...     field: typing.Union[
...         Annotated[asn1.Variant[int, typing.Literal["IntA"]], asn1.Implicit(0)],
...         Annotated[asn1.Variant[int, typing.Literal["IntB"]], asn1.Implicit(1)],
...     ]
>>> obj = Example(field=asn1.Variant(9, "IntA"))
>>> decoded = asn1.decode_der(Example, asn1.encode_der(obj))
>>> decoded.field.value
9
>>> decoded.field.tag
'IntA'

EXPLICIT and IMPLICIT tagging

Use Explicit and Implicit annotations to apply ASN.1 context-specific tags to fields:

>>> from typing import Annotated
>>> from cryptography.hazmat import asn1
>>> @asn1.sequence
... class Tagged:
...     explicit_field: Annotated[int, asn1.Explicit(0)]
...     implicit_field: Annotated[int, asn1.Implicit(1)]
>>> encoded = asn1.encode_der(Tagged(explicit_field=5, implicit_field=10))
>>> decoded = asn1.decode_der(Tagged, encoded)
>>> decoded.explicit_field
5
>>> decoded.implicit_field
10

Tagging is typically needed to disambiguate OPTIONAL fields that would otherwise share the same tag:

>>> import typing
>>> from typing import Annotated
>>> from cryptography.hazmat import asn1
>>> @asn1.sequence
... class Example:
...     a: Annotated[typing.Union[int, None], asn1.Implicit(0)]
...     b: Annotated[typing.Union[int, None], asn1.Implicit(1)]
>>> asn1.decode_der(Example, asn1.encode_der(Example(a=9, b=None))).a
9
>>> asn1.decode_der(Example, asn1.encode_der(Example(a=None, b=9))).b
9

SEQUENCE OF and SET OF

Use list[T] for SEQUENCE OF and SetOf for SET OF:

>>> import typing
>>> from cryptography.hazmat import asn1
>>> @asn1.sequence
... class IntList:
...     values: typing.List[int]
>>> decoded = asn1.decode_der(IntList, asn1.encode_der(IntList(values=[1, 2, 3])))
>>> decoded.values
[1, 2, 3]

SET OF elements are sorted in DER encoding:

>>> from cryptography.hazmat import asn1
>>> @asn1.sequence
... class IntSet:
...     values: asn1.SetOf[int]
>>> decoded = asn1.decode_der(IntSet, asn1.encode_der(IntSet(values=asn1.SetOf([3, 1, 2]))))
>>> decoded.values.as_list()
[1, 2, 3]

Size constraints

Use Size to restrict the length of collection and string fields:

>>> import typing
>>> from typing import Annotated
>>> from cryptography.hazmat import asn1
>>> @asn1.sequence
... class BoundedList:
...     values: Annotated[typing.List[int], asn1.Size(min=1, max=4)]
>>> asn1.encode_der(BoundedList(values=[1, 2]))
b'0\x080\x06\x02\x01\x01\x02\x01\x02'

A real-world example

Here is a more complete example modeling the X.509 Validity structure:

Validity ::= SEQUENCE {
     notBefore      Time,
     notAfter       Time  }

Time ::= CHOICE {
     utcTime        UTCTime,
     generalTime    GeneralizedTime }

This translates to:

>>> import typing
>>> import datetime
>>> from typing import Annotated
>>> from cryptography.hazmat import asn1
>>> @asn1.sequence
... class Validity:
...     not_before: typing.Union[asn1.UTCTime, asn1.GeneralizedTime]
...     not_after: typing.Union[asn1.UTCTime, asn1.GeneralizedTime]
>>> not_before = asn1.UTCTime(datetime.datetime(2025, 1, 1, tzinfo=datetime.timezone.utc))
>>> not_after = asn1.UTCTime(datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc))
>>> validity = Validity(not_before=not_before, not_after=not_after)
>>> decoded = asn1.decode_der(Validity, asn1.encode_der(validity))
>>> decoded.not_before.as_datetime().year
2025
>>> decoded.not_after.as_datetime().year
2026