1

Is anyone knows a way to do PKCS 1.5 RSA encryption using a public key in PHP 7 ?. Every PHP library that I tried to that, looks like replaced that encryption padding with OAEP padding because PKCS 1.5 encryption is not secure anymore. (Phpseclib equivalent to Java RSA Encryption) . Given below is PHP code I tried so far. What I am doing wrong here ?. Is there any way to accomplish this task in PHP?. I am using CodeIgniter 3 PHP framework.

<?php
     defined('BASEPATH') or exit('No direct script access allowed');

     use phpseclib3\Crypt\PublicKeyLoader;
     use phpseclib3\Crypt\RSA;

     class Welcome extends CI_Controller
     {

         /**
          * Index Page for this controller.
          *
          * Maps to the following URL
          * http://example.com/index.php/welcome
          * - or -
          * http://example.com/index.php/welcome/index
          * - or -
          * Since this controller is set as the default controller in
          * config/routes.php, it's displayed at http://example.com/
          *
          * So any other public methods not prefixed with an underscore will
          * map to /index.php/welcome/<method_name>
          * @see https://codeigniter.com/user_guide/general/urls.html
          */

         
         public function index()
         {
             $key = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzIrJng2dW9xPratyAE0nDm5qrnYxyw2lOVVtBgS7C01Aufw/+RDUnneZuHvYB0rU6LExdANvMzDvqNxVQeQNwd5Frrgtx1GV5yZaKuDMqSa6TtFfW/loaKHiLJyIKJTiig4zqjHi0mYI2+Z2Z4wDXx1J8R+Pv+poFShK8vj7Tgx5LwgE/cK7Iq/coTXWEQJrzEbbstBJIq5or5oWBhK0XqB0L3THZZDp3U2b3siIWBniRTU4hquKrwu2/JTmrTYfOAFdR8FRj3oJcFVaexsbhwpiA8RFoY043fhYKBzDz4NK8tFegYn3JIxq+7XReJJQjSKW8/LAxHAypG/aj3C8QIDAQAB';
             $publicKey = "-----BEGIN PUBLIC KEY-----\n" . wordwrap($key, 64, "\n", true) . "\n-----END PUBLIC KEY-----";

             $timestamp = new DateTime();
             $timestamp = $timestamp->getTimestamp();
             $tk = <token>;

             $text = $timestamp . '+' . $tk;

            /** Method 1 - unsuccessfull */
            $key = PublicKeyLoader::load($publicKey);
            $key = $key->withPadding(RSA::PADDING_PKCS15_COMPAT);

            $encryptedString = base64_encode($key->encrypt($text));

            /** Method 2 - unsuccessfull */
            // $encryptedString = '';
            // openssl_public_encrypt(utf8_encode($text),$encryptedString,$publicKey);
           // $encryptedString = base64_encode($encryptedString);

           /** Method 3 */
           // $rsa = new Crypt_RSA();
           // $rsa->loadKey($publicKey); // public key

           // $rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
           // $encryptedString = $rsa->encrypt($text);

           $encryptedString = utf8_encode($encryptedString);
           $encryptedString = urlencode($encryptedString);
           echo $encryptedString;
           echo '<br/>';
           echo '<a href="https://adstream.daraz.lk/api/v1/marketing/download_feeds?token=' . $encryptedString . '">Click</a>';
    }
}
?>

This is the code given by daraz to generate token in python and it works.

import sys, time
import urllib.parse
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5 as Cipher_PKCS1_v1_5
from base64 import b64decode,b64encode

