Making RSA encryption compatible in Python (PyCrypto) and PHP (OpenSSL) - php

I am migrating an entire PHP API, and while I used PyCrypto before, I am not sure how to translate the following encryption call, since I need the exact same result. The PHP call is:
define('KEY', "-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC81t5iu5C0JxYq5/XNPiD5ol3Z
w8rw3LtFIUm7y3m8o8wv5qVnzGh6XwQ8LWypdkbBDKWZZrAUd3lybZOP7/82Nb1/
noYj8ixVRdbnYtbsSAbu9PxjB7a/7LCGKsugLkou74PJDadQweM88kzQOx/kzAyV
bS9gCCVUguHcq2vRRQIDAQAB
-----END PUBLIC KEY-----");
$cypher = "";
$result = openssl_public_encrypt($plain, $cypher, KEY, OPENSSL_PKCS1_PADDING);
echo bin2hex($cypher);
Assuming everything goes right, this prints the content from $cypher, passed to hexadecimal. For a sample input "azzzzzzzzzzzzdfdf" I get something like: "2281aeebc1166cdfb2f17a0a0775d927ca5a9ad999bae0e4954f58bd8082fdf7efe1fd284876530341f714456d7eb8cd44c57b20ab27029b84d5dc77a674bede3fe9065282931404286082e9df8607bdcff0818b90324dfee7d76b566d0f99bebc5cc913372c276ba373712128f1bcc226b59367cff93f7cdd6dbde25b366863".
I must assume this value as right, since the code was taken from an existing API I am migrating. However, trying the same with PyCrypto (yes, I am migrating the API to be available in Python), I use the following code:
def bin2hex(s):
return "".join([hex(ord(c))[2:].zfill(2) for c in s])
KEY = """-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC81t5iu5C0JxYq5/XNPiD5ol3Z
w8rw3LtFIUm7y3m8o8wv5qVnzGh6XwQ8LWypdkbBDKWZZrAUd3lybZOP7/82Nb1/
noYj8ixVRdbnYtbsSAbu9PxjB7a/7LCGKsugLkou74PJDadQweM88kzQOx/kzAyV
bS9gCCVUguHcq2vRRQIDAQAB
-----END PUBLIC KEY-----"""
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA
encrypter = PKCS1_v1_5.new(RSA.importKey(KEY))
print bin2hex(encrypter.encrypt("azzzzzzzzzzzzdfdf"));
While I expect the same value be returned and printed, the value finally is "3dd94ffabd01bb0e94010c0fedbcd4eb648f12e5d9e6d934b77ae86f76681d8a1b790cad9fddf6e6720415b4d645e525c33c402fa9778739b8e461790387e9508f7158a5fdc5723f5fc26d166b11a00759f0e0ee3ba6719a2e7c6b918f66e1311d1fff878ee2ca8762e1d6120f1e9585a76cdc7719ca20129ae76182b4277170".
Using PKCS1_OAEP outputs "290f60f37088c2cb46ae9221b01ff46a463f270ef7cf70bbea49de0b5ae43aec34a0eb46e694cf22f689eb77e808c590fdc30eda09f9d3f3cb8c15e0505bf5a984c2a121bc9fa83c6b5ccf50235f072467b4ae9cdf0f0ee2e486626ffa62ad1aa715fbe29e8afe4ceab3ca5a5df4c1dc75d7f258285d7ff1f4f2b4dcb7a8413a".
It is easy to tell that I must fix my python code. How can I fix my python code so it returns the exact same result as the given PHP call?

Your code is fine. PKCS#1 v1.5 padding in pyCrypto is randomized (source). So the encryption will always be different even if you use the same key and plaintext. This is a desirable property.
If you want to check compatibility between pyCrypto and PHP's OpenSSL extension, then you would need to encrypt in one, decrypt in the other and check that you got what you expected.
PKCS#1 v1.5 padding should not be used nowadays, because there are efficient attacks against it. OAEP is a much better alternative.

I've struggled with the same problem for quite long but finally managed to solve this.
The main issue was setting proper SHA hashes, in case of PHP's openssl_public_encrypt() it should be SHA1.
Here's the code for openssl_public_encrypt() with OPENSSL_PKCS1_OAEP_PADDING:
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
def encrypt_key(message):
with open('public_key', 'rb') as key_file:
pub_key = key_file.read()
public_key = load_pem_public_key(pub_key, default_backend())
ciphertext = public_key.encrypt(
message,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA1()),
algorithm=hashes.SHA1(),
label=None
)
)
return ciphertext
basically a copy of cryptography.io documentation regarding RSA.

