| Example use case | |
| ================ | |
| .. toctree:: | |
| :maxdepth: 2 | |
| To briefly explain how to approach pyasn1, consider a quick workflow example. | |
| Grab ASN.1 schema for SSH keys | |
| ------------------------------ | |
| ASN.1 is widely used in many Internet protocols. Frequently, whenever ASN.1 is employed, | |
| data structures are described in ASN.1 schema language right in the RFC. | |
| Take `RFC2437 <https://www.ietf.org/rfc/rfc2437.txt>`_ for example -- we can look into | |
| it and weed out data structures specification into a local file: | |
| .. code-block:: python | |
| # pkcs-1.asn | |
| PKCS-1 {iso(1) member(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) modules(0) pkcs-1(1)} | |
| DEFINITIONS EXPLICIT TAGS ::= BEGIN | |
| RSAPrivateKey ::= SEQUENCE { | |
| version Version, | |
| modulus INTEGER, | |
| publicExponent INTEGER, | |
| privateExponent INTEGER, | |
| prime1 INTEGER, | |
| prime2 INTEGER, | |
| exponent1 INTEGER, | |
| exponent2 INTEGER, | |
| coefficient INTEGER | |
| } | |
| Version ::= INTEGER | |
| END | |
| Compile ASN.1 schema into Python | |
| -------------------------------- | |
| In the best case, you should be able to automatically compile ASN.1 spec into | |
| Python classes. For that purpose we have the `asn1ate <https://github.com/kimgr/asn1ate>`_ | |
| tool: | |
| .. code-block:: bash | |
| $ asn1ate pkcs-1.asn > rsakey.py | |
| Though it may not work out as, as it stands now, asn1ate does not support | |
| all ASN.1 language constructs. | |
| Alternatively, you could check out the `pyasn1-modules <https://github.com/etingof/pyasn1-modules>`_ | |
| package to see if it already has the ASN.1 spec you are looking for compiled and shipped | |
| there. Then just install the package, import the data structure you need and use it: | |
| .. code-block:: bash | |
| $ pip install pyasn1-modules | |
| As a last resort, you could express ASN.1 in Python by hand. The end result | |
| should be a declarative Python code resembling original ASN.1 syntax like | |
| this: | |
| .. code-block:: python | |
| # rsakey.py | |
| class Version(Integer): | |
| pass | |
| class RSAPrivateKey(Sequence): | |
| componentType = NamedTypes( | |
| NamedType('version', Version()), | |
| NamedType('modulus', Integer()), | |
| NamedType('publicExponent', Integer()), | |
| NamedType('privateExponent', Integer()), | |
| NamedType('prime1', Integer()), | |
| NamedType('prime2', Integer()), | |
| NamedType('exponent1', Integer()), | |
| NamedType('exponent2', Integer()), | |
| NamedType('coefficient', Integer()) | |
| ) | |
| Read your ~/.ssh/id_rsa | |
| ----------------------- | |
| Given we've put our Python classes into the `rsakey.py` module, we could import | |
| the top-level object for SSH keys container and initialize it from our | |
| `~/.ssh/id_rsa` file (for sake of simplicity here we assume no passphrase is | |
| set on the key file): | |
| .. code-block:: python | |
| from base64 import b64decode | |
| from pyasn1.codec.der.decoder import decode as der_decoder | |
| from rsakey import RSAPrivateKey | |
| # Read SSH key from file (assuming no passphrase) | |
| with open('.ssh/id_rsa') as key_file: | |
| b64_serialisation = ''.join(key_file.readlines()[1:-1]) | |
| # Undo BASE64 serialisation | |
| der_serialisation = b64decode(b64_serialisation) | |
| # Undo DER serialisation, reconstruct SSH key structure | |
| private_key, rest_of_input = der_decoder(der_serialisation, asn1Spec=RSAPrivateKey()) | |
| Once we have Python ASN.1 structures initialized, we could inspect them: | |
| .. code-block:: pycon | |
| >>> print('%s' % private_key) | |
| RSAPrivateKey: | |
| version=0 | |
| modulus=280789907761334970323210643584308373... | |
| publicExponent=65537 | |
| privateExponent=1704567874679144879123080924... | |
| prime1=1780178536719561265324798296279384073... | |
| prime2=1577313184995269616049017780493740138... | |
| exponent1=1193974819720845247396384239609024... | |
| exponent2=9240965721817961178848297404494811... | |
| coefficient=10207364473358910343346707141115... | |
| Play with the keys | |
| ------------------ | |
| As well as use them nearly as we do with native Python types: | |
| .. code-block:: pycon | |
| >>> pk = private_key | |
| >>> | |
| >>> pk['prime1'] * pk['prime2'] == pk['modulus'] | |
| True | |
| >>> pk['prime1'] == pk['modulus'] // pk['prime2'] | |
| True | |
| >>> pk['exponent1'] == pk['privateExponent'] % (pk['prime1'] - 1) | |
| True | |
| >>> pk['exponent2'] == pk['privateExponent'] % (pk['prime2'] - 1) | |
| True | |
| Technically, pyasn1 classes `emulate <https://docs.python.org/3/reference/datamodel.html#emulating-container-types>`_ | |
| Python built-in types. | |
| Transform to built-ins | |
| ---------------------- | |
| ASN.1 data structures exhibit a way more complicated behaviour compared to | |
| Python types. You may wish to simplify things by turning the whole tree of | |
| pyasn1 objects into an analogous tree made of base Python types: | |
| .. code-block:: pycon | |
| >>> from pyasn1.codec.native.encoder import encode | |
| >>> ... | |
| >>> py_private_key = encode(private_key) | |
| >>> py_private_key | |
| {'version': 0, 'modulus': 280789907761334970323210643584308373, 'publicExponent': 65537, | |
| 'privateExponent': 1704567874679144879123080924, 'prime1': 1780178536719561265324798296279384073, | |
| 'prime2': 1577313184995269616049017780493740138, 'exponent1': 1193974819720845247396384239609024, | |
| 'exponent2': 9240965721817961178848297404494811, 'coefficient': 10207364473358910343346707141115} | |
| You can do vice-versa: initialize ASN.1 structure from a dict: | |
| .. code-block:: pycon | |
| >>> from pyasn1.codec.native.decoder import decode | |
| >>> py_private_key = {'modulus': 280789907761334970323210643584308373} | |
| >>> private_key = decode(py_private_key, asn1Spec=RSAPrivateKey()) | |
| Write it back | |
| ------------- | |
| Possibly not that applicable to the SSH key example, but you can of course modify | |
| any part of the ASN.1 data structure and serialise it back into the same or other | |
| wire representation: | |
| .. code-block:: python | |
| from pyasn1.codec.der.encoder import encode as der_encoder | |
| # Serialise SSH key data structure into DER stream | |
| der_serialisation = der_encoder(private_key) | |
| # Serialise DER stream into BASE64 stream | |
| b64_serialisation = '-----BEGIN RSA PRIVATE KEY-----\n' | |
| b64_serialisation += b64encode(der_serialisation) | |
| b64_serialisation += '-----END RSA PRIVATE KEY-----\n' | |
| with open('.ssh/id_rsa.new', 'w') as key_file: | |
| key_file.write(b64_serialisation) | |