0

I got a PHP application that has to encrypt a string with a private key. It's a bit uncommon to encrypt with the private key, I know, but that's the way it is.

The application is PHP5 using a very old version of phpseclib. Now, we have to migrate to PHP8 and need to upgrade phpseclib. I was google-ling the whole day but I was not able to figure out how to migrate the legacy code. The legacy code looks like this:

define('RSA_PRIV_KEY', "<base64 encoded key>");

$rsa = new Crypt_RSA();
$rsa->loadKey(RSA_PRIV_KEY);
$result = base64_encode($rsa->encrypt("my fancy string"));

I had a look to phpseclib3 but was not able to get it to work at all. Same for openssl.

Edit best attempt made with phpseclib3

// this works
$key = PublicKeyLoader::loadPrivateKey(RSA_PRIV_KEY);
// this works although my IDE claims that no method named getLoadedFormat() exists. It gives me 'PKCS1'
$this->log->info(">>>" . $key->getLoadedFormat());
// This does not work cause 'Call to undefined method phpseclib3\Crypt\RSA\PrivateKey::encrypt()'
$this->log->info(">>>" . $key->encrypt("my fancy string"));
1
  • Please edit your question to include the attempts in converting your code and the errors/problems you get with these attempts. Commented Mar 10 at 18:46

2 Answers 2

0

The following worked for me:

define('RSA_PRIV_KEY', "<base64 encoded key>");
$privkey = openssl_pkey_get_private(RSA_PRIV_KEY);

$encrypted = ""; 
openssl_private_encrypt("my fancy string", $encrypted, $privkey);

$this->log->info(">>>" . base64_encode($encrypted)); 
Sign up to request clarification or add additional context in comments.

1 Comment

This may be a possible solution for you, but this code is not compatible with the phpseclib/v1 code from the question, i.e. the decryption code matching the phpseclib/v1 code cannot decrypt the ciphertexts of the PHP/OpenSSL code, see my answer for more details.
0

The phpseclib/v1 code encrypts with OAEP, using the private exponent for encryption and the public exponent for decryption.

In phpseclib/v3, however, this is no longer possible, as the private key does not expose an encrypt()-function and the public key no decrypt()-function.
Nevertheless, the functionality can be ported to v3 by swapping the public and private exponents in the keys and then encrypting with the public key and decrypting with the private key as usual.

Example: Decryption of a ciphertext in v3, which was generated with the posted v1 code:

$privateKeyPem = "-----BEGIN PRIVATE KEY-----
MIIEvg...
-----END PRIVATE KEY-----";

$privateKeySwapped = null;
$publicKeySwapped = null;
swapKeys($privateKeyPem, $privateKeySwapped, $publicKeySwapped);

$ciphertext = base64_decode('<Base64 encoded ciphertext generated with the posted v1 code>');
$decrypted = $privateKeySwapped->withHash('sha1')->withMGFHash('sha1')->decrypt($ciphertext);
print($decrypted . PHP_EOL); // my fancy string

with:

function swapKeys($privateKeyPem, &$privateKeySwapped, &$publicKeySwapped) {
    $privateKey = PublicKeyLoader::load($privateKeyPem);
    $privateKeyParams = unserialize($privateKey->toString('Raw'));
    $privateKeyParamsSwapped = [
        'e' => $privateKeyParams['d'],
        'n' => $privateKeyParams['n'],
        'd' => $privateKeyParams['e'],
        'p' => $privateKeyParams['primes'][1],
        'q' => $privateKeyParams['primes'][2]
    ];
    $privateKeySwapped = PublicKeyLoader::loadPrivateKey($privateKeyParamsSwapped);
    
    $publicKeyParamsSwapped = [
        'e' => $privateKeyParams['d'],
        'n' => $privateKeyParams['n']
    ];
    $publicKeySwapped = PublicKeyLoader::loadPublicKey($publicKeyParamsSwapped);
}

Here it must be taken into account that v1 uses SHA-1 by default for the OAEP- and MGF1-digest, while v3 applies SHA-256 by default, so that in v3 both digests must be explicitly set to SHA-1.


Note that the process "encryption with the private key" is, strictly speaking, not defined at all (but only (1) "encryption with the public key"/"decryption with the private key" and (2) "signing with the private key"/"verification with the public key").

Often the paraphrase "encryption with the private key" refers to signing, which may also be the case here. And accordingly, many libraries (if they support such a process at all) sign under the hood (and use the padding for signing), and do not encrypt, unlike phpseclib/v1, which, as described above, really encrypts (with OAEP and swapped exponents)!


Also note that the PHP/OpenSSL code posted in your answer generates a signature with PKCS#1 v1.5 padding, where the (unhashed) message is signed directly (and not the DER encoding of the DigestInfo value as it actually should be to comply with the specification in RFC 8017).
It may even be what you need, but it is not compatible with the logic implemented by the phpseclib/v1 code posted in the question.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.