Related

Dart: How to use the `AES-256-CBC` method in encrypt package?

My PHP server uses the encrypt as follows.
openssl_encrypt('data', 'AES-256-CBC', '1234567890123456', 0, '1234567890123456')
the result is adVh7c/vcyascTS0Z669IA==.
My dart server uses encrypt package as follows.
import 'package:encrypt/encrypt.dart' as encrypt;
Encrypter(AES(encryptKey, mode: AESMode.cbc)).encrypt('data', iv: '1234567890123456').base64
final encrypt.Key encryptKey = encrypt.Key.fromUtf8('1234567890123456');
final encrypt.IV encryptIvKey = encrypt.IV.fromUtf8('1234567890123456');
final encrypt.Encrypter encrypter = encrypt.Encrypter(encrypt.AES(encryptKey, mode: encrypt.AESMode.cbc));
print(encrypter.encrypt('data', iv: encryptIvKey).base64);
The result is KQjJ76efmVlgGKDsj6dCog==.
These result values are different.
I saw the cipher method of PHP. If I change the cipher method in the PHP server from
AES-256-CBC
to
aes-128-cbc // or aes-128-cbc-hmac-sha1, aes-128-cbc-hmac-sha256
The result will be KQjJ76efmVlgGKDsj6dCog==. (same as the result from the dart server)
But editing files in the PHP server is the last choice.
What I can do in the dart server to make the result the same as the result from the PHP server (AES-256-CBC method)?
How to use the AES-256-CBC method in encrypt package?
If I must edit files in the PHP server, what method I should use?
The aes-128-cbc, aes-128-cbc-hmac-sha1 and aes-128-cbc-hmac-sha256 give the same result. Or some method better than this and it is available in encrypt package as follows in this image. Suggestion me, please.
The summary from the comment in my post by #Topaco.
The aes-256-cbc cipher method requires a 32 bytes key.
Use the key with a string length of 32 or use the padRight(32, '\x00') function.
example:
final encrypt.Key encryptKey = encrypt.Key.fromUtf8('1234567890123456'.padRight(32, '\x00'));
Regarding aes-128-cbc, aes-128-cbc-hmac-sha1 and aes-128-cbc-hmac-sha256: Apply aes-128-cbc(ref)

How to convert rsa from python to php?

I am trying to encrypt a password to sent to through an API for authentication. My situation is quite similar to another encrypt example
The python part is form see the get_pwd_rsa function
Python part:
import rsa
key = rsa.PublicKey(n, e)
encropy_pwd = rsa.encrypt(message, key)
binascii.b2a_hex(encropy_pwd)
And I have try using phpseclib to solve my problem, but the encrypt result is not correct:
use phpseclib\Crypt\RSA;
$rsa = new RSA();
$rsa->loadKey([
'n' => new \phpseclib\Math\BigInteger($n, 16),
'e' => new \phpseclib\Math\BigInteger($e, 16),
]);
$ciphertext = $rsa->encrypt($message);
return bin2hex($ciphertext);
The encrypt result is not correct and I'm not sure which part am I missing.
To add to what James said, my suspicion is that whatever Python library you're using is doing PKCS1 padding if it's doing any padding at all.
Contrast this with phpseclib which uses OAEP padding by default. PKCS1 padding can be enabled but it defaults to the more secure OAEP padding.
Further, both PKCS1 and OAEP encryption are randomized. I say that because I kinda wonder if you're saying the PHP code is wrong because it doesn't give the same result as the python code. It shouldn't. If you run a round of RSA encryption with phpseclib twice with the same plaintext and key each time you'll get a different ciphertext each time. PKCS1 and OAEP were designed this way to protect against known plaintext attacks.

