Decryption using Public Key in Python - php

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'
>>>

Related

Encryption method that works both in ABAP and PHP?

Is there is any encryption function that work in SAP and PHP, of course except using BASE64 encode-decode method.
The scenario is
ABAP encrypt string with key.
The string send to PHP.
PHP decrypt the string using key.
Thank you very much for any advise.
Base64 doesn't an encryption method. #Dirk shared a blog post, which has information about cl_sec_sxml_writer class.
If your system hasn't got this class you can use AES library. It has more capable than cl_sec_sxml_writer class. You can use ECB, CBC, PCBC, CFB, OFB, CTR encryption modes and None, PKCS #5, PKCS #7 padding standarts.
Another option is using SSF_KRN_ENVELOPE function for encrypt. It use RSA standart so result may be huge for ofen trasfers. It is using server certificate for encryption, you an check certificate in STRUST t-code. You check SSF01 demo program.

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.

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!

How to decrypt a symmetrically encrypted OpenPGP message using PHP?

I have an OpenPGP message which looks something like this given to me in a file:
-----BEGIN PGP MESSAGE-----
Version: GnuPG v1.4.9 (MingW32)
jA0EAgMCtCzaGHIQXY9g0sBnAeDOQ9GuVA/uICuP+7Z2dnjNCLgRN0J/TzJs1qcW
aJYBTkH5KQCClCxjwTYbHZCox1sENfIS+KxpCKJQqAX3SNEFm0ORNE6RNwEgb1Zj
uOdIw8auxUsjmQKFLAcZIPKjBjyJqSQVfmEoteVn1n+pwm8RdIZevCHwLF2URStB
nBVuycaxcaxcaxcxccxcxacqweqweqwe123fsMqQPaTusOBGpEQrWC9jArtvYEUpY
aNF6BfQ0y2CYrZrmzRoQnmtnVu10PagEuWmVxCucyhVwlthVgN0iBog9jhjliQkc
rrDTupqB4IimMEjElGUHtkuvrCQ0jQnOHEAJmmefMDH0NkYKGd5Ngt21I5ge5tob
/uBjHKMxjNgg1nWfg6Lz4jqoKe/EweuEeg==
=+N9N
-----END PGP MESSAGE-----
and was given a 15 character passphrase to decrypt it, I suppose. But I really don't have any idea to decrypt the file using PHP. I take a look at PHP's GnuPG manual page and under the gnugpg_decrypt() example it gives this code:
$res = gnupg_init();
gnupg_adddecryptkey($res,"8660281B6051D071D94B5B230549F9DC851566DC","test");
$plain = gnupg_decrypt($res,$encrypted_text);
echo $plain;
So taking a look at this function gnupg_adddecryptkey, it mentioned I need a fingerprint. What is that actually? And where can I get it?
The fingerprint is a hash sum calculated on the public key and some meta data like key creation time. It is also returned after importing a key through gnupg_import as fingerprint attribute.
This is for public/private key cryptography, which you're seemingly not using: when encrypting with a passphrase, you're omitting the public/private key cryptography part and directly use symmetric encryption for the message, with a session key (sometimes also called cipher block or symmetric key) derived from your passphrase.
Symmetric encryption is not supported by PHP's GnuPG module. There are no functions to perform symmetric decryption, and this limitation is also described in the module's source documentation:
This class provides an object oriented interface to GNU Privacy Guard (GPG).
Though GPG can support symmetric-key cryptography, this class is intended only to facilitate public-key cryptography.
You will have to perform decryption manually by calling gpg. An example command line would be
gpg --symmetric --decrypt [file]
(alternatively, you can also provide the input through STDIN). For handing over the passphrase, have a look at GnuPG's --passphrase... options:
--passphrase-fd n
Read the passphrase from file descriptor n. Only the first line will be read from file descriptor n. If you use 0 for n, the passphrase will be read from STDIN. This can
only be used if only one passphrase is supplied.
--passphrase-file file
Read the passphrase from file file. Only the first line will be read from file file. This can only be used if only one passphrase is supplied. Obviously, a passphrase
stored in a file is of questionable security if other users can read this file. Don't use this option if you can avoid it.
--passphrase string
Use string as the passphrase. This can only be used if only one passphrase is supplied. Obviously, this is of very questionable security on a multi-user system. Don't use
this option if you can avoid it.
Be aware that all other users of a computer can read all other user's command line arguments, so especially for shared hosting platforms, --passphrase is a definite no-go.
This answer is compatible with not just PHP, but GnuGPG in general. To summarize Jens Erat's answer and adding the encryption step for anyone else who comes across this question, here's a solution, assuming a file exists called passwords.txt:
// encrypt
gpg --output passwords.gpg --symmetric passwords.txt
// decrypt
gpg —decrypt passwords.gpg

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

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.

Categories