For the project I am currently working on, we had a need to decode data in Python that had been encoded using PHP’s openssl_seal() function. Python does not appear to have a direct equivalent function. The M2Crypto package can decode quite a few different formats, but openssl_seal() and openssl_open() aren’t included.
Fortunately, M2Crypto contains the components required to decode this data, but you have to jump through a couple of hoops to get there.
Symmetrical and asymmetrical encryption
There are two main types of encryption – symmetrical and asymmetrical. Symmetrical encryption uses the same mechanism to encrypt and decrypt the data. Typically you just use the encryption mechanism a second time on the encrypted data to get back to the plaintext version of the data. The key point is that the process is reversable if you know the key.
Asymmetric encryption uses two keys – a public key and a private key. Once you have encrypted your data with the public key, you cannot reverse the process. The only way to decrypt the data is with the private key.
Symmetrical encryption tends to be a fast process. Asymmetric encryption is fairly processor-intensive – especially if you have a long key.
If you are encrypting small amounts of data, you can probably get away with using asymmetric encryption. But if you want to encrypt larger amounts of data there is a more efficient way to handle the encryption – and this is what openssl_seal() does.
How openssl_seal() encrypts data
openssl_seal() uses two encryption mechanisms. It uses a symmetrical encryption mechanism called RC4 to encrypt the data, having first picked a random key for the encryption. Then it uses asymmetric encryption to encode the key. This gives the benefits of public/private key encryption without using large amounts of processor time. The processor-intensive portion of the operation is limited to the encryption of the RC4 key, which is relatively short.
openssl_seal() allows data to be encoded for multiple recipients. In that situation, it encodes the data once using RC4 and then encrypts the key for each recipient using their public key. The resulting encrypted key is called an envelope. You would then send the encrypted data and the appropriate envelope to each recipient. If you send the wrong envelope to the wrong recipient they will not be able to decode it using their private key, and so they will not be able to decode the RC4 data.
Some example data
This data comes from my previous tutorials – OpenSSL and PHP (parts one and two). For the purposes of this tutorial I shall assume that the reader is a Python programmer rather than a PHP programmer, so I’m only showing the data that we will be decrypting rather than the PHP code used to create it.
You will need the M2Crypto package installed in order to decode this data.
Firstly we need our private key:
-----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQDVK04tGOVw+ya5s1t7CeRYrAyLFd/YudfwOx6eHB+D1rFjj5q8 phMTdVDr+XjS8EVVNJzcQhuoYeiyLS2jeZyd6VIiT13W5spcOd8icMp0zWjQvFSH z3iQadyIfu2b6H/uABRA3q8bXzXLlpQ70RurSnu1a6K/jRDlpdI1JIIi0QIDAQAB AoGARqDMjCCdlKuCDzEf9Eo4wDBxD3w16Ibaxxuvb09+GZ5+s7AW4r5bv6y1HRpR MAKv1iVSe5/jrgySnsZdQra+bvC1sjaSbBrTtdMRlWaWcJ2eK5eCyJopDA2QQMF2 4pMjukA/AkFPdAkvlAv2AtRnBqtzxdgvQXR01YnBhx8oTuECQQD0LLm5GNQSCAM2 AyNRiKm9GJ23KEdyHNsgM6bH+7npKySbxB6VjzySbObVZ3iP4HGxmsJWXdxnq0Ol NGbzUixVAkEA334sbuJ9K0wXDgFjQt0Sb/0Xe7+e1exRNc1m9t9ZWvTlo94rYitK Vl/PU5PpoxA6NGDyMATBhoQ9H5ZF93jYjQJAZI3qqhRUeVx9Xgfqyo/6PtpdUOkw iwjhIKDExUSgKirPN6qLYdIMAs0APtAOsUmf6KEv+PtMYhEAmY87+mZTxQJAAYVc TAziiS7lYUUFJelXPMfeJwtwy0fmbZVORBPVCdds0KasaOieguP4BzuUdXWgz8Zx H36Iyp+Pwu1E4KBD6QJBAPP4knEVJfbFl+Jhl9DbBfvWtx4vTvKdHQaMp3XgSIzg VQZheyFObscs3lsaBF3jSb3nkF5a06NwR2EcmdpZc7Q= -----END RSA PRIVATE KEY-----
This will typically be stored in a file or a database. For this example I will not show the public key, which was used to encrypt the data. It is not required to decode the data.
Next we have the encrypted data (I have base64-encoded it for readability):
and finally, the envelope (again, base64-encoded for readability):
g3AGJK8DMHejGjq6hZ2iUTFvyV0cdbHJKJUEFgw6LEco/6vot9biLLGp3AdnKYC2J7fxU/s6kMM5DpiW 9IlU4M0+deH+1I7qsPuui0VfdjqULw9QyO6IzV14HvBR9YRen0TCZm3ezH+flSgiMCw9z8Egs0DzuaEV zha8CHuE00c=
In this example, the data that I’ve encrypted is very short. But if the data had been longer, the encrypted data would be longer but the envelope would not. The encrypted data is typically the same length as the plaintext data and the envelope is the same length as the asymmetric key (for instance, 1024 bits).
Decrypting the example data (envelope)
Firstly the private key needs to be loaded. This will typically be stored in either a file or a database. If it is in a file you can load it using a single command. If it is anywhere else, you load the string version and then load the key from that:
Assuming the key loads correctly, the next step is to decrypt the envelope asymmetrically:
Assuming the data decrypts (you might be trying to use the wrong private key, or the data is corrupted), you get the key for the RC4 encryption. In this example, the key (base64-encoded) is:
Decrypting the example data (RC4)
RC4 is a stream cipher. This means it encrypts and decrypts data on the fly, rather than all in one go. This is why the encrypted data is normally the same length as the unencrypted data.
In the case of openssl_seal(), RC4 is used to encrypt all of the data in one go, but RC4 can be used for other purposes too.
Because RC4 is a stream cipher, you can’t do a single call to M2Crypto.RC4 to encrypt or decrypt data. There may be additional data coming later (in the stream). The state of the encoder changes as it encodes, so if you feed the same string in multiple times in a row, you will get different encrypted data out each time. For this reason, M2Crypto.RC4 uses an object rather than static calls.
To decode the RC4 data, you first need an RC4 object initialised with the RC4 key that you’ve decoded:
If you need to reset an RC4 object back to its initialised state, you don’t need to delete and recreate the object – you can just call the set_key() method on it:
Finally, you call the update() method on the RC4 object to decrypt your data:
This should decrypt the encoded data back into plaintext for you. In this case:
A complete function for decoding openssl_seal() data
A function to decode data encrypted using PHP’s openssl_seal() would look something like this:
def openssl_open(cipher_text, cipher_envelope, key):
plain_envelope = key.private_decrypt(cipher_envelope,
rc4 = M2Crypto.RC4.RC4(plain_envelope)
plain_text = rc4.update(cipher_text)
This assumes that key is the private key, already loaded using the appropriate load_key function. Note that this example has no error trapping.
An important caveat about RC4 encryption
I mentioned this in my PHP tutorial, but it is worth mentioning here too. The asymmetric encryption mechanism includes integrity checking. So if the data is corrupted or tampered with, the entire decryption will fail.
On the other hand, RC4 does not have such integrity checking. It is entirely possible for data to be corrupted or tampered with and the decryption will still appear to have worked. It won’t generate an error, but the decrypted data will be incorrect.
It is unlikely that somebody tampering with the data will be able to manipulate the data in such a way as to get a specific result in the output data (since they would not be able to decrypt the data), but they could still cause things to break that generate problems further on in your processes.
A simple solution is to generate a hash of your data prior to encrypting it, using MD5, SHA1 or similar. When you decrypt the data, it is then a simple process to split the hash from the data and check that it matches. If the match fails, you know there is an issue with your data.