2

We migrated to PHPSecLib a while ago, but did not migrate our legacy data that was encrypted by the old PEAR\Crypt_RSA library. We've reached a point where we need to migrate that data into PHPSecLib's RSA format. While investigating this, I came across this old forum thread. I attempted to apply the suggestion in the response, but could not get it to successfully decrypt our data. It's not erroring out or anything, it just appears to still be encrypted or encoded. We're running PHPSecLib 2.0.6 currently, and I suspect the instructions were for 1.x.

Here's a roughed out version of my adapted decryption flow (based off the forum thread):

$rsaDecryptor = new RSA();

// The Private Key is encrypted based off a password
$mc      = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');
$iv      = mcrypt_create_iv(mcrypt_enc_get_iv_size($mc), MCRYPT_DEV_URANDOM);
$keySize = mcrypt_enc_get_key_size($mc);
$key     = substr($rsaDecryptor->password, 0, $keySize);

mcrypt_generic_init($mc, $key, $iv);
$privateKey = mdecrypt_generic($mc, base64_decode($privateKey));
mcrypt_generic_deinit($mc);
mcrypt_module_close($mc);

list($privateKeyModulus, $privateKeyExponent) = unserialize(base64_decode($privateKey));

$privateKeyExponent = new BigInteger(strrev($privateKeyExponent), 256);
$privateKeyModulus = new BigInteger(strrev($privateKeyModulus), 256);

$rsaDecryptor->modulus        = $privateKeyModulus;
$rsaDecryptor->exponent       = $privateKeyExponent;
$rsaDecryptor->publicExponent = $privateKeyExponent;
$rsaDecryptor->k              = strlen($this->decRSA->modulus->toBytes());

// ciphertext is the raw encrypted string created by PEAR\Crypt_RSA
$value = base64_decode($ciphertext);
$value = new BigInteger($value, 256);
$value = $rsaDecryptor->_exponentiate($value)->toBytes();
$value = substr($value, 1);
bgrahamfs
  • 23
  • 3
  • "...it just appears to still be encrypted or encoded..." Without showing your code this question should be closed as off-topic. Questions seeking debugging help ("why isn't this code working?") must include the desired behavior, a specific problem or error and the shortest code necessary to reproduce it in the question itself. Questions without a clear problem statement are not useful to other readers. See: [How to create a Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve) – President James K. Polk Nov 10 '17 at 21:51
  • 1
    @JamesKPolk, thanks for the heads up. I'd argue this is less of a debugging question than it is a "Is this thing actually possible?" question, but that being said, I've provided a rough example of the code flow. I've also modified the title of the post to try to match my intent. – bgrahamfs Nov 10 '17 at 22:08

1 Answers1

1

Bugs In PEAR's Crypt_RSA

So I was playing around with this. There's a bug in PEAR's Crypt_RSA (latest version) that might prevent this from working at all. The following code demonstrates:

$key_pair = new Crypt_RSA_KeyPair(1024);
$privkey = $key_pair->getPrivateKey();
$pubkey = $key_pair->getPublicKey();
$a = $privkey->toString();
$b = $pubkey->toString();

echo $a == $b ? 'same' : 'different';

You'd expect $a and $b to be different, wouldn't you? Well they're not. This is because RSA/KeyPair.php does this:

$this->_public_key = &$obj;
...
$this->_private_key = &$obj;

If you remove the ampersands it works correctly but they're in the code by default.

It looks like this is an unresolved issue as of https://pear.php.net/bugs/bug.php?id=15900

Maybe it behaves differently on PHP4 but I have no idea.

Decrypting Data

Assuming the above bug isn't an issue for you then the following worked for me (using phpseclib 2.0):

function loadKey($key) // for keys genereated with $key->toString() vs $key->toPEMString()
{
    if (!($key = base64_decode($key))) {
        return false;
    }
    if (!($key = unserialize($key))) {
        return false;
    }
    list($modulus, $exponent) = $key;
    $modulus = new BigInteger(strrev($modulus), 256);
    $exponent = new BigInteger(strrev($exponent), 256);
    $rsa = new RSA();
    $rsa->loadKey(compact('modulus', 'exponent'));
    return $rsa;
}

function decrypt($key, $ciphertext)
{
    if (!($ciphertext = base64_decode($ciphertext))) {
        return false;
    }
    $key->setEncryptionMode(RSA::ENCRYPTION_NONE);
    $ciphertext = strrev($ciphertext);
    $plaintext = $key->decrypt($ciphertext);
    $plaintext = strrev($plaintext);
    $plaintext = substr($plaintext, 0, strpos($plaintext, "\0"));
    return $plaintext[strlen($plaintext) - 1] == "\1" ?
        substr($plaintext, 0, -1) : false;
}

$key = loadKey($private_key);
$plaintext = decrypt($key, $ciphertext);
echo $plaintext;

Private Keys Generated with toPEMString()

With PEAR's Crypt_RSA you can generate private keys an alternative way:

$key_pair->toPEMString();

This method works without code changes. If you used this approach to generate your private keys the private key starts off with -----BEGIN RSA PRIVATE KEY-----. If this is the case then you don't need to use the loadKey function I wrote. You can do this instead:

$key = new RSA();
$key->loadKey('...');
$plaintext = decrypt($key, $ciphertext);
echo $plaintext;
neubert
  • 15,947
  • 24
  • 120
  • 212
  • 1
    @bgrahamfs - `$key->loadKey('...')` basically tries every supported key format until it finds one that loads the key. One of the supported key formats is `RSA::PUBLIC_FORMAT_RAW` which accepts arrays. Its the only key format that accepts arrays as input: https://github.com/phpseclib/phpseclib/blob/2.0.7/phpseclib/Crypt/RSA.php#L1030 – neubert Nov 13 '17 at 16:32
  • 2
    Thanks for the clarification and the great code example. I've got rough proofs of concept working on both encrypt and decrypt now. – bgrahamfs Nov 13 '17 at 17:16