pubkey = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzIrJng2dW9xPratyAE0nDm5qrnYxyw2lOVVt
BgS7C01Aufw/+RDUnneZuHvYB0rU6LExdANvMzDvqNxVQeQNwd5Frrgtx1GV5yZaKuDMqSa6TtFfW/l
oaKHiLJyIKJTiig4zqjHi0mYI2+Z2Z4wDXx1J8R+Pv+poFShK8vj7Tgx5LwgE/cK7Iq/coTXWEQJrzEbbstBJIq5o
r5oWBhK0XqB0L3THZZDp3U2b3siIWBniRTU4hquKrwu2/JTmrTYfOAFdR8FRj3oJcFVaexsbhwpiA8RFoY
043fhYKBzDz4NK8tFegYn3JIxq+7XReJJQjSKW8/LAxHAypG/aj3C8QIDAQAB
-----END PUBLIC KEY-----"""
timestamp = int(time.time()*1000);
tk = < token >;
msg = str(timestamp) + '+' + tk;
keyPub = RSA.importKey(pubkey);
cipher = Cipher_PKCS1_v1_5.new(keyPub);
cipher_text = cipher.encrypt(msg.encode());
emsg = b64encode(cipher_text);
token = str(emsg,'utf-8');
urlencoded_token = urllib.parse.quote_plus(token);
print(token);
print();
print(urlencoded_token);

This is how I done it using jsEncrypt Js library and it was also ended in a success

<script>
    let timestamp = Date.now();
    let accountToken = < token >;
    let publicKey = `"""-----BEGIN PUBLIC KEY-----
                                     MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzIrJng2dW9xPratyAE0nDm5qrnYxyw2lOVVt
                                     BgS7C01Aufw/+RDUnneZuHvYB0rU6LExdANvMzDvqNxVQeQNwd5Frrgtx1GV5yZaKuDMqSa6TtFfW/l
                                     oaKHiLJyIKJTiig4zqjHi0mYI2+Z2Z4wDXx1J8R+Pv+poFShK8vj7Tgx5LwgE/cK7Iq/coTXWEQJrzEbbstBJIq5o
                                     r5oWBhK0XqB0L3THZZDp3U2b3siIWBniRTU4hquKrwu2/JTmrTYfOAFdR8FRj3oJcFVaexsbhwpiA8RFoY
                                     043fhYKBzDz4NK8tFegYn3JIxq+7XReJJQjSKW8/LAxHAypG/aj3C8QIDAQAB
                                     -----END PUBLIC KEY-----"""`;
    let stringToEncrypt = timestamp + "+" + accountToken;
    // let token = RSA_ENCRYPT(stringToEncrypt,
    //     secret);

    // Encrypt with the public key...
    let encrypt = new JSEncrypt();
    encrypt.setPublicKey(publicKey);
    let token = encrypt.encrypt(stringToEncrypt, 'RSAES-PKCS1-V1_5');
    token = encodeURIComponent(token);
    // console.log('https://adstream.daraz.lk/api/v1/marketing/download_feeds?token=' + token);
    window.location.replace('https://adstream.daraz.lk/api/v1/marketing/download_feeds?token=' + token);
</script>

But still I'm finding a way to accomplish same in PHP. All the ways I tried in PHP so far ended in a failure because the daraz API says encrypted token is invalid. Any help please ?

  • 1
    OpenSSL is been able to do the job: "openssl_public_encrypt($plaintext, $ciphertext, $publicKey, OPENSSL_PKCS1_PADDING);". See a full running example with code here (disclaimer: I'm the author): https://replit.com/@javacrypto/CpcPhpRsaEncryptionPkcs15String#main.php/ – Michael Fehr Aug 06 '21 at 06:30
  • You just chose the wrong padding, apply `RSA::ENCRYPTION_PKCS1`, i.e. `$key = $key->withPadding(RSA::ENCRYPTION_PKCS1)` then your code works (at least on my machine ;-). – Topaco Aug 06 '21 at 08:37
  • @Michael Fehr Well. I think that is the default padding in "openssl_public_encrypt" function. I tried out your change as well. But the token generated using that cannot use in daraz API because it says invalid request. I don't know whether I am grabbing a wrong corner in this problem. But huge thanks for the help and requesting help from everyone ( including you as well ) also. :-) :-) . In this scenario I didn't encountered any PHP error. But I wasn't able to do that in PHP, but did that in python and JS as well. Updating the question for more information now. – Ravindu Dharmasena Aug 06 '21 at 08:41
  • @Topaco, I tried it as well and I'm also suspecting the padding there, but still daraz API rejects it. I'm updating the question for more information now. – Ravindu Dharmasena Aug 06 '21 at 08:44
  • The encryption itself is correct. I checked the ciphertext with [this page](https://www.devglan.com/online-tools/rsa-encryption-decryption). You can also check it yourself with a valid key pair. Probably there is a compatibility problem with this daraz API (not related keys, incompatible padding, wrong data format, etc.). – Topaco Aug 06 '21 at 08:53
  • @Topaco I think most of the libraries don't support PKCS 1.5 encryption now due to the security vulnerabilities it has. I didn't checked the compatibility problems and thanks for pointing it out as well :-) – Ravindu Dharmasena Aug 06 '21 at 12:51
  • You' re welcome. But your guess is not correct, most libraries nowadays still supports PKCS#1 v1.5 (and of course the more modern OAEP), e.g. phpseclib and openssl/PHP. – Topaco Aug 06 '21 at 13:00
  • I voted to reopen the post, since imo all the information needed to answer is available: the code in question and two working codes for comparison. – Topaco Aug 06 '21 at 14:42
  • All the posted codes (the _fixed_ PHP, Python, JSEncrypt code) use PKCS#1 v1.5 padding and encrypt _correctly_, as can be proven using a valid key pair and a second (independent) application for decryption. Have you considered that the bug may not be the encryption itself? E.g. the Python and JSEncrypt code give the timestamp in _milliseconds_, while the PHP code gives it in _seconds_! If the timestamp is validated, this could also result in an invalid token. – Topaco Aug 06 '21 at 14:45
  • @Topaco THANK YOU SO MUCH !!!!!!! :-):-). for pointing out the timestamp issue. It was in seconds, not in milliseconds. After fixing that the PHP code is working with the phpseclib and openssl_public_encrypt(utf8_encode($text),$encryptedString,$publicKey,OPENSSL_PKCS1_PADDING); method as well. Can you do a good explanation with the mistakes you have identified in code such as the timestamp issue for this the question if the question is reopened. You have the answer. I will definitely vote for that. Thanks pal :-) – Ravindu Dharmasena Aug 06 '21 at 16:18
  • @MichaelFehr openssl_public_encrypt($plaintext, $ciphertext, $publicKey, OPENSSL_PKCS1_PADDING) working fine. The problem was I wasn't converted the timestamp in PHP to millisecond as "@Topaco" pointed out. Thanks for your assistance. :-) – Ravindu Dharmasena Aug 06 '21 at 16:20
  • @RavinduDharmasena - My pleasure. If the post should be reopened, I'll post an answer. – Topaco Aug 06 '21 at 18:54
  • @Topaco: the post is reopened now – Michael Fehr Aug 07 '21 at 07:48

1 Answers1

2

The posted Python (PyCryptodome) and JavaScript (JSEncrypt) reference codes do the following:

  • Generation of a timestamp (number of milliseconds elapsed since January 1, 1970 00:00:00 UTC).
  • Concatenation of timestamp and token in the format <timestamp>+<token>.
  • Encryption with RSA and PKCS#1 v1.5 padding (RSAES-PKCS1-v1_5)
  • Base64 encoding and subsequent URL encoding

The posted PHP code uses different libraries for encryption: phpseclib (V3 and V1) and OpenSSL. The only difference from the reference code turned out to be that the timestamp is determined in seconds (instead of milliseconds), which was eventually identified as the cause of the problem.

The rest is consistent with the reference code, including the encryption with RSA and PKCS#1 v1.5 padding originally suspected as the cause of the bug, as could be easily verified by using a valid key pair and decrypting the ciphertext generated with the PHP code with a second independent application (e.g. online).

Note that phpseclib V3 specifies PKCS#1 v1.5 padding in the context of encryption with RSA::ENCRYPTION_PKCS1.

Topaco
  • 40,594
  • 4
  • 35
  • 62