I am generating a EC key using python cryptography module in this way
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import ec
key=ec.generate_private_key(ec.SECP256R1(), default_backend())
The asn.1 structure of EC key is as follows
ECPrivateKey ::= SEQUENCE {
version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
privateKey OCTET STRING,
parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
publicKey [1] BIT STRING OPTIONAL
}
from https://www.rfc-editor.org/rfc/rfc5915 setion 3.
my question is how to get the ASN.1 components from this key. I want to convert the key object to OpenSSH private key, something like
-----BEGIN EC PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,9549ED842979FDAF5299BD7B0E25B384
Z+B7I6jfgC9C03Kcq9rbWKo88mA5+YqxSFpnfRG4wkm2eseWBny62ax9Y1izGPvb
J7gn2eBjEph9xobNewgPfW6/3ZDw9VGeaBAYRkSolNRadyN2Su6OaT9a2gKiVQi+
mqFeJmxsLyvew9XPkZqQIjML1d1M3T3oSA32zYX21UY=
-----END EC PRIVATE KEY-----
It is easy with handling DSA or RSA because all the ASN.1 parameters are integers in that.
Thank You in advance
It's relatively easy to extract the public point from the ASN.1 sequence using pyasn1, but if you want PEM-encrypted PKCS1 (aka "traditional OpenSSL") then pyca/cryptography can do that quite easily:
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
backend = default_backend()
key = ec.generate_private_key(ec.SECP256R1(), backend)
serialized_key = key.private_bytes(
serialization.Encoding.PEM,
serialization.PrivateFormat.TraditionalOpenSSL,
serialization.BestAvailableEncryption(b"my_great_password")
)
You can find more information about the private_bytes method in the docs. At this time BestAvailableEncryption will encrypt using AES-256-CBC.
Related
I am trying to find the Python-equivalent of running openssl ec -pubin -in example.pem -inform PEM -outform DER conv_form compressed
Example using the following public key:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4q+ot7o3PuJqsBonZni2spVPvqLk
6FCiEF9GXzTyYZ1snzreGB+pyoiUUkz2/H60XWmQsgC7zZ60TBT0rVimtg==
-----END PUBLIC KEY-----
and running the following command gives the following output:
echo '-----BEGIN PUBLIC KEY-----\r\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4q+ot7o3PuJqsBonZni2spVPvqLk\r\n6FCiEF9GXzTyYZ1snzreGB+pyoiUUkz2/H60XWmQsgC7zZ60TBT0rVimtg==\r\n-----END PUBLIC KEY-----\r\n' | openssl ec -pubin -inform PEM -outform DER -conv_form compressed | base64
read EC key
writing EC key
MDkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDIgAC4q+ot7o3PuJqsBonZni2spVPvqLk6FCiEF9GXzTyYZ0=
now providing a bit more verbosity with the same command plus the -text flag and minus the base64 encoding:
echo '-----BEGIN PUBLIC KEY-----\r\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4q+ot7o3PuJqsBonZni2spVPvqLk\r\n6FCiEF9GXzTyYZ1snzreGB+pyoiUUkz2/H60XWmQsgC7zZ60TBT0rVimtg==\r\n-----END PUBLIC KEY-----\r\n' | openssl ec -pubin -inform PEM -outform DER -conv_form compressed -text
read EC key
Private-Key: (256 bit)
pub:
02:e2:af:a8:b7:ba:37:3e:e2:6a:b0:1a:27:66:78:
b6:b2:95:4f:be:a2:e4:e8:50:a2:10:5f:46:5f:34:
f2:61:9d
ASN1 OID: prime256v1
NIST CURVE: P-256
writing EC key
090*�H�*�H�="⯨��7>�j�'fx���O����P�_F_4�a�%
So far I am able to do something like:
import base64
import cryptography
csr_crypto = cryptography.x509.load_pem_x509_csr(csr_encoded) # csr_encoded being the CSR in PEM format that the public key is derived from
pub_key = csr_crypto.public_key()
compressed_bytes = pub_key.public_bytes(cryptography.hazmat.primitives.serialization.Encoding.X962, cryptography.hazmat.primitives.serialization.PublicFormat.CompressedPoint).hex()
this leads to compressed_bytes being equal to
02e2afa8b7ba373ee26ab01a276678b6b2954fbea2e4e850a2105f465f34f2619d
which if you look closely is equal to what is in the pub above.
How are these hex bytes ultimately converted to the string MDkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDIgAC4q+ot7o3PuJqsBonZni2spVPvqLk6FCiEF9GXzTyYZ0= that openssl outputs as a compressed public key?
The first key posted is a public key in X.509/SPKI format (PEM encoded), which contains the actual key in uncompressed format 0x04 + <x> + <y>.
The key derived from this using the OpenSSL statement is also a public key in X.509/SPKI format, but contains the actual key in compressed format 0x02 + <x> or 0x03 + <x> for even or odd y, respectively. This format also contains the complete information, since for a given curve an uncompressed key can be derived from a compressed key.
X.509/SPKI keys can be parsed with an ASN.1 parser (in addition to OpenSSL), e.g. online: https://lapo.it/asn1js.
In Python, an X.509/SPKI key with uncompressed key can be converted to an X.509/SPKI key with compressed key using the PyCryptodome library, see export_key():
from base64 import b64encode
from Crypto.PublicKey import ECC
x509PemWithUncompressed = '''-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4q+ot7o3PuJqsBonZni2spVPvqLk
6FCiEF9GXzTyYZ1snzreGB+pyoiUUkz2/H60XWmQsgC7zZ60TBT0rVimtg==
-----END PUBLIC KEY-----'''
publicKey = ECC.import_key(x509PemWithUncompressed);
x509withCompressed = publicKey.export_key(format='DER', compress=True)
print(b64encode(x509withCompressed).decode('utf-8')) # MDkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDIgAC4q+ot7o3PuJqsBonZni2spVPvqLk6FCiEF9GXzTyYZ0=
The Cryptography library on the other hand only allows the export of an X.509/SPKI key with uncompressed key. For this, the format has to be specified with SubjectPublicKeyInfo. An export as X.509/SPKI key with compressed key is not possible. The options UncompressedPoint and CompressedPoint only allow export in the format 0x04 + x + y or 0x02/0x03 + x, but not in the X.509/SPKI format.
But there is a workaround for the desired conversion. In the X.509/SPKI format, the actual key (compressed or uncompressed) is located at the end. The preceding part contains information about the curve, data lengths, etc. In the case of an X.509/SPKI key with compressed key and curve P-256, the preceding part is: 0x3039301306072a8648ce3d020106082a8648ce3d030107032200 (note that the prefix for an X.509 /SPKI key with uncompressed key differs because of the different data lengths) and can be used as a prefix for converting a compressed key in 0x02/0x03 + x format to X.509/SPKI format:
from base64 import b64encode
from cryptography.hazmat.primitives import serialization
x509PemWithUncompressed = b'''-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4q+ot7o3PuJqsBonZni2spVPvqLk
6FCiEF9GXzTyYZ1snzreGB+pyoiUUkz2/H60XWmQsgC7zZ60TBT0rVimtg==
-----END PUBLIC KEY-----'''
publicKey = serialization.load_pem_public_key(x509PemWithUncompressed)
compressedKey = publicKey.public_bytes(serialization.Encoding.X962, serialization.PublicFormat.CompressedPoint)
x509withCompressed = bytes.fromhex('3039301306072a8648ce3d020106082a8648ce3d030107032200') + compressedKey
print(b64encode(x509withCompressed).decode('utf-8')) # MDkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDIgAC4q+ot7o3PuJqsBonZni2spVPvqLk6FCiEF9GXzTyYZ0=
Working with an HSM I can download the public key of a RSA 4096, and can use the HSM to encrypt a blob using the corresponding private key, which I can't download.
I'm trying to find a reference to how I can use the HSM apis, to build the X509 certificate myself. I could find the example below in python, wrapping up openssl libs.
It does mostly all, but
from OpenSSL import crypto, SSL
from socket import gethostname
from pprint import pprint
from time import gmtime, mktime
CERT_FILE = "selfsigned.crt"
KEY_FILE = "private.key"
def create_self_signed_cert():
# create a key pair
k = crypto.PKey()
k.generate_key(crypto.TYPE_RSA, 1024)
# create a self-signed cert
cert = crypto.X509()
cert.get_subject().C = "UK"
cert.get_subject().ST = "London"
cert.get_subject().L = "London"
cert.get_subject().O = "Dummy Company Ltd"
cert.get_subject().OU = "Dummy Company Ltd"
cert.get_subject().CN = gethostname()
cert.set_serial_number(1000)
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(10*365*24*60*60)
cert.set_issuer(cert.get_subject())
cert.set_pubkey(k)
cert.sign(k, 'sha1')
open(CERT_FILE, "wt").write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
open(KEY_FILE, "wt").write(crypto.dump_privatekey(crypto.FILETYPE_PEM, k))
create_self_signed_cert()
Mostly what I would expect to be possible is to replace the line
cert.sign(k, 'sha1')
by the corresponding calls to the APIs of my HSM with "an export" of the structure of "cert". Is it possible at all ?
So after one day of research I found a satisfactory answer at Adding a signature to a certificate . While there seems to be some Python examples out how to build the certificate through Python with pyasn1, the most robust seems to be the ones in java using boucycastle libraries. I also found a strong answer how to incorporate it with openssl here How to generate certificate if private key is in HSM? (mostly points 3 and 4), but the approach is far beyond my capacities.
When you want someone else, like your HSM, to sign a certificate for you the standard way to do that is to generate a Certificate Signing Request (CSR) and send the CSR to the signer. The CSR can contain all of the fields that you can set when making a certificate, and those values will be reflected in the resulting certificate.
While you can do this with the OpenSSL library that you are using, see X509Req, their documentation (and I quote) "strongly suggests" that you use the cryptography library instead. (They're maintained by the same group of people.)
In the cryptography library you use the CertificateSigningRequestBuilder to make a CSR. To quote from their docs:
>>> from cryptography import x509
>>> from cryptography.hazmat.primitives import hashes
>>> from cryptography.hazmat.primitives.asymmetric import rsa
>>> from cryptography.x509.oid import AttributeOID, NameOID
>>> private_key = rsa.generate_private_key(
... public_exponent=65537,
... key_size=2048,
... )
>>> builder = x509.CertificateSigningRequestBuilder()
>>> builder = builder.subject_name(x509.Name([
... x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'),
... ]))
>>> builder = builder.add_extension(
... x509.BasicConstraints(ca=False, path_length=None), critical=True,
... )
>>> builder = builder.add_attribute(
... AttributeOID.CHALLENGE_PASSWORD, b"changeit"
... )
>>> request = builder.sign(
... private_key, hashes.SHA256()
... )
>>> isinstance(request, x509.CertificateSigningRequest)
True
To render out the CSR in the format usually used for sending them around:
>>> from cryptography.hazmat.primitives import serialization
>>> request.public_bytes(serialization.Encoding.PEM)
I am currently trying to write a script that allows me to compute the Tor HS address from the hiddens service's private key file.
In order to do this the file needs to be brought into the DER format.
Using OpenSSL this can be done with:
openssl rsa -in private_key -pubout -outform DER
Piping this into python with:
base64.b32encode(hashlib.sha1(sys.stdin.read()[22:]).digest()[:10]).lower()'
will return the address correctly.
However I would like to perform the same using only python. My problem is that using the pycrypto module the DER output is different and the address therefore incorrect.
key = RSA.importKey(keyfile.read()).publickey()
print(key.exportKey(format='DER'))
Will result in a different output than the openssl call.
Is this just a matter of implementation that allows different results? Or am I making a mistake somewhere?
Any help would be appreciated
convert certificate to der using python
first we load the file
cert_file = keyfile.read()
Then we convert it to pem format
from OpenSSL import crypto
cert_pem = crypto.load_certificate(crypto.FILETYPE_PEM, cert_file)
now we are generating the der-output
i.e.: output equals to openssl x509 -outform der -in certificate.pem -out certificate.der.
cert_der = crypto.dump_certificate(crypto.FILETYPE_ASN1, cert_pem)
I was looking for something similar and, as of March 2019, OpenSSL recommends using pyca/cryptography instead of the crypto module. (source)
Here after is then what you intend to do: convert PEM to DER
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
with open("id_rsa", "rb") as keyfile:
# Load the PEM format key
pemkey = serialization.load_pem_private_key(
keyfile.read(),
None,
default_backend()
)
# Serialize it to DER format
derkey = pemkey.private_bytes(
serialization.Encoding.DER,
serialization.PrivateFormat.TraditionalOpenSSL,
serialization.NoEncryption()
)
# And write the DER format to a file
with open("key.der", "wb") as outfile:
outfile.write(derkey)
I want Convert Certificate file not the key file from DER to PEM, but Google took me here. thanks #alleen1's answer, I can convert certificate or key from DER to PEM and vice versa.
Step one, load the file.
Step two,save it to the format you want.
I ommit the process to get the "pem_data" and "der_data",you can get it from file or anywhere else. they should be bytes not string, use method .encode() when needed.
from cryptography import x509
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
# Step one, load the file.
# Load key file
# PEM
key = serialization.load_pem_private_key(pem_data, None, default_backend())
# DER
key = serialization.load_pem_private_key(der_data, None, default_backend())
# Load cert file
# PEM
cert = x509.load_pem_x509_certificate(pem_data, default_backend())
# DER
cert = x509.load_der_x509_certificate(der_data, default_backend())
# Step two,save it to the format you want.
# PEM key
key_val = key.private_bytes(
serialization.Encoding.PEM,
serialization.PrivateFormat.TraditionalOpenSSL,
serialization.NoEncryption()
)
# DER key
key_val = key.private_bytes(
serialization.Encoding.DER,
serialization.PrivateFormat.TraditionalOpenSSL,
serialization.NoEncryption()
)
# PEM cert
cert_val = cert.public_bytes(serialization.Encoding.PEM)
# DER cert
cert_val = cert.public_bytes(serialization.Encoding.DER)
The inital question is: "Exact the public key from private key", this because the openSSL command states "pubout" in initial question.
Using OpenSSL this can be done with: (note that "pubout" defines OUTPUT as public key only)
openssl ALGORITHM_USED -in private_key -pubout -outform DER
But with Python cryptography module you can exact the public key from private key (note this seems applicable for RSA and EC based cryptography).
With Python:
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.backends import default_backend
# Create private key (example uses elliptic curve encryption)
priv_key = ec.generate_private_key(ec.SECP256K1, default_backend())
pub_key = priv_key.public_key()
pub_key_pem = pub_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
with open('public_key.pem', 'wb') as outfile:
outfile.write(public_key_pem)
More info on cryptography documentation: https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/#cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey
I want to generate a private , public key pair and put them into private.key and public.key files respectively.
I have the following code.
from OpenSSL import crypto, SSL
def gen_rsa_key_pair():
k = crypto.PKey()
k.generate_key(crypto.TYPE_RSA, 1024)
open("Priv.key", "wt").write(crypto.dump_privatekey(crypto.FILETYPE_PEM, k))
crypto.dump_publickey() is not available.
How do I dump public key to a file?
The OpenSSL functions to print the public RSA key do not seem to be exported by the Python OpenSSL wrapper. By accessing the internals of the crypto module, you could still do it yourself (assuming that you have this package installed locally), as this code snippet shows:
>>> bio = crypto._new_mem_buf()
>>> rsa = crypto._lib.EVP_PKEY_get1_RSA(k._pkey)
>>> crypto._lib.PEM_write_bio_RSAPublicKey(bio, rsa)
1
>>> s = crypto._bio_to_string(bio)
>>> print(s)
-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBANF1gYh10F8HTQdM6+bkwAwJ0Md6bMciKbP3qS6KTki3v3m+cM17Szqq
Mp4xxWbvnS2oeotYfn8eaZg0QUTOVDd1F7tuOxVEdvQ9ZEp1aeOCRU3b9QZSmVfg
wJrqDG3f149mNdexI12plwaxyt6odonv6+fEQJrbhrV/nIA8N/EFAgMBAAE=
-----END RSA PUBLIC KEY-----
This is just for illustration purposes. A proper solution should be added to the crypto module itself, via a new method dump_publickey() or the like.
I have a RSA public key in PEM format + PKCS#1(I guess):
-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBAJNrHWRFgWLqgzSmLBq2G89exgi/Jk1NWhbFB9gHc9MLORmP3BOCJS9k
onzT/+Dk1hdZf00JGgZeuJGoXK9PX3CIKQKRQRHpi5e1vmOCrmHN5VMOxGO4d+zn
JDEbNHODZR4HzsSdpQ9SGMSx7raJJedEIbr0IP6DgnWgiA7R1mUdAgMBAAE=
-----END RSA PUBLIC KEY-----
I want to get the SHA1 digest of its ASN1 encoded version in Python. The first step should be to read this key, but I failed to do it in PyCrypto:
>> from Crypto.PublicKey import RSA
>> RSA.importKey(my_key)
ValueError: RSA key format is not supported
The documentation of PyCrypto says PEM + PKCS#1 is supported, so I'm confused.
I've also tried M2Crypto, but it turns out that M2Crypto does not support PKCS#1 but only X.509.
PyCrypto supports PKCS#1 in the sense that it can read in X.509 SubjectPublicKeyInfo objects that contain an RSA public key encoded in PKCS#1.
Instead, the data encoded in your key is a pure RSAPublicKey object (that is, an ASN.1 SEQUENCE with two INTEGERs, modulus and public exponent).
You can still read it in though. Try something like:
from Crypto.PublicKey import RSA
from Crypto.Util import asn1
from base64 import b64decode
key64 = 'MIGJAoGBAJNrHWRFgWLqgzSmLBq2G89exgi/Jk1NWhbFB9gHc9MLORmP3BOCJS9k\
onzT/+Dk1hdZf00JGgZeuJGoXK9PX3CIKQKRQRHpi5e1vmOCrmHN5VMOxGO4d+znJDEbNHOD\
ZR4HzsSdpQ9SGMSx7raJJedEIbr0IP6DgnWgiA7R1mUdAgMBAAE='
keyDER = b64decode(key64)
seq = asn1.DerSequence()
seq.decode(keyDER)
keyPub = RSA.construct( (seq[0], seq[1]) )
Starting from version 2.6, PyCrypto can import also RsaPublicKey ASN.1 objects.
The code is then much simpler:
from Crypto.PublicKey import RSA
from base64 import b64decode
key64 = b'MIGJAoGBAJNrHWRFgWLqgzSmLBq2G89exgi/Jk1NWhbFB9gHc9MLORmP3BOCJS9k\
onzT/+Dk1hdZf00JGgZeuJGoXK9PX3CIKQKRQRHpi5e1vmOCrmHN5VMOxGO4d+znJDEbNHOD\
ZR4HzsSdpQ9SGMSx7raJJedEIbr0IP6DgnWgiA7R1mUdAgMBAAE='
keyDER = b64decode(key64)
keyPub = RSA.importKey(keyDER)