I would like to print out the binary form (not sure if this is how I would refer to it) of a .pem key using python. To clarify, I want to do in python what this unix command would print out:
cat privateKey.pem | openssl rsa -pubout -outform DER
I can't just call this command using subprocess because I want it to work on Windows. I've looked at the M2Crypto and PyCrypto libraries, and with the M2Crypto library I am able to load the key using
from M2Crypto import RSA
rsaKey = RSA.load_key('privateKey.pem')
But I don't see any methods of rsaKey that print out the binary form.
Edit:
Here's what I have so far:
import M2Crypto
key = M2Crypto.RSA.load_key('key.pem')
bio = M2Crypto.BIO.MemoryBuffer()
key.save_key_der_bio(bio)
der = bio.read()
But der isn't the same as what openssl printed out. I piped the output of openssl into hexdump to compare them.
I would do this:
from Crypto.PublicKey import RSA
key = RSA.importKey(open("privatekey.pem").read())
der = key.publickey().exportKey("DER")
I figured it out. So the unix command
cat privateKey.pem | openssl rsa -pubout -outform DER
Is actually printing out the DER form of the public key.
Here is what I had to do, using the M2Crypto library:
import M2Crypto
privatekey = M2Crypto.RSA.load_key('privatekey.pem')
bio = M2Crypto.BIO.MemoryBuffer()
privatekey.save_pub_key_bio(bio)
pubkey = bio.read()
pubkey = ''.join(pubkey.split('\n')[1:-2]) # remove -----BEGIN PUB KEY... lines and concatenate
der = base64.b64decode(pubkey)
This is the form that I wanted. For some reason, if I did
pubkey = M2Crypto.RSA.load_pub_key_bio(bio)
pubkey.save_key_der_bio(bio)
der = bio.read()
It gave me the wrong answer.
Related
I want to use the following code to encrypt the data, but I don't know why it gives me an error in the hashAlgo=SHA256 section:
Expected type 'HashLikeClass | HashLikeModule | None', got 'Type[SHA256]' instead
from Cryptodome.Cipher import PKCS1_OAEP
from Cryptodome.PublicKey import RSA
import base64
from cryptography.hazmat.primitives.hashes import SHA256
def aes_key_encryptions(data):
key = RSA.import_key(open('rsa.public').read())
cipher= PKCS1_OAEP.new(key, hashAlgo=SHA256)
signature =cipher.encrypt(data)
base64_bytes = base64.b64encode(signature)
base64_signature = base64_bytes.decode('ascii')
# print()
return base64_signature
The Python cryptography documentation says that you can specify hashAlgo=SHA256
My public key is:
KEY = -----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxdzREOEfk3vBQogDPGTM
qdDQ7t0oDhuKMZkA+Wm1lhzjjhAGfSUOuDvOKRoUEQwP8oUcXRmYzcvCUgcfoRT5
iz7HbovqH+bIeJwT4rmLmFcbfPke+E3DLUxOtIZifEXrKXWgSVPkRnhMgym6UiAt
nzwA1rmKstJoWpk9Nv34CYgTk8DKQN5jQJqb9L/Ng0zOEEtI3zA424tsd9zv/kP4
/SaSnbbnj0evqsZ29X6aBypvnTnwH9t3gbWM4I9eAVQhPYClawHTqvdaz/O/feqf
m06QBFnCgL+CBdjLs30xQSLsPICjnlV1jMzoTZnAabWP6FRzzj6C2sxw9a/WwlXr
Kn3gldZ7Ctv6Jso72cEeCeUI1tzHMDJPU3Qy12RQzaXujpMhCz1DVa47RvqiumpT
NyK9HfFIdhgoupFkxT14XLDl65S55MF6HuQvo/RHSbBJ93FQ+2/x/Q2MNGB3BXOj
NwM2pj3ojbDv3pj9CHzvaYQUYM1yOcFmIJqJ72uvVf9Jx9iTObaNNF6pl52ADmh8
5GTAH1hz+4pR/E9IAXUIl/YiUneYu0G4tiDY4ZXykYNknNfhSgxmn/gPHT+7kL31
nyxgjiEEhK0B0vagWvdRCNJSNGWpLtlq4FlCWTAnPI5ctiFgq925e+sySjNaORCo
HraBXNEwyiHT2hu5ZipIW2cCAwEAAQ==
-----END PUBLIC KEY-----
You are mixing 2 different cryptography-related packages.
PKCS1_OAEP.new is from the pycryptodome package
hazmat.primitives.hashes.SHA256 is from the pyca/cryptography package
They are not interchangeable, as far as I know.
It's best to check the package's documentation for that method, and use the correct data types and functions provided by the package itself.
Since you are using pycrytodome's PKCS1_OAEP.new:
hashAlgo (hash object) – The hash function to use. This can be a module under Crypto.Hash or an existing hash object created from any of such modules. If not specified, Crypto.Hash.SHA1 is used.
And apparently, pycryptodome has the corresponding Crypto.Hash.SHA256:
from Cryptodome.Cipher import PKCS1_OAEP
from Cryptodome.PublicKey import RSA
from Cryptodome.Hash import SHA256 # <----
def aes_key_encryptions(data):
key = RSA.import_key(open("rsa.public").read())
cipher = PKCS1_OAEP.new(key, hashAlgo=SHA256) # <----
...
I want to perform a sha256withRSA signature of an hexadecimal string (0xDEADBEEF in this example) using the PKCS1_PSS module in Python:
import Crypto
from Crypto.Signature import PKCS1_PSS
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
from Crypto import Random
import binascii
message = 'DEADBEEF'
message = binascii.unhexlify(message)
h = SHA256.new(message) # sha256 hashing the message
my_hash = binascii.hexlify(h.digest())
I get the following hash :
b'5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953'
Then I sign it using the following Private Key :
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDRFNU++93aEvz3cV8LSUP9ib3iUxT7SufdVXcgVFK9M3BYzvro
A1uO/parFOJABTkNhTPPP/6mjrU2CPEZJ1zIkpaSNJrrhpp/rNMO9nyLYPGs9Mfd
BiWUPmHW5mY1oD0ye4my0tEsHOlgHC8AhA8OtiHr6IY0agXmH/y5YmSWbwIDAQAB
AoGAAj/IH3pUI6FqqTrF+/gYzCRsL4AXTLC8l8vwkR93GGPyRHJNjqtik8I3WrXJ
zUiBGZ0iNouIsL/+QQuNlGiw/c5i2X3nTntREDS9xs2M0x+MWD/5qI1sn0Qk0HNP
BbDczlvO8wXNFGIHiTiPVEawoeNwhMqJDyGcbsEOZp2pLokCQQDvlMBU6dOeOP9a
jnENFSlrvzNR0nugFeoGmfq6s4Czz2QtUd9baKqBfEBSdJskwFVHgxbFA1Dc7iFu
rJkoQEeFAkEA32j9ibSVryxLvWUZngKNwo2xE+wcYDAYVBMsYC3OBU3FXhVkFD06
ZVnJsY/4bd2VdQI+bI2KV99aHutMJG2WYwJABMn2ZjweTMVa5VZ/kAFiSJMT1Yjd
i7+kY+lkB6Na6T02BWnjixI2hkwThRJrn3pwufM2201Lqn7gEDRHA3T1eQJBAKZG
1RUNo6558HEo8vUIf4vCu33RaJkqkqDYmFmJHeISrQfGMfNiUrkmJ5iRR9w1ZExu
/Bj9C281XDTQ+Z3PNnMCQQCan+pvj0OZH6o0PAMJGBBwRECPpfZ6mUjwA2YD3g61
MHjtIYmKKGmn64Qs8zQ4mNEDboQqyaov3Ij/I6c0ZQlc
-----END RSA PRIVATE KEY-----
Signing command :
key = RSA.import_key(open('private_key.pem').read())
signature = PKCS1_PSS.new(key).sign(h)
print(binascii.hexlify(signature))
The result :
b'67bcc8c0cd625a58272dc8808602beb630c0dc47622da153c6b3f7fcfdddd5e082beb9e73ed0e66f9751e68106b42ff71f8d291045ca7e9a5a265e885e19c016b6095e5f895801d3f735393e8cd3e4a18382a914487b46cf6c3ca3346c0b6f4bac923e491ca9933e12f826914b90955ce24d0203824dbb2c9cb7cb617af7cdef'
When I try to calculate the signature myself using the RSA encryption method (x = (m^d)%n ), I get a different value :
n = key.n
d = key.d
x = 0x5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953 # hash
calculated_signature = pow(x,d,n)
print (hex(calculated_signature))
I get this :
'0x4e3484dde9ca8987b77a52c696b6848e5980c858f635a62e10fe24d45bea52bac4873378b9612f47398cb73102243b73cfa2da87b487ec0d26f949e09e4edb299bd4acc4aa0eb43afa47b814b3430ff9b01b8e41bcf53ba310ae26c191cb516c07f3ebe272bcbb95acb9e7bbdc22f0c17997ba6c1884c177ba41d4e384184275'
I'm wondering why I get this difference? My understanding of the sign method previously used is that it encrypts the input (the hash) using the operation (x^d)%n.
Am I missing something ? Can anyone suggest me the reason of the difference ? Is it due to some bad manipulation/understanding from my side ?
I have an RSA public key and a signed X509 certificate. How can I check that the key signed the certificate? (My example happens to be a self-signed certificate.)
Here's what I'm doing now:
Generate self-signed cert and convert to DER encoding with openssl cli:
(I'm expecting DER in my real application)
openssl req -x509 -newkey rsa:2048 -keyout selfsigned.key -nodes -out selfsigned.cert -sha256 -days 1000
openssl x509 -outform der -in selfsigned.cert -out self.der
Decode it to a Crypto.Util.asn1.DerSequence instance.
>>> from Crypto.Util.asn1 import DerSequence
>>> from Crypto.PublicKey import RSA
>>> der = open('self.der').read()
>>> cert = DerSequence()
>>> cert.decode(der)
905L
>>> # according to RFC5280, this is a 3-length sequence:
>>> # tbsCertificate, signatureAlgorithm, signatureValue
>>> # "tbs" == "to be signed"
>>> len(cert)
3
Then I pull the RSA public key out:
>>> tbscert = DerSequence()
>>> tbscert.decode(cert[0])
>>> subjectPublicKeyInfo = tbscert[6]
>>> rsa_key = RSA.importKey(subjectPublicKeyInfo)
>>> >>> rsa_key
<_RSAobj #0x7fb27287d128 n(2048),e>
Then I pull the signature out
This is very annoying. I'm using another library to decode the DER again because this one gives me a slightly more convenient representation of the "bit string" encoding of the signature value. For now, I copy and paste the base-2 string representation of the value into int() to get a long (what the RSA.verify() method expects).
>>> from pyasn1_modules import rfc2437,rfc2459
>>> from pyasn1.codec.der import decoder
>>> cert2,rest = decoder.decode(der, asn1Spec=rfc2459.Certificate())
>>> sig_bits = cert2.getComponentByName("signatureValue")
>>> sig_bits
BitString("'10101110101100111010100000111001000110111111101001110000100111110111011111111110011010110101001001110110111011011001110001010000111001001001110111001110011101000000100000001100001101000111000110010100110101000110111101110001011011001001100011110011010101011100000001010111101110111001110011010011110101101001110101110100011111011111110001110001110100000110110100010000001010111010000100101110101001110000111001100010011110110100101001010000101001110101101111100111001111000010111001110000000101000000011110011110000110000101101101110110101101110101011110101111111000101011001000010000110100111111001011111100110011011011001001111110000000110100000001101000011010010100001100110100001001000111001011111100000011000101001100001010101001111000001010100101010101000100001000100011101111100101010010001111001110100001011110101100010111001010100010001011100000100101110001011101100010010111100010111110010010110111111100100100101010010000000010001011111110011000011000101000001000000000000011111101110010101100100000010111111110101000110010101111100101011101000010010110101000101101110001001101101000110101110011101011111100010000101001111011100100100010101001011011110001110000000000010011011111011110100000111001100010100000100001101111110010000111100110110110000001010010110011010111100101110101000001111001010011101001101101101101011000100010011110000101010110111011100010100011110101100110101011010111000011111000001111111111000101101101011110010111100101011100010000111011101101010101001011101101111011001001110000010011001111010001011110000011001011000110100011100000100100111000111100000010000001001001010001100000010011110100000111010010100001101001111111001111110111010001101010110100001100111010101000010000101000000111100001001001000100011100110010110101001110111101000101101011011100000010010000100111001100001110010101000100000010010111110001100011110010000100001000101100011000011000110010110011100010100010111011011111111010000001001100000100011010000000110111100111010101001110101000011111011000100010111101100110100010101111000110111110'B")
>>> bit_string = '10101110101100111010100000111001000110111111101001110000100111110111011111111110011010110101001001110110111011011001110001010000111001001001110111001110011101000000100000001100001101000111000110010100110101000110111101110001011011001001100011110011010101011100000001010111101110111001110011010011110101101001110101110100011111011111110001110001110100000110110100010000001010111010000100101110101001110000111001100010011110110100101001010000101001110101101111100111001111000010111001110000000101000000011110011110000110000101101101110110101101110101011110101111111000101011001000010000110100111111001011111100110011011011001001111110000000110100000001101000011010010100001100110100001001000111001011111100000011000101001100001010101001111000001010100101010101000100001000100011101111100101010010001111001110100001011110101100010111001010100010001011100000100101110001011101100010010111100010111110010010110111111100100100101010010000000010001011111110011000011000101000001000000000000011111101110010101100100000010111111110101000110010101111100101011101000010010110101000101101110001001101101000110101110011101011111100010000101001111011100100100010101001011011110001110000000000010011011111011110100000111001100010100000100001101111110010000111100110110110000001010010110011010111100101110101000001111001010011101001101101101101011000100010011110000101010110111011100010100011110101100110101011010111000011111000001111111111000101101101011110010111100101011100010000111011101101010101001011101101111011001001110000010011001111010001011110000011001011000110100011100000100100111000111100000010000001001001010001100000010011110100000111010010100001101001111111001111110111010001101010110100001100111010101000010000101000000111100001001001000100011100110010110101001110111101000101101011011100000010010000100111001100001110010101000100000010010111110001100011110010000100001000101100011000011000110010110011100010100010111011011111111010000001001100000100011010000000110111100111010101001110101000011111011000100010111101100110100010101111000110111110'
>>> len(bit_string)
2048
>>> sig_long = int(bit_string, 2)
22054057292543290008991218833668878365914778519473463062473060546762899555976103489048033910135613221569150796460758806399269198735780309519101363051388009338597879536630494212385605300708879019160215628821483902624509955250980351374010304684207884550324020859785789812498991361733361061223150200173076263554090698006436248180914014712709890577579243572383188197634606581121383593473899061397708617253275982314075801792358481980896751043809539358665686019958496887281091997170247998458556812030465091755579654010246474389968142047627934047174316731806191431717418170761689395728146445291177267566370799362894264463806L
Then I compute the SHA256 hash of the "to be signed certificate":
>>> import Crypto.Hash.SHA256
>>> comp_hash = Crypto.Hash.SHA256.new(cert[0]).digest()
>>> comp_hash
'\xa3t\x84\xd6\xf5\xfe\x16\xb9\xdb(&\x12\xb3m^+\x94\xa7bZ\xf9s\xf7\xbay\xa1j\xa3Y\xea\xa8\x7f'
Then the verify() method tells me the signature doesn't match.
>>> rsa_key.verify(comp_hash, (sig_long, None))
False
I hope there's a better way (this doesn't even work), but I've spent hours looking at PyCrypto and PyOpenSSL and haven't found it.
edits
This similar S.O. question from a few years ago has no answer: Verify SSL/X.509 certificate is signed by another certificate
I guess one way to accomplish this is to create an X509StoreContext containing only one certificate corresponding to the public key I want to check for.
>>> from OpenSSL import crypto
>>> x509_self_signed # already loaded
<OpenSSL.crypto.X509 object at 0x7fcc4049c9b0>
>>> cert_store = crypto.X509Store()
>>> cert_store.add_cert(x509_self_signed)
>>> store_ctx = crypto.X509StoreContext(cert_store, x509_self_signed)
>>> store_ctx.verify_certificate()
>>> # ^ that raises an exception if it fails to verify
verify_certificate() was added to PyOpenSSL only a little over a year ago, so maybe that's why it was hard to find...
https://github.com/pyca/pyopenssl/pull/155
I am generating a key with OpenSSL, providing the password from stdin:
openssl genpkey -algorithm RSA -out private-key.pem -outform PEM -pass stdin -des3 -pkeyopt rsa_keygen_bits:4096
The key then looks like:
-----BEGIN ENCRYPTED PRIVATE KEY-----
XXX...
-----END ENCRYPTED PRIVATE KEY-----
My Python code looks like:
from Crypto.PublicKey import RSA
# ...
f = open('private-key.pem', 'r')
r = RSA.importKey(f.read(), passphrase='some-pass')
f.close()
but I am getting an exception:
File "/usr/lib/python2.7/dist-packages/Crypto/PublicKey/RSA.py", line 665, in importKey
return self._importKeyDER(der)
File "/usr/lib/python2.7/dist-packages/Crypto/PublicKey/RSA.py", line 588, in _importKeyDER
raise ValueError("RSA key format is not supported")
ValueError: RSA key format is not supported
What's wrong?
Is it possible to generate an encrypted RSA key, store it in a file and later use it with PyCrypto? Is it possible to do it with OpenSSL? What formats are supported?
Importing the public key works fine, however it is not encrypted.
Hypothesis #1
After looking to the source code, I think, I solved the mystery. The way how import works for PEM keys encrypted with a password is that the PEM gets decrypted to DER and after that importKeyDER function is called. If provided password is not correct, the format of generated DER representation will not be correct too and you would get an exception that you've provided. To confirm that, I ran two quick tests below:
>>> from Crypto.PublicKey import RSA
>>> f = open('<some-path>/private-key.pem','r')
>>> r=RSA.importKey(f.read(),passphrase='foo')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python2.7/dist-packages/Crypto/PublicKey/RSA.py", line 665, in importKey
return self._importKeyDER(der)
File "/usr/local/lib/python2.7/dist-packages/Crypto/PublicKey/RSA.py", line 588, in _importKeyDER
raise ValueError("RSA key format is not supported")
ValueError: RSA key format is not supported
>>> f = open('<some-path>/private-key.pem','r')
>>> r=RSA.importKey(f.read(),passphrase='<valid-pass-phrase>')
>>> r
<_RSAobj #0xb7237b2c n(4096),e,d,p,q,u,private>
After receiving the PEM from the author, I've realized that Hypothesis #1 is not valid for his case. I still want to keep it here as one possible reason of import failure, so other users are aware.
Hypothesis #2 - this is the author's case.
RSA.py looks for the following in PEM file to determine what kind of encryption was applied to PEM:
Proc-Type: 4,ENCRYPTED
When key is generated using "openssl genrsa ..." command, this string is present in PEM in clear, however when "opensl genpkey ..." is used the "Proc-Type" is not present.
RSA.py doesn't even try to decrypt the PEM if the "Proc-Type" is not found:
# The encrypted PEM format
if lines[1].startswith(b('Proc-Type:4,ENCRYPTED')):
DEK = lines[2].split(b(':'))
....
So, my conclusion at this time is that keys generated by "openssl genpkey" are not supported by PyCrypto v 2.6.1.
Important Update
It does work in PyCrypto's latest version 2.7a1. You can download it from here: http://ftp.dlitz.net/pub/dlitz/crypto/pycrypto/pycrypto-2.7a1.tar.gz
>>> f = open('key.pem','r')
>>> r = RSA.importKey(f.read(), passphrase='123456')
>>> r
<_RSAobj #0xb6f342ec n(2048),e,d,p,q,u,private>
A quick update for those who seek to solve this problem without installing an experimental release of long-abandoned PyCrypto. The library can be safely replaced by pycryptodome (https://github.com/Legrandin/pycryptodome) - it can provide both a drop-in replacement for pycrypto, and it can be used as an alternative library as well (pycryptodomex).
I'm attempting to write a script to generate SSH Identity key pairs for me.
from M2Crypto import RSA
key = RSA.gen_key(1024, 65337)
key.save_key("/tmp/my.key", cipher=None)
The file /tmp/my.key looks great now.
By running ssh-keygen -y -f /tmp/my.key > /tmp/my.key.pub I can extract the public key.
My question is how can I extract the public key from python? Using key.save_pub_key("/tmp/my.key.pub") saves something like:
-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADASDASDASDASDBarYRsmMazM1hd7a+u3QeMP
...
FZQ7Ic+BmmeWHvvVP4Yjyu1t6vAut7mKkaDeKbT3yiGVUgAEUaWMXqECAwEAAQ==
-----END PUBLIC KEY-----
When I'm looking for something like:
ssh-rsa AAAABCASDDBM$%3WEAv/3%$F ..... OSDFKJSL43$%^DFg==
Use cryptography! pycrypto is not in active development anymore and if possible you should be using cryptography. Since June it's possible to generate SSH public keys as well:
from cryptography.hazmat.primitives import serialization as crypto_serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend as crypto_default_backend
key = rsa.generate_private_key(
backend=crypto_default_backend(),
public_exponent=65537,
key_size=2048
)
private_key = key.private_bytes(
crypto_serialization.Encoding.PEM,
crypto_serialization.PrivateFormat.PKCS8,
crypto_serialization.NoEncryption()
)
public_key = key.public_key().public_bytes(
crypto_serialization.Encoding.OpenSSH,
crypto_serialization.PublicFormat.OpenSSH
)
Note: You need at least version 1.4.0.
Note: If your SSH client does not understand this private key format, replace PKCS8 with TraditionalOpenSSL.
Just in case there are any future travellers looking to do this. The RSA module support writing out the public key in OpenSSH format now (possibly didn't at the time of earlier posts). So I think you can do what you need with:
from os import chmod
from Crypto.PublicKey import RSA
key = RSA.generate(2048)
with open("/tmp/private.key", 'wb') as content_file:
chmod("/tmp/private.key", 0600)
content_file.write(key.exportKey('PEM'))
pubkey = key.publickey()
with open("/tmp/public.key", 'wb') as content_file:
content_file.write(pubkey.exportKey('OpenSSH'))
The files are opened with a 'wb' as the keys must be written in binary mode.
Obviously don't store you're private key in /tmp...
Edit 05/09/2012:
I just realized that pycrypto already has this:
import os
from Crypto.PublicKey import RSA
key = RSA.generate(2048, os.urandom)
print key.exportKey('OpenSSH')
This code works for me:
import os
from Crypto.PublicKey import RSA
key = RSA.generate(2048, os.urandom)
# Create public key.
ssh_rsa = '00000007' + base64.b16encode('ssh-rsa')
# Exponent.
exponent = '%x' % (key.e, )
if len(exponent) % 2:
exponent = '0' + exponent
ssh_rsa += '%08x' % (len(exponent) / 2, )
ssh_rsa += exponent
modulus = '%x' % (key.n, )
if len(modulus) % 2:
modulus = '0' + modulus
if modulus[0] in '89abcdef':
modulus = '00' + modulus
ssh_rsa += '%08x' % (len(modulus) / 2, )
ssh_rsa += modulus
public_key = 'ssh-rsa %s' % (
base64.b64encode(base64.b16decode(ssh_rsa.upper())), )
The key used by ssh is just base64 encoded, i don't know M2Crypto very much, but after a quick overview it seems you could do what you want this way:
import os
from base64 import b64encode
from M2Crypto import RSA
key = RSA.gen_key(1024, 65537)
raw_key = key.pub()[1]
b64key = b64encode(raw_key)
username = os.getlogin()
hostname = os.uname()[1]
keystring = 'ssh-rsa %s %s#%s' % (b64key, username, hostname)
with open(os.getenv('HOME')+'/.ssh/id_rsa.pub') as keyfile:
keyfile.write(keystring)
I didn't test the generated key with SSH, so please let me know if it works (it should i think)
The base64 decoded version of ssh-keygen output to the contents of key.pub() the format of the keyfile is
b64encode('\x00\x00\x00\x07ssh-rsa%s%s' % (key.pub()[0], key.pub()[1]))
If you want, you could just also use ssh-keygen itself.
You can extend this to also create your file, and just use open to read the content later, but i focused on creating a .pub key from an already existing key here.
from subprocess import Popen, PIPE
import os
home = f'{os.path.expanduser("~")}'
cert_pos = f'{home}/.ssh/my_key'
your_key_pw = ''
cmd = ['ssh-keygen', '-y', '-f', cert_pos]
if your_key_pw:
cmd.append('-P')
cmd.append(your_key_pw)
p = Popen(cmd, stdout=PIPE)
p.wait()
res, err = p.communicate()
cert_content = res.decode('utf-8')
Here is an example using the Twisted Conch library which leverages PyCrypto under the covers. You can find the API documentation at http://twistedmatrix.com/documents/current/api/twisted.conch.ssh.keys.html:
from twisted.conch.ssh import keys
# one-time use key
k="""-----BEGIN RSA PRIVATE KEY-----
PRIVATE KEY STUFF
-----END RSA PRIVATE KEY-----"""
# create pycrypto RSA object
rsa = keys.RSA.importKey(k)
# create `twisted.conch.ssh.keys.Key` instance which has some nice helpers
key = keys.Key(rsa)
# pull the public part of the key and export an openssh version
ssh_public = key.public().toString("openssh")
print ssh_public
You can use pycryptodome as described in documentation:
from Crypto.PublicKey import RSA
key = RSA.generate(2048)
private_key = key.export_key()
file_out = open("private.pem", "wb")
file_out.write(private_key)
public_key = key.publickey().export_key()
file_out = open("receiver.pem", "wb")
file_out.write(public_key)
Just guessing... but have you tried something like this?:
print "ssh-rsa " + "".join([ l.strip() for l in open('/tmp/my.key.pub') if not l.startswith('-----')])
Can you get the AAAA...Dfg== string out of it while it's an object? If so, you could simply open a file yourself and save that instead of using the built in save_pub_key function.
I don't know of such a library that comes standard with Python.
If you want to look to third-party libraries, you might find the paramiko library useful (also available from PyPI). It implements the SSH protocol, and has functionality for handling existing keys, but not generating them.
Generation of keys might be a useful addition to that library (you could work with the developers to incorporate it into the Paramiko library), and an easier start than doing it from scratch.