My server is in Python and client is Swift. I'm using cryptography package for Python and SwCrypt for Swift.
I create a public and private key pair
Server send public key to iOS
iOS creates a random AES key and encrypt it with the public key
let publicKeyDER = try SwKeyConvert.PublicKey.pemToPKCS1DER(publicPemReceivedFromServer)
let msg = "this_is_thirty_two_character_lon".data(using: .ascii)!
let random_aes_cypher = try CC.crypt(.encrypt, blockMode: .cbc, algorithm: .aes, padding: .pkcs7Padding, data: msg, key: CC.generateRandom(32), iv: CC.generateRandom(16))
iOS sends the encrypted AES key back to server
let x = try CC.RSA.encrypt(random_aes_cypher, derKey: publicKeyDER, tag: Data(), padding: .oaep, digest: .sha1)
//Post x to server
Server decrypts the encrypted AES key with the private key
private_key.decrypt(encrypted_aes_key, oaep_padding)
On the last step, the decrypted message contains very weird character..Any idea why? It looks something like this on PyCharm: \�<�>Ddž䥋��wp+6'���W=��$�O�rܨosf�.C��qKT=_�{�B��pE#�-mn��t����Y^0���L�9f#�=O*��\���B��z�;��"�0��k&��z,��J�\�L
When I call len() on the decrypted message, I see that I get the correct number of characters back (32 in this case), but the letters are just weird like shown above.
Related
I asked a while ago how to do something similar: Decrypting and encrypting java JWEObject with algorithm RSA-OAEP-256 on python
Now I have a different encryption key and that code is not working for me anymore.
I need to be able to encrypt my data: {"value": "Object Encryption"} with JWE using RSA.
I have this key id: a4d4039e-a8c7-4d06-98c8-2bda90ab169c
and this encryption key:
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9JJaeFiDdB+dGvi3jUzKUMU73kG6vvc/P+jwZXRKKpJSwf8PU4SapMyFPFFoHwca6Z8vZogF4ghEJ18JipNyF3BLnfCt1EHuZ15FG1Aywvpi+xw7F0UoJ9DWItBM1SodKXIh1be44/9SiLrpcyROKId349zWMOl3IVVxekLPKWTHsy2Iowp7JsjNEK3t9RdV+PAtUzp1ACyqHD/MDYSmpJuEOR9AbmBayaFIWVP+52q1ir7ea88zocmklDg0SGjiRNXq1tUAljWezpKstKQNz/IZN1kMLQ8SknrlpZL0vjjAnHFlgtLfcwPbESt76surRshfGwwfx8T9AOfXMgELNQIDAQAB
and I should get this:
eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIiwia2lkIjoiYTRkNDAzOWUtYThjNy00ZDA2LTk4YzgtMmJkYTkwYWIxNjljIn0.2hGqQVSbgZ9-9Hiz8VZizORpWRR2yioHb8vK6R9tQCpxr0jxBGehNL0K36XfJWJC5KxcxDdD9byeI_YTtB_hYTgsuMTHS5p-4aJ4nLk43Ya5yR8p8nn4s11wbkfSj0jbqSFb_1IOCMgX0Xu8lmnVe7Tjc4vACwBoaM6VpudEsLHpyQ9OxNaa1apbRp-BX3DEVM3l7ltHhMIh_DCRWbC4-LbS51L4RqLWxmihqRA97FYX4HX38Vbt3O__2tq5KfSjq78UEOffEFe_CRg8mXZ1CHgyH4YPMNmjS-jAI4m07Coja4zLXgv7ctFaFQePISLaZLgdp3a0a-Sht5cwwZfAhg.mc7_YA9mg3l7VV5B.ZOnYjkiXx1YSxDIILjcHUXluwW8jqsSO5NuIkto.9KtJGJRS9QevrqZPYYlcTQ
That's the java code I'm trying to rewrite in python:
private RSAPublicKey getObjectEncryptionKey()
throws NoSuchAlgorithmException, InvalidKeySpecException {
logger.debug("Getting object encryption key");
if (Objects.isNull(objectEncryptionKey)) {
objectEncryptionKey = getActiveKey(Algorithm.RSA);
}
byte[] encryptionKey = base64Decode(String.valueOf(objectEncryptionKey.getEncryptionKeyValue()).getBytes());
KeyFactory keyFactory = getInstance(Algorithm.RSA.name());
return (RSAPublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(encryptionKey));
}
public String encryptObject(Object object) {
logger.debug("Encrypting object with keyId: {}", getObjectEncryptionKeyId());
JsonWebEncryption encryptedObject = getJWEObject(object);
try {
return encryptedObject.getCompactSerialization();
} catch (JoseException e) {
throw new CryptoException("Could not encrypt object/event", e);
}
}
private JsonWebEncryption getJWEObject(Object object) {
JsonWebEncryption jwe = new JsonWebEncryption();
try {
jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.RSA_OAEP_256);
jwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_256_GCM);
jwe.setKey(getObjectEncryptionKey());
jwe.setKeyIdHeaderValue(getObjectEncryptionKeyId());
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new CryptoException("Could not create JsonWebEncryption", e);
}
}
How is it different from my previous question and what is the correct way to do it in python?
I tried doing something like that:
def grouper(iterable, n, fillvalue=''):
args = [iter(iterable)] * n
return zip_longest(*args, fillvalue=fillvalue)
def decryption_key_to_pem(decryption_key: str) -> bytes:
pem = ['-----BEGIN PRIVATE KEY-----']
for group in grouper(decryption_key, 64):
pem.append(''.join(group))
pem.append('-----END PRIVATE KEY-----')
return str.encode('\n'.join(pem))
jwk.JWK.from_pem(decryption_key_to_pem(key))
but I get this exception:
ValueError: ('Could not deserialize key data. The data may be in an incorrect format, it may be encrypted with an unsupported algorithm, or it may be an unsupported key type (e.g. EC curves with explicit parameters).', [_OpenSSLErrorWithText(code=503841036, lib=60, reason=524556, reason_text=b'error:1E08010C:DECODER routines::unsupported'), _OpenSSLErrorWithText(code=109052072, lib=13, reason=168, reason_text=b'error:068000A8:asn1 encoding routines::wrong tag'), _OpenSSLErrorWithText(code=109576458, lib=13, reason=524554, reason_text=b'error:0688010A:asn1 encoding routines::nested asn1 error'), _OpenSSLErrorWithText(code=109576458, lib=13, reason=524554, reason_text=b'error:0688010A:asn1 encoding routines::nested asn1 error')])
Also tried something like:
def get_jwe_key(data, encryption_key, encryption_key_id):
jwe = jwcrypto.jwe.JWE()
jwe.plaintext = json.dumps(data).encode('utf-8')
jwe.alg = 'RSA-OAEP-256'
jwe.enc = 'A256GCM'
jwe.recipient = encryption_key
jwe.header = {'kid': encryption_key_id}
return jwe
jwe_key = get_jwe_key(decrypted_data, key, key_id)
jwe_key.serialize()
and I get: jwcrypto.common.InvalidJWEOperation: No available ciphertext
In the JWCrypto documentation you can find examples for the encryption with JWCrypto. You basically only need to insert your values:
from jwcrypto import jwk, jwe
import json
spki_pem = '''-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9JJaeFiDdB+dGvi3jUzK
UMU73kG6vvc/P+jwZXRKKpJSwf8PU4SapMyFPFFoHwca6Z8vZogF4ghEJ18JipNy
F3BLnfCt1EHuZ15FG1Aywvpi+xw7F0UoJ9DWItBM1SodKXIh1be44/9SiLrpcyRO
KId349zWMOl3IVVxekLPKWTHsy2Iowp7JsjNEK3t9RdV+PAtUzp1ACyqHD/MDYSm
pJuEOR9AbmBayaFIWVP+52q1ir7ea88zocmklDg0SGjiRNXq1tUAljWezpKstKQN
z/IZN1kMLQ8SknrlpZL0vjjAnHFlgtLfcwPbESt76surRshfGwwfx8T9AOfXMgEL
NQIDAQAB
-----END PUBLIC KEY-----'''
data = {"value": "Object Encryption"}
public_key = jwk.JWK.from_pem(spki_pem.encode('utf-8'))
payload = json.dumps(data).encode('utf-8')
protected_header = {
"alg": "RSA-OAEP-256",
"enc": "A256GCM",
"kid": "a4d4039e-a8c7-4d06-98c8-2bda90ab169c",
}
jwetoken = jwe.JWE(payload, recipient=public_key, protected=protected_header)
jewcompact = jwetoken.serialize(True)
print(jewcompact)
Possible output:
eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIiwia2lkIjoiYTRkNDAzOWUtYThjNy00ZDA2LTk4YzgtMmJkYTkwYWIxNjljIn0.DstnMY6WkdCGA1NC0S4JIU3CNVFuUNQSghQyqkh8RpmUyBvnEelvkmWTTf3AApj4jNflnYL_bp8vbeu8PO6CyF1Pi_gUZ1vE1PHHcLD8VZ2eMiIdG08Qq9L7uDlqTIaM6qX0n_uctsm4Y0rflWXrSbr5iwXHxgOQ5XDgLCgg870tObS3RbK2RzrjYRnQs-_hK4R8LRJgCEKeV__fzmU6nx5jA4qXXs_U3y9Uxs3_4OE-xSelPT_yY5xCMs8fAHvaua92mrRCMSu9cp9iAYW8qu3bYdUFjnWqifOQIUB2HljqMH85tCxS02tBuVPs52b8pgNUckqa_v43BvxTbnwuJg.RWtp5j38l8mz_qoJ.2VBsxt1zyk5rAmSvg3k0eMLzIAPo9ttn-7dLB2nP.k4k9ZnCRRA1im2sbvMLlbQ
An encrypted token consists of 5 parts, the header, the encrypted key, the IV/nonce, the actual ciphertext and the tag, each separated by . and Base64url encoded. Since AES generates a different key and IV/nonce for each encryption and RSA encryption is non-deterministic, a different encrypted token is generated for each encryption except for the first part. So you should not expect that the code generates the same encrypted token that you posted.
The generated token can be decrypted using the private key associated with the posted public key and the decryption code of the linked post. This is also the right way for a test.
Edit:
Regarding the first part of your question from the comment: If the header
eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIiwia2lkIjoiYTRkNDAzOWUtYThjNy00ZDA2LTk4YzgtMmJkYTkwYWIxNjljIn0
of the encrypted token is Base64url encoded, you get:
{"alg":"RSA-OAEP-256","enc":"A256GCM","kid":"a4d4039e-a8c7-4d06-98c8-2bda90ab169c"}
Here A256GCM as enc means that the plaintext is encrypted with AES-256 in GCM mode (symmetric encryption), with a random key being generated for the AES encryption.
RSA-OAEP-256 as alg means that this AES key is encrypted with RSA and with OAEP/SHA256 as padding (asymmetric encryption).
Bear in mind that RSA encryption always uses the public key (of the recipient).
So both cryptography types (symmetric and asymmetric) are involved, which is also called hybrid encryption.
The private RSA key does not come into play at all during encryption, but only during decryption, as you can verify in the linked post. There, the encrypted token is decrypted with the private RSA key (which must be associated with the public key that was used for encryption), meaning more precisely that the symmetric AES key is decrypted with RSA (using the private RSA key), which is then used to decrypt the actual ciphertext.
Regarding the second part: The format and type of the key can be determined with an ASN.1 parser, e.g. https://lapo.it/asn1js/.
The key you posted is an ASN.1/DER encoded public RSA key in X.509/SPKI format that is Base64 encoded. A public RSA key essentially contains modulus and public exponent. A private key also contains the private fields and also uses other formats.
I want to achieve the same result in Dart and in Python in encryption using AES.
My problem is that my Python version only outputs the first two bytes successfully and nothing else.
In doing so, I (must) start in Dart.
As seen in the code, I generate an IV and a key, which is passed as a parameter.
List<String> aesEncryption(List<int> key, String message) {
final iv =
enc.IV.fromLength(16); // IV generation
final aesKey = enc.Key(Uint8List.fromList(
key)); // transform the key into the right format
enc.Encrypter encrypter =
enc.Encrypter(enc.AES(aesKey, mode: enc.AESMode.ctr));
final encryptedMessage = encrypter.encrypt(message, iv: iv);
// Here I send my encrypted message and IV to my Flask app, to use the same IV and to
// compare the results with eacht other
return [encryptedMessage.base64, iv.base64];
}
Then, eventually, I just encrypt the message "test" with the following key:
The sha256 Hash of the string "low;high"
I then send this tuple to my Flask app and receive the data as follows:
#app.route("/test", methods=["POST"])
def test():
values = request.get_json()
iv = base64.b64decode(values["iv"]) # my IV which was sent of the Dart App
sig = values["signature"] # just the AES encryption value of the Dart App
key = values["key"] # the string "low;high"
aes_key = hashlib.sha256(key.encode('utf-8')).digest() # as mentioned before, I have
# to hash the string "low;high" first and I will use this as my aes key
# I tested if the computed hash values are the same in python and dart, and yes it is so
# I create a Counter with my IV, I do not completely understand this part, maybe here is my mistake somewhere.
counter = Counter.new(128, initial_value=int.from_bytes(iv, byteorder='big'))
cipher = AES.new(aes_key, AES.MODE_CTR, counter=counter)
test = "test".encode('utf-8')
# The output of this print is: b'\xd52\xf8m\x0b\xcb"\xa7\x0e\xf7\xab\x96\x0f\xfc\x9f\n'
print(base64.b64decode(sig))
encrypted = cipher.encrypt(test)
# The output of this print is: b'\xd52\xf8m'
print(encrypted)
return "", 200
As you can see only the first two bytes are output of the python AES encryption and unfortunately, I don't understand why. I hope someone can help me with this because this implementation is essential for me.
Thank you in advance for every helpful answer!
Kind regards,
Bagheera
I was able to find the error again pretty quickly.
The problem is that automatic PKCS7 padding is used in Dart and not in Python.
So the solution (just for the matter that the encryption values are the same) is the following in Dart:
enc.Encrypter encrypter =
enc.Encrypter(enc.AES(aesKey, mode: enc.AESMode.ctr, padding: null));
New rephrased Question
There are two programs that work together, a client and a server.
The client is having issues decrypting, and i have ran the following test on the client without any server interaction and this does not work.
I get rsa.pkcs1.DecryptionError: Decryption failed when i run this code on the client.
# Public key saved in ini file as this format "PublicKey(n, e)"
# Private key saved in ini file as this format "PrivateKey(n, e, d, p, q)"
key_string = public_key.strip("PublicKey(").strip(")")
n, e = key_string.split(", ", 1)
value = rsa.encrypt(b"Hello", public_key)
key_string = self.private_key.strip("PrivateKey(").strip(")")
n, e, d, p, q = key_string.split(", ", 4)
private_key = rsa.PrivateKey(int(n), int(e), int(d), int(p), int(q))
decrypted = rsa.decrypt(value, private_key)
Old "Question" asked
I am writing a python program that is essentially a P2P chat
application utilising a rendezvous server for new connections.
Walkthrough of the steps taken by client/server.
Client:
Connects to server using sockets
Sends its public key to server
Server:
Reads public key
Creates AES key and ciphers a message (list of already connected peers)
Encrypts the AES Key using the clients RSA public key
Sends the key and ciphertext
Client:
Reads the information and splits into the key portion and the ciphertext portion
Decrypts the AES Key (However this fails even though the same code works on the server to decode)
Decrypts the cipher text using the now unencrypted AES Key
# Encrypt with AES cipher_text, key, nonce = self.aes.encrypt(json.dumps(message))
# Encrypt AES Key with RSA encrypted_key = self.rsa.encrypt(key, peer['public_key'])
# Send data to peer self.socket.sendto(encrypted_key + nonce + cipher_text, peer['address']) ```
``` CLIENT CODE
data, address = self.socket.recvfrom(65536) recv = {"key": data[:256],
"nonce": data[256:272], "data": data[272:]}
key = self.rsa.decrypt(recv["key"]) peers =
json.loads(self.aes.decrypt(recv["data"], key, recv["nonce"])) ```
Solved this, Thanks for the help!
The error was with my import of the config file, my statement was checking if there was a valid RSA-pub/priv key and if there wasn't it would generate a new pair for the user.
The problem was it was always generating a new keypair. meaning it was attempting to decrypt with the incorrect private key.
I'm trying to understand the pyCrypto encrypt and decrypt methods for public and private keys, and I'm seeing something strange. Suppose I have a set of private and public keys, stored in files dummy_private.txt and dummy_public.txt.
I create a private key object and public key object like this:
private_key_file='dummy_private.txt'
f = open(private_key_file, 'r')
privateKey = RSA.importKey(f.read(),None)
f.close()
public_key_file='dummy_public.txt'
f = open(public_key_file, 'r')
publicKey = RSA.importKey(f.read(),None)
f.close()
Now suppose I want to encrypt some message. I can do it like this:
s='This is a super secret message'
sutf8=s.encode('utf8')
enc=publicKey.encrypt(sutf8,None)[0]
encb64=base64.encodestring(enc)
print "Public key Encoded message is %s" % (encb64,)
This makes sense because I am encrypting with the public key and I should be able to decrypt with the private key.
However, I can also encrypt the above using the private key, and it gives me the same result!
enc2=privateKey.encrypt(sutf8,None)[0]
encb642=base64.encodestring(enc2)
print "Private key Encoded message is %s" % (encb642,)
When I print out the base64 encoded version of the encrypted data, using either the private key or the public key, they are the same! Why is that?
And this raises the problem of digitally signing something with the private key. If I can sign something with the public key and get the same results, then how does signing verify that I am who I say I am? This must be some issue with the encrypt method that I don't understand. Can someone please explain?
Since encrypting with both the public key and private key gives the same results, it appears that decrypting with the private key can be done regardless of whether the encryption was done with the private key or the public key. I'm totally confused as to why one could encrypt with the private key and get a result that is the same as if it were done with the public key.
When you encrypt with a private key, pycrypto is actually using the public key (which can be generated from the private key).
Source: PyCrypto: Decrypt only with public key in file (no private+public key)
You'll find that pycrypto doesn't allow you to decrypt using the public key for good reason:
>>> publicKey.decrypt(enc2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python2.7/site-packages/pycrypto-2.6-py2.7-linux-x86_64.egg/Crypto/PublicKey/RSA.py", line 174, in decrypt
return pubkey.pubkey.decrypt(self, ciphertext)
File "/usr/local/lib/python2.7/site-packages/pycrypto-2.6-py2.7-linux-x86_64.egg/Crypto/PublicKey/pubkey.py", line 93, in decrypt
plaintext=self._decrypt(ciphertext)
File "/usr/local/lib/python2.7/site-packages/pycrypto-2.6-py2.7-linux-x86_64.egg/Crypto/PublicKey/RSA.py", line 239, in _decrypt
mp = self.key._decrypt(cp)
File "/usr/local/lib/python2.7/site-packages/pycrypto-2.6-py2.7-linux-x86_64.egg/Crypto/PublicKey/_slowmath.py", line 52, in _decrypt
raise TypeError("No private key")
TypeError: No private key
Mathematically, RSA makes it possible to encrypt with the private key and decrypt with the public key, but you're not supposed to do that. The public key is PUBLIC - it's something you would readily share and thus would be easily disseminated. There's no added value in that case compared to using a symmetric cipher and a shared key (see: https://crypto.stackexchange.com/questions/2123/rsa-encryption-with-private-key-and-decryption-with-a-public-key)
Conceptually, "encrypting" with the private key is more useful for signing a message whereas the "decryption" using the public key is used for verifying the message.
More background: exchange public/private key in PKCS#1 OAEP encryption/decryption
To connect a server, I've found that, using PHP, I need to use openssl_seal(). That's OK, but I want to use Python. I'm not able to convert openssl_seal() in an equivalent function.
Can you help me?
This is what openssl_seal() does:
Description
int openssl_seal ( string $data , string &$sealed_data , array &$env_keys ,
array $pub_key_ids )
openssl_seal() seals (encrypts) data by using RC4 with a randomly generated
secret key. The key is encrypted with each of the public keys associated
with the identifiers in pub_key_ids and each encrypted key is returned in
env_keys. This means that one can send sealed data to multiple recipients
(provided one has obtained their public keys). Each recipient must receive
both the sealed data and the envelope key that was encrypted with the
recipient's public key.
this blogpost has a very detailed description of what's going on inside openssl_seal(). It also has an implementation in java.
From this, I would think it should be relatively straightforward ("the proof left as an exercise to the reader" kind of straightforward) to do an equivalent implementation in python using pyopenssl, which includes RC4, or the newer, but for these purposes more focused tlslite.
What openssl_seal does is:
Extract the public_key from the certificate
Generate a 128 bits (16 bytes) long random_key (this will be used to encrypt the message using a symmetrical algorithm, since it's faster)
Encrypt the random_key using PKCS #1
Encrypt the message using ARC4 a secure cipher method and the random_key (Note that ARC4 is no longer considered secure and that PHP strongly recommends to explicitly specify a secure cipher method using the cipher_algo param)
Output the encrypted_random_key and the encrypted_message
The receiving party can then decrypt the encrypted_random_key using their private_key and then decrypt the encrypted_message using the random_key.
Since there's no way of doing this in Python via the standard library, I'm just gonna' throw out the 3 approaches that I've tried out:
# pyca/cryptography (cryptography.io) version
# pip install cryptography
import os
import cryptography
from cryptography import x509
message = 'Super secret secret message'
message = message.encode('utf-8')
certificate_data = open('/path/to/certificate.cer', 'r').read()
certificate_data = certificate_data.encode('utf-8')
certificate = cryptography.x509.load_pem_x509_certificate(data=certificate_data, backend=cryptography.hazmat.backends.default_backend())
public_key = certificate.public_key()
random_key = os.urandom(16)
encrypted_random_key = public_key.encrypt(plaintext=random_key, padding=cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15())
print(encrypted_random_key)
algorithm = cryptography.hazmat.primitives.ciphers.algorithms.AES(random_key)
cipher = cryptography.hazmat.primitives.ciphers.Cipher(algorithm=algorithm, mode=None, backend=cryptography.hazmat.backends.default_backend())
encryptor = cipher.encryptor()
encrypted_message = encryptor.update(message)
print(encrypted_message)
.
# M2Crypto version
# pip install pip install git+https://gitlab.com/m2crypto/m2crypto#python3
import M2Crypto
message = 'Super secret secret message'
message = message.encode('utf-8')
certificate = M2Crypto.X509.load_cert('/path/to/certificate.cer')
public_key = certificate.get_pubkey()
rsa_pub = public_key.get_rsa()
random_key = M2Crypto.Rand.rand_bytes(16)
encrypted_random_key = rsa_pub.public_encrypt(random_key, M2Crypto.RSA.pkcs1_padding)
print(encrypted_random_key)
cipher = M2Crypto.EVP.Cipher(alg='aes_128_cbc', key=random_key, iv=b'', op=M2Crypto.encrypt)
encrypted_message = cipher.update(message)
encrypted_message += cipher.final()
print(encrypted_message)
.
# PyCrypto version
# Update: PyCrypto 2.x is unmaintained, obsolete, and contains security vulnerabilities!!!
# pip install pycrypto
# Please bear in mind that PyCrypto cannot handle x509 certificates.
# You will have to extract the public_key to a pem file:
# openssl x509 -inform pem -in certificate.cer -pubkey -noout > public_key.pem
from Crypto import Random
from Crypto.Cipher import ARC4
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA
message = 'Super secret secret message'
message = message.encode('utf-8')
public_key_data = open('/path/to/public_key.pem', 'r').read()
public_key = RSA.importKey(public_key_data)
random_key = Random.new().read(16)
cipher = PKCS1_v1_5.new(public_key)
encrypted_random_key = cipher.encrypt(random_key)
print(encrypted_random_key)
cipher = ARC4.new(random_key)
encrypted_message = cipher.encrypt(message)
print(encrypted_message)
You can check out my post at => http://helpfulsheep.com/2017-09-01-openssl-seal-in-python/
Since I can't post comments yet, I need to add to Gabi Nagy's answer, that while their answer describes a correct algorithm, it is not the same as using openssl_seal() function.
OpenSSL doesn't let unencrypted key to get outside of OpenSSL structures. It generates key somewhere inside and keeps it there, giving you only encrypted key. The crucial difference is that when OpenSSL cleans it's structures, it should dispose of unencrypted key in a safe way.