Usage¶
Note
You can find a complete example in Examples.
The Python-ASN1 API is exposed by a single Python module named
asn1. The main interface is provided by the following classes:
Encoder: Used to encode ASN.1.Decoder: Used to decode ASN.1.Error: Exception used to signal errors.
Type Mapping¶
The Python-ASN1 encoder and decoder make a difference between primitive and
constructed data types. Primitive data types can be encoded and decoded
directly with read() and write() methods. For these types, ASN.1 types are
mapped directly to Python types and vice versa, as per the table below:
ASN.1 type |
Tag Number |
Decoding |
Encoding |
|---|---|---|---|
Boolean |
0x01 |
bool |
bool |
Integer |
0x02 |
int |
int |
Null |
0x05 |
None |
None |
ObjectIdentifier |
0x06 |
str |
|
Real |
0x09 |
float |
float |
Enumerated |
0x0A |
int |
Because ASN.1 has more data types than Python, the situation arises that one Python
type corresponds to multiple ASN.1 types. In this situation, the to be encoded
ASN.1 type cannot be determined from the Python type. The solution
implemented in Python-ASN1 is that the most frequently used type will be the
implicit default. This is indicated in the Encoding column.
If another type is desired than that must be specified
explicitly through the API.
Some ASN.1 types can be either primitive or constructed. They can be encoded and decoded like primitive types. The following table shows the mapping between ASN.1 types and Python types.
ASN.1 type |
Tag Number |
Decoding |
Encoding |
|---|---|---|---|
BitString |
0x03 |
bytes |
|
OctetString |
0x04 |
bytes |
bytes |
UTF8String |
0x0C |
str |
|
NumericString |
0x12 |
str |
|
PrintableString |
0x13 |
str |
str |
T61String |
0x14 |
str |
|
VideotextString |
0x15 |
str |
|
IA5String |
0x16 |
str |
|
GraphicString |
0x19 |
str |
|
VisibleString |
0x1A |
str |
|
GeneralString |
0x1B |
str |
|
UniversalString |
0x1C |
str |
|
CharacterString |
0x1D |
str |
|
UnicodeString |
0x1E |
str |
For constructed types, there are two possibilities. The first is to treat them as a sequence of types. In this case, the encoder and decoder will automatically map the ASN.1 types to Python types and vice versa.
ASN.1 type |
Tag Number |
Decoding |
Encoding |
|---|---|---|---|
Sequence |
0x10 |
list |
list |
Set |
0x11 |
list |
The second possibility is to treat them as a stack of types. In this approach,
the user needs to explicitly enter/leave the constructed type using the
Encoder.enter() and Encoder.leave() methods of the encoder and the
Decoder.enter() and Decoder.leave() methods of the decoder.
Encoding¶
If you want to encode data and retrieve its DER-encoded representation, use code such as:
import asn1
encoder = asn1.Encoder()
encoder.start()
encoder.write('1.2.3', asn1.Numbers.ObjectIdentifier)
encoded_bytes = encoder.output()
It is also possible to encode data directly to a file or any stream:
import asn1
with open('output.der', 'wb') as f:
encoder = asn1.Encoder()
encoder.start(f)
encoder.write('1.2.3', asn1.Numbers.ObjectIdentifier)
You can encode complex data structures such as sequences and sets:
import asn1
with open('output.der', 'wb') as f:
encoder = asn1.Encoder()
encoder.start(f)
encoder.write(['test1', 'test2', [
1,
0.125,
b'\x01\x02\x03'
]])
ASN.1 types are automatically mapped to Python types.
If you want to precisely specify the ASN.1 type, you have to use the Encoder.enter() and Encoder.leave() methods:
import asn1
with open('output.der', 'wb') as f:
encoder = asn1.Encoder()
encoder.start(f)
encoder.enter(asn1.Numbers.Sequence)
encoder.write('test1', asn1.Numbers.PrintableString)
encoder.write('test2', asn1.Numbers.PrintableString)
encoder.enter(asn1.Numbers.Sequence)
encoder.write(1, asn1.Numbers.Integer)
encoder.write(0.125, asn1.Numbers.Real)
encoder.write(b'\x01\x02\x03', asn1.Numbers.OctetString)
encoder.leave()
encoder.leave()
This also allows to encode data progressively, without having to keep everything in memory.
DER and CER¶
The encoder uses DER (Distinguished Encoding Rules) encoding by default. If you want to use CER (Canonical Encoding Rules) encoding,
you can do so by calling the Encoder.start() method with a stream as argument:
stream = open('output.cer', 'wb')
encoder = asn1.Encoder()
encoder.start(stream)
You can explicitly specify the CER encoding without using a stream:
encoder = asn1.Encoder()
encoder.start(Encoder.CER)
You can explicitly specify the DER encoding when using a stream:
stream = open('output.cer', 'wb')
encoder = asn1.Encoder()
encoder.start(stream, Encoder.DER)
DER has the advantage to be predicatable: there is one and only one way to encode a message using DER. DER is commonly used in security-related applications such as X.509 digital certificates. DER uses definite lengths for all encoded messages. This means that the length of the encoded message is known in advance. This is useful for encoding messages that are fixed in size or that do not change in size over time. The disadvantage of DER is that the encoded binary data are kept in memory until the encoding is finished. This can be a problem for very large messages.
CER is similar to DER, but it uses indefinite lengths. This means that the length of the encoded message is not known in advance. This is useful for encoding messages that may be very large or that may change in size over time. The advantage of CER is that the encoded binary data are not kept in memory until the encoding is finished. This means that the encoder can start writing the encoded message to a file or a stream as soon as it is available.
IMPORTANT: There was a mistake in version 3.0.0 of Python-ASN1 where the encoder used CER encoding by default contrary to the previous versions that were using DER. This was fixed in version 3.0.1: CER is the default encoding when using a stream. Otherwise, DER is the default encoding.
Decoding¶
If you want to decode ASN.1 from BER (DER, CER, …) encoded bytes, use code such as:
import asn1
decoder = asn1.Decoder()
decoder.start(encoded_bytes)
tag, value = decoder.read()
It is also possible to decode data directly from a file or any stream:
import asn1
with open('input.der', 'rb') as f:
decoder = asn1.Decoder()
decoder.start(f)
tag, value = decoder.read()
You can decode complex data structures. The decoder will automatically map ASN.1 types to Python types:
import asn1
with open('example7.der', 'rb') as f:
decoder = asn1.Decoder()
decoder.start(f)
tag, value = decoder.read()
print(tag)
pprint.pprint(value)
You can ask the decoder to return the number of unused bits when decoding a BitString:
import asn1
encoded = b'\x23\x0C\x03\x02\x00\x0B\x03\x02\x00\x0B\x03\x02\x04\x0F'
decoder = asn1.Decoder()
decoder.start(encoded)
tag, (value, unused) = decoder.read(asn1.ReadFlags.WithUnused)
print('Tag: ', tag)
print('Value: ', value)
print('Unused bits: ', unused)
The flag ReadFlags.WithUnused can be used with any ASN.1 type. When used, the read method will return a tuple with the value and the number of unused bits.
If the type is not a BitString, the number of unused bits is always 0.
Constants¶
A few constants are defined in the asn1 module. The
constants immediately below correspond to ASN.1 tag numbers.
They can be used as the nr parameter of the
Encoder.write() method, and are returned as the
first part of a (nr, typ, cls) tuple as returned by
Decoder.peek() and
Decoder.read().
Constant |
Value (hex) |
|---|---|
Numbers.Boolean |
0x01 |
Numbers.Integer |
0x02 |
Numbers.BitString |
0x03 |
Numbers.OctetString |
0x04 |
Numbers.Null |
0x05 |
Numbers.ObjectIdentifier |
0x06 |
Numbers.ObjectDescriptor |
0x07 |
Numbers.External |
0x08 |
Numbers.Real |
0x09 |
Numbers.Enumerated |
0x0a |
Numbers.EmbeddedPDV |
0x0b |
Numbers.UTF8String |
0x0c |
Numbers.RelativeOID |
0x0d |
Numbers.Time |
0x0e |
Numbers.Sequence |
0x10 |
Numbers.Set |
0x11 |
Numbers.NumericString |
0x12 |
Numbers.PrintableString |
0x13 |
Numbers.T61String |
0x14 |
Numbers.VideotextString |
0x15 |
Numbers.IA5String |
0x16 |
Numbers.UTCTime |
0x17 |
Numbers.GeneralizedTime |
0x18 |
Numbers.GraphicString |
0x19 |
Numbers.VisibleString |
0x1a |
Numbers.GeneralString |
0x1b |
Numbers.UniversalString |
0x1c |
Numbers.CharacterString |
0x1d |
Numbers.UnicodeString |
0x1e |
Numbers.Date |
0x1f |
Numbers.TimeOfDay |
0x20 |
Numbers.DateTime |
0x21 |
Numbers.Duration |
0x22 |
Numbers.OIDinternationalized |
0x23 |
Numbers.RelativeOIDinternationalized |
0x24 |
The following constants define the two available encoding types (primitive
and constructed) for ASN.1 data types. As above they can be used with the
Encoder.write() and are returned by
Decoder.peek() and
Decoder.read().
Constant |
Value (hex) |
|---|---|
Types.Constructed |
0x20 |
Types.Primitive |
0x00 |
Finally the constants below define the different ASN.1 classes. As above
they can be used with the Encoder.write() and are
returned by Decoder.peek() and
Decoder.read().
Constant |
Value (hex) |
|---|---|
Classes.Universal |
0x00 |
Classes.Application |
0x40 |
Classes.Context |
0x80 |
Classes.Private |
0xc0 |