python string format pkcs8 RSA signature

I need to use pkcs8 private key in my project as example below, I can't found any library or method relate to pkcs8 signature only pkcs1 are available for the Crypto.Signature.
My python code example:
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA
from Crypto.PublicKey import RSA
privatekey='''-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAOuxJs5RD6s2ECEV
C38Jfj6xBBrcX6W1Qy+GLiXhh+wKj650BPSerQNQI+XPx/+uUemk/dJM3RMm75Di
mXJlp7/5LY11VyKSqS+x6ELIvWz8lo2OhHW6/wU4jiQ1MlburYbn/pRRClGGA8i4
FjHMvMu9/QCNPlNTlf7qJDO6lDVlAgMBAAECgYB0iP76/DGXIgAPm2w3v+Xf8X5q
GZRhRqKVmO6wZDbkisRIKa1ZlitNfA6DzpzA2tw9fgrSNJcKpTHGnYPpgEHUQrcw
t471yuAvzAOBffaqVjIt7vKyL3QnkNfxWenliasacX3olwBpKPrkvG75H2KBe9S0
WKpju1Cm6MebAJ3z8QJBAP8gSx3l9nNTqJcBBmP+TGDcTL9Sif2nBZrU/pWnzzop
HolaFIQTYXPIj8rMoVT/c0gLo1gcdHoSUJTbB0WO7V8CQQDsf9FAWCI3fkWgS/fW
21ApO6B07/388CpNHOTjvfsdlent4yXLL+Guh5apMH4Oq0tkGkEij5YPG1EZSYXi
48+7AkEAiX3m3ZMMMXTZe5/CyOrIUL8I4WbjFP8JJzs4hICuTmLQoScZvWAQeeyR
ibKkE4GjqCUVf6u+Hfd20/ICRjtTswJANCjEh8JoWYDZ7k6S7KoV9eIWs3OyurRl
P/idarUdyxqjKzorvbJjvdBdpBbz1lxlFkDMGMk+OTq3GjKi+rVvvQJAGhBieR2Q
69JNeoOg7J8Xk7evNBJHwtJfRDQXKJpN5YZsKclf6oRaYFEQOM6ThuNoYHISfGKw
UbDoUsPZoQFPtQ==
-----END PRIVATE KEY-----'''
signstr = testdata123
key = RSA.importKey(privatekey)
h = SHA.new(signstr)
signer = PKCS1_v1_5.new(key)
signature = signer.sign(h)
sign = base64.b64encode(signature)
The signature generated by code above do not match the public key in the api system I am using.
For PHP side I am able to sign correctly. The working code for PHP as below:
$private_key= openssl_get_privatekey($private_key);
openssl_sign($signStr,$sign_info,$private_key,OPENSSL_ALGO_MD5);
$sign = base64_encode($sign_info);
Any advice on how to sign with RSA pkcs8 format private key using python is much appreciated.
Hash algorithm
Of course, it must be different, because you're using different hash algorithms.
Your Python code uses SHA-1 and your PHP code uses MD5. The default hash algorithm in PHP is OPENSSL_ALGO_SHA1 as the documentation describes:
if (!openssl_sign($signStr, $sign_info, $private_key)) {
echo "Failed to sign";
}
Key encoding
There are many ways of encoding a private key. Two of the most common ways are PKCS#1 and PKCS#8 encodings. See more information on that here.
The key encoding is not a concern here, because both pycrypto and openssl use the correct one here.
Padding
There are two common padding schemes that are used for signing: PKCS#1 v1.5 padding and RSA-PSS (technically RSASSA-PKCS1-V1_5 and RSASSA-PSS). Both of them are defined in PKCS#1, but the latter was introduced in PKCS#1 v2.x.
Pycrypto supports both padding schemes, but openssl only supports PKCS#1 v1.5 in PHP.
Compatibility
If you want to check for compatibility of the same algorithm between different languages, you need to sign in one using the private key and verify the signature in the other using the public key. After you've done that, you can try the other way around if you also need the other direction.
This is only relevant for RSA-PSS, because that is randomized.
If you're using PKCS#1 v1.5 padding which is not randomized, you should be able to create a signature in both Python and PHP and compare them directly to check for compatibility.

