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 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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