Parse and pack OpenSSH private and public key files
Project description
openssh_key_parser
This repository provides openssh_key
, a Python package providing utilities to
parse and pack OpenSSH private and public key files.
It supports parsing and packing "ssh-rsa" and "ssh-ed25519" keys, with or without "aes256-ctr" encryption under the "bcrypt" KDF. It is compliant with the OpenSSH private key vendor extension, in particular supporting multiple keys in a private key file.
Keys can be converted to corresponding classes provided by:
Check out the documentation.
Example
See what is in an OpenSSH key file
$ ssh-keygen -t ed25519 -N secret_passphrase -C my_comment -f test_id_ed25519
Generating public/private ed25519 key pair.
Your identification has been saved in test_id_ed25519.
Your public key has been saved in test_id_ed25519.pub.
The key fingerprint is:
SHA256:NbhmjL1RfNkYWOb63Rq2ugIzsmr9zLoSsn5ZUPa3Qic my_comment
The key's randomart image is:
+--[ED25519 256]--+
| o+ |
| o o.o = |
| o .. = = . |
| . +E+o+ |
| ...S+.. |
| . . oo=o.. . . |
| o = o.= . + .|
| . = oo . . + |
| ..o.oo++ .ooo |
+----[SHA256]-----+
$ python -m openssh_key test_id_ed25519 --passphrase secret_passphrase
{
"data": [
[
{
"header": {
"key_type": "ssh-ed25519"
},
"params": {
"data": {
"public": "b'\\xd0\\x96\\x7f\\xcd\\x02K\\x8e\\xfe)\\xc1\\xd1p\\x00\\xbd\\xcf\\xe3\\xf6\\xe8\\x91\\xc9\\x84\\xf5\\x9e\\xacL\\xe0\\x9c/2i8R'"
}
},
"footer": {},
"clear": {}
},
{
"header": {
"key_type": "ssh-ed25519"
},
"params": {
"data": {
"public": "b'\\xd0\\x96\\x7f\\xcd\\x02K\\x8e\\xfe)\\xc1\\xd1p\\x00\\xbd\\xcf\\xe3\\xf6\\xe8\\x91\\xc9\\x84\\xf5\\x9e\\xacL\\xe0\\x9c/2i8R'",
"private_public": "b'\\x99\\x08;#\\x07\\xb970\\xc3\\xeb\\\\\\x0e\\xe4\\xc1\\x1a\\xd4\\x12\\xa6\\x05\\x88v\\xae\\x9e9\\xc28\\x1a\\xb8\\x92b0\\x8c\\xd0\\x96\\x7f\\xcd\\x02K\\x8e\\xfe)\\xc1\\xd1p\\x00\\xbd\\xcf\\xe3\\xf6\\xe8\\x91\\xc9\\x84\\xf5\\x9e\\xacL\\xe0\\x9c/2i8R'"
}
},
"footer": {
"comment": "my_comment"
},
"clear": {}
}
]
],
"byte_string": "b'openssh-key-v1\\x00\\x00\\x00\\x00\\naes256-ctr\\x00\\x00\\x00\\x06bcrypt\\x00\\x00\\x00\\x18\\x00\\x00\\x00\\x10\\xfa\\xca\\x90\\x04\\x96\\x83\\xbb\\xe9\\x00\\x05\\'\\x8ev\\x06,t\\x00\\x00\\x00\\x10\\x00\\x00\\x00\\x01\\x00\\x00\\x003\\x00\\x00\\x00\\x0bssh-ed25519\\x00\\x00\\x00 \\xd0\\x96\\x7f\\xcd\\x02K\\x8e\\xfe)\\xc1\\xd1p\\x00\\xbd\\xcf\\xe3\\xf6\\xe8\\x91\\xc9\\x84\\xf5\\x9e\\xacL\\xe0\\x9c/2i8R\\x00\\x00\\x00\\x90\\xf9Iu\\x91\\x7f\\x82V\\xe1E2\\x98\\x17\\x82g8jmdy\\xabZz\\x85\\xa5\\xa1\\x05%\\x9a\\xdds\\x18/\\xd2[\\xad\\xd6\\xc6\\xe3\\xb14\\x92\\xa85\\x05BI#7\\x93#\\x07\\x9cu\\xe4\\xcb\\xccJ\\xe2\\x98\\xb4\\xde\\xf8\\x96\\x8f/)2P\\xef\\x02DgO\\x1d\\xe9\\x82\\xc2\\xa0D\\xbe\\x88\\xef\\xb4\\x86\\xbb\"I\\xc0\\x10\\x91\\xebT|\\x9a:\\xaf\\r6MZq\\xba\\xa7|r\\x17=\\xe7\\xaa\\xdeq.\\xa4\\xef\\xdc!\\x12N\\xdf\\x14\\x98\\xec-,~6\\x81.\\xa0\\xec\\xfe[.\\x17\\xf3z\\xbf\\xa1Q\\xbf\\xda\\xb3\\xeeY'",
"header": {
"auth_magic": "b'openssh-key-v1\\x00'",
"cipher": "aes256-ctr",
"kdf": "bcrypt",
"kdf_options": "b\"\\x00\\x00\\x00\\x10\\xfa\\xca\\x90\\x04\\x96\\x83\\xbb\\xe9\\x00\\x05'\\x8ev\\x06,t\\x00\\x00\\x00\\x10\"",
"num_keys": 1
},
"cipher_bytes": "b'\\xf9Iu\\x91\\x7f\\x82V\\xe1E2\\x98\\x17\\x82g8jmdy\\xabZz\\x85\\xa5\\xa1\\x05%\\x9a\\xdds\\x18/\\xd2[\\xad\\xd6\\xc6\\xe3\\xb14\\x92\\xa85\\x05BI#7\\x93#\\x07\\x9cu\\xe4\\xcb\\xccJ\\xe2\\x98\\xb4\\xde\\xf8\\x96\\x8f/)2P\\xef\\x02DgO\\x1d\\xe9\\x82\\xc2\\xa0D\\xbe\\x88\\xef\\xb4\\x86\\xbb\"I\\xc0\\x10\\x91\\xebT|\\x9a:\\xaf\\r6MZq\\xba\\xa7|r\\x17=\\xe7\\xaa\\xdeq.\\xa4\\xef\\xdc!\\x12N\\xdf\\x14\\x98\\xec-,~6\\x81.\\xa0\\xec\\xfe[.\\x17\\xf3z\\xbf\\xa1Q\\xbf\\xda\\xb3\\xeeY'",
"kdf_options": {
"salt": "b\"\\xfa\\xca\\x90\\x04\\x96\\x83\\xbb\\xe9\\x00\\x05'\\x8ev\\x06,t\"",
"rounds": 16
},
"decipher_bytes": "b'\\xb1\\xe5\\x03+\\xb1\\xe5\\x03+\\x00\\x00\\x00\\x0bssh-ed25519\\x00\\x00\\x00 \\xd0\\x96\\x7f\\xcd\\x02K\\x8e\\xfe)\\xc1\\xd1p\\x00\\xbd\\xcf\\xe3\\xf6\\xe8\\x91\\xc9\\x84\\xf5\\x9e\\xacL\\xe0\\x9c/2i8R\\x00\\x00\\x00@\\x99\\x08;#\\x07\\xb970\\xc3\\xeb\\\\\\x0e\\xe4\\xc1\\x1a\\xd4\\x12\\xa6\\x05\\x88v\\xae\\x9e9\\xc28\\x1a\\xb8\\x92b0\\x8c\\xd0\\x96\\x7f\\xcd\\x02K\\x8e\\xfe)\\xc1\\xd1p\\x00\\xbd\\xcf\\xe3\\xf6\\xe8\\x91\\xc9\\x84\\xf5\\x9e\\xacL\\xe0\\x9c/2i8R\\x00\\x00\\x00\\nmy_comment\\x01\\x02\\x03'",
"decipher_bytes_header": {
"check_int_1": 2984575787,
"check_int_2": 2984575787
},
"decipher_padding": "b'\\x01\\x02\\x03'"
}
$ python -m openssh_key test_id_ed25519.pub
[
{
"header": {
"key_type": "ssh-ed25519"
},
"params": {
"data": {
"public": "b'\\xd0\\x96\\x7f\\xcd\\x02K\\x8e\\xfe)\\xc1\\xd1p\\x00\\xbd\\xcf\\xe3\\xf6\\xe8\\x91\\xc9\\x84\\xf5\\x9e\\xacL\\xe0\\x9c/2i8R'"
}
},
"footer": {},
"clear": {
"key_type": "ssh-ed25519",
"comment": "my_comment"
}
}
]
Manipulate a private key file
$ python
>>> import openssh_key.private_key_list as pkl
>>> sk_list = pkl.PrivateKeyList.from_string(open('test_id_ed25519').read(), passphrase='secret_passphrase')
>>> sk_list
[PublicPrivateKeyPair(public=<openssh_key.key.PublicKey object at 0x7fd0808f6400>, private=<openssh_key.key.PrivateKey object at 0x7fd07f781640>)]
>>> sk_list[0].private.footer
{'comment': 'my_comment'}
>>> sk_list[0].private.footer['comment'] = 'new_comment'
>>> _ = open('modified_test_id_ed25519', 'w').write(sk_list.pack_string(passphrase='new_secret_passphrase'))
>>> _ = open('modified_test_id_ed25519.pub', 'w').write(sk_list[0].public.pack_public_string())
Generate a private key
>>> import openssh_key.key_params as kp
>>> sk_params = kp.RSAPrivateKeyParams.generate_private_params()
>>> import openssh_key.key as k
>>> sk_key = k.PrivateKey(header={'key_type': 'ssh-rsa'}, params=sk_params, footer={'comment': 'comment'})
>>> pk_key = k.PublicKey(header={'key_type': 'ssh-rsa'}, params=sk_params, footer={})
>>> pk_sk_pair = pkl.PublicPrivateKeyPair(pk_key, sk_key)
>>> generated_sk_list = pkl.PrivateKeyList.from_list([pk_sk_pair], cipher='aes256-ctr', kdf='bcrypt')
>>> _ = open('generated_test_id_rsa', 'w').write(generated_sk_list.pack_string(passphrase='secret_passphrase'))
>>> _ = open('generated_test_id_rsa.pub', 'w').write(generated_pk_key.pack_public_string())
Convert keys to external classes
>>> import cryptography.hazmat.primitives.asymmetric.rsa as rsa
>>> sk_params.convert_to(rsa.RSAPrivateKey)
<cryptography.hazmat.backends.openssl.rsa._RSAPrivateKey object at 0x7f74522fadc0>
>>> sk_params.convert_to(rsa.RSAPublicKey)
<cryptography.hazmat.backends.openssl.rsa._RSAPublicKey object at 0x7f74522faac0>
Tests
The package provides a full-coverage test suite and complete type annotations.
$ git clone https://github.com/scottcwang/openssh_key_parser.git
$ pip install .
$ pip install -r requirements-dev.txt
$ pytest
Changelog
0.0.3
- 0.0.2 and on requires Python >=3.10
0.0.2
- Support
ssh-dss
,ssh-ecdsa-*
,sk-*@openssh.com
(FIDO/U2F security key) and*-cert-v01@openssh.com
(certificate) key types
0.0.1
Initial release, supporting:
ssh-rsa
andssh-ed25519
key typesnone
andaes256-ctr
ciphers for private keysnone
andbcrypt
key derivation functions for ciphers- OpenSSH key formats
Disclaimer
This software hasn't undergone a security review; use at your own risk.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file openssh_key_parser-0.0.3.tar.gz
.
File metadata
- Download URL: openssh_key_parser-0.0.3.tar.gz
- Upload date:
- Size: 43.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.7.1 importlib_metadata/4.10.1 pkginfo/1.8.2 requests/2.27.1 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.10.2
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | a14c69c31c03203ece5ad11a3278c1f313d7663c702cba0bc14baad21a10f0fd |
|
MD5 | cc7a6d0d0c84ac572d1c9d613d69418f |
|
BLAKE2b-256 | 5cbc17c9466de471cc2aff06014816252455152a47c73bd27bdd9ea6b9976f68 |
File details
Details for the file openssh_key_parser-0.0.3-py3-none-any.whl
.
File metadata
- Download URL: openssh_key_parser-0.0.3-py3-none-any.whl
- Upload date:
- Size: 69.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.7.1 importlib_metadata/4.10.1 pkginfo/1.8.2 requests/2.27.1 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.10.2
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 6480837720c5f3648c8d166c4c3568a30b9003019860138059a621427f06a7b2 |
|
MD5 | 64b167fd36f7664544dd95d6ff738b55 |
|
BLAKE2b-256 | 2bb70a0775eb17b915055bdd8b24bcf5ae90791928b3dbf6689b8c9ea01e26dc |