Decryption using Public Key in Python

The question is asked repeatedly, but I am unable to get work done so posting again.
I am shared a public key file, which is in format of
-----BEGIN CERTIFICATE----- XXXXXXXXXXXXXXXXXXXXXXX -----END CERTIFICATE-----
Using this file, in PHP I am able to perform decryption and obtain decoded message using openssl_public_decrypt(). Now I am trying to perform decryption process in Python. I have tried with:
Crypto - gives 'valueerror: rsa key format is not supported' when RSA.importKey() is called.
python-rsa and M2Crypto also didn't help.
For M2Crypto I referred this link, but RSA.load_pub_key_bio(bio) line of code gives error.
Any help is really appreciated. Thank you.
You can do this with the cryptography library's hazmat layer (note that cryptography is now the back-end library used by pyOpenSSL; while pyOpenSSL is convenient for some uses, cryptography provides a much more complete OpenSSL binding as well as useful higher-level (safer, better) cryptographic primitives and bindings to other cryptographic libraries as well). Before you do, note that it's very easy to shoot yourself in the foot with the hazmat layer and the cryptography team won't apologize if you do.
You should be sure you understand what's going with these operations before you rely on the code to provide any kind of security. Also, if you control the full stack - from encryption to decryption - stop doing it this way. Use something like PGP instead.
>>> from cryptography.hazmat.backends import default_backend
>>> from cryptography.hazmat.primitives import serialization
>>> with open("key.pem") as key_file:
... pkey = serialization.load_pem_private_key(key_file.read(), password=None, backend=default_backend())
...
>>> from cryptography.hazmat.primitives.asymmetric import padding
>>> ciphertext = pkey.public_key().encrypt(b"asdasd", padding.PKCS1v15())
>>> print repr(ciphertext)
'*\xf3\x8e2T\x8f\x93\xae}\x18\x9f7\x00\xbcw\xbd$\x14\x9f6\x9a\xc3\xa1\xa8\xf3\xa5\xcc\xae\x89\x17]\x91\x1d\x85\xb0.\xf7&\x12w\x0ca\x1cN\xd2\x8f\xf5\xf7\xfe\x93\xfbL\x17#\xc6g\x1dj\x84\xc3ET\xd1\x92 \xd2u\xc7AF\xa9<4i`d\xdb\xc0%\xae\x06\xc4\xeeJsC\x06\x80\xc9* \x11\x99P\xdc\xa9S\xad\xe2\xe5L\x9f\x9f\x0c\x04\xef\x95\xd7:*\x06#\r|e\xcdL\xfe9\x80R\x82c\x00\xc0\x9as\xea'
>>> pkey.decrypt(ciphertext, padding.PKCS1v15())
'asdasd'
>>>

Converting RSA encryption code from php to python

I'm trying to connect to a api in my python app .
so the api documentations comes with php and asp sample code but no python
I'm pretty good with php but have no experience with encryption ... I'm trying to re-write python code for the api using php sample .
They use this class for RSA
https://github.com/AlaFalaki/Pclass/blob/master/libraries/rsa.class.php
(Since its a RSA lib im guessing python RSA lib would take care of this part ) :
static function rsa_sign($message, $private_key, $modulus, $keylength) {
$padded = RSA::add_PKCS1_padding($message, false, $keylength / 8);
$number = RSA::binary_to_number($padded);
$signed = RSA::pow_mod($number, $private_key, $modulus);
$result = RSA::number_to_binary($signed, $keylength / 8);
return $result;
}
Here is the problem php sign function takes 4 arguments uses some internal functions ... But python rsa has 3 and one of them is just the hash method !
rsa.sign(message, priv_key, hash)
Parameters:
message – the message to sign. Can be an 8-bit string or a file-like object. If message has a read() method, it is assumed to be a file-like object.
priv_key – the rsa.PrivateKey to sign with
hash – the hash method used on the message. Use ‘MD5’, ‘SHA-1’, ‘SHA-256’, ‘SHA-384’ or ‘SHA-512’.
i've tried little experiment to see if i get same output
so i've singed a simple text string in php
echo base64_encode(RSA::rsa_sign(sha1("test"),$private_key,$modulus,$key_length));
i got
something like
dKt+4CocMNdIrtYCUr8aZykR8CpfmYUEEVONMuAPlM5mR70AoyzMhGjcEGB9fKLVC4rr5xt66w2ZmHqWO+p834rJmo9Fj57udRSY5wFs0VokMF2S2SMFn5WTYYmMBuWciRzZybWnfXcSIyp9Ibi28cdwl5hXJOMpXEJrNQLFy2s=
next i extracted private_key , public_key , modulus from a xml file that they gave me with api containing my keys ( using the same RSA class ) like
$xmlObj = simplexml_load_string($xmlRsakey);
$this->modulus = RSA::binary_to_number(base64_decode($xmlObj->Modulus));
$this->public_key = RSA::binary_to_number(base64_decode($xmlObj->Exponent));
$this->private_key = RSA::binary_to_number(base64_decode($xmlObj->D));
$this->key_length = strlen(base64_decode($xmlObj->Modulus))*8;
i made a python dictionary with them
def keys():
obj = {
'modulus' : "14417185111734127374105962730273......." ,
'public_key' : "61111" ,
'private_key' : "3739752306322843055980611965983321761993....." ,
'key_length' : 1024 ,
}
return obj
and i've tried to sign a string in python
def sign(request):
api = keys()
message = 'test'
crypto = rsa.sign(message.encode('utf-8'), api['private_key'] , 'SHA-1')
b64 = base64.b64encode(crypto)
return HttpResponse(b64)
but i get :
'str' object has no attribute 'n'
and that was my failed experiment
As i said i dont have any experience with encryption or rsa .... i want some advice from someone who worked with this stuff .
Should i give up and use php to encrypt/decrypt ?
They use this class for RSA
https://github.com/AlaFalaki/Pclass/blob/master/libraries/rsa.class.php
My advice: Run away screaming.
RSA is a mine field of security issues. There are a lot of things that you can screw up. So when someone implements the primitives in PHP using the BC extension, that's the security equivalent of standing naked in front of a firing squad and expecting to have no holes.
Encrypting with PKCS1 padding allows near-trivial message decryption
Screwing up your parameters can completely remove all security from your crypto
Home-grown RSA is ripe with side-channel attacks
Recommendation: Use Libsodium Instead
There are both PHP and Python bindings available for libsodium.
If RSA is Unavoidable...
If you really want RSA and not modern cryptography, check out phpseclib.
<?php
use
$rsa = new RSA();
// HIGHLY RECOMMENDED FOR SECURITY:
$rsa->setEncryptionMode(RSA::ENCRYPTION_OAEP);
$rsa->setMGFHash('sha256');
$rsa->loadKey($yourPEMencodedRSAPublicKey);
$ciphertext = $rsa->encrypt($plaintext);
If you're going to encrypt with RSA, you must follow these cryptography rules. If your library doesn't let you follow these rules, it's time to switch to libsodium.
Also note that encrypting large messages with RSA is both slow and dangerous: There's usually nothing preventing messages from being reordered, which can be really bad.
The solution here is: Use symmetric-key authenticated encryption and use RSA to encrypt the public key. That's what EasyRSA does, although I'm not aware of any Python equivalents, so I can't recommend that as a solution.
Of course, if you use libsodium's crypto_box API you don't have to worry about that!

Categories