singpolyma / openpgp-php

OpenPGP.php is a pure-PHP implementation of the OpenPGP Message Format (RFC 4880).
http://singpolyma.github.io/openpgp-php/
The Unlicense
179 stars 69 forks source link

Generated messages by openpgp-php are intermittently undecryptable by openpgp-php #137

Open paulie51 opened 2 months ago

paulie51 commented 2 months ago

Hi, We have trying to migrate from php-gnupg to openpgp-php with an existing application (and therefore keys).

We were seeing inconsistent results in testing and eventually when we couldn't find a cause took to running bulk loops of message encryption and decryption to see if they were stable.... This highlighted the inconsistent issues we were seeing, depending on various factors (message length, potentially key source, key size) between in 1 in 30 and 1 in 5 messages generated using the below code would result in this error :

PHP Fatal error:  Uncaught OutOfRangeException: Ciphertext representative out of range in openpgp-php/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/PrivateKey.php:68
Stack trace:
#0 openpgp-php/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/PrivateKey.php(320): phpseclib3\Crypt\RSA\PrivateKey->rsadp(Object(phpseclib3\Math\BigInteger))
#1 openpgp-php/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/PrivateKey.php(441): phpseclib3\Crypt\RSA\PrivateKey->rsaes_pkcs1_v1_5_decrypt(Object(phpseclib3\Math\BigInteger))
#2 openpgp-php/lib/openpgp_crypt_rsa.php(218): phpseclib3\Crypt\RSA\PrivateKey->decrypt('\xBB\x9C\xD4zt\e\xB3\x90V/\xF64\x84\xFF3...')
#3 openpgp-php/lib/openpgp_crypt_rsa.php(200): OpenPGP_Crypt_RSA::try_decrypt_session(Object(phpseclib3\Crypt\RSA\PrivateKey), '\xBB\x9C\xD4zt\e\xB3\x90V/\xF64\x84\xFF3...')
#4 openpgp-php/examples/deASCIIdeCryptCompressed.php(39): OpenPGP_Crypt_RSA->decrypt(Object(OpenPGP_Message))
#5 {main} thrown in openpgp-php/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/PrivateKey.php on line 68`

This is tested with multiple different keys generated from different sources and with different key sizes, against mainly PHP 8.1 but also 8.0 and 8.2. We've also tested against different architectures and different builds of PHP.

We've also tested this without using compression and with multiple message sizes (generally the larger the message the more frequently this occurs). We've also tested it with and without generating the ASCII message and decrypting from there. Nothing we've found has prevented a message being generated intermittently that openpgp-php then cannot decipher.

The script being used :

<?php

// USAGE: php examples/deASCIIdeCrypt.php secretkey.asc password message.asc
// Derived from examples/deASCIIdeCrypt.php and examples/encryptdecrypt.php

@include_once dirname(__FILE__).'/../vendor/autoload.php';
require_once dirname(__FILE__).'/../lib/openpgp.php';
require_once dirname(__FILE__).'/../lib/openpgp_crypt_rsa.php';
require_once dirname(__FILE__).'/../lib/openpgp_crypt_symmetric.php';

$keyASCII = file_get_contents("generated.private.asc");
$pubkeyASCII = file_get_contents("generated.public.asc");
$pass = "testing";

$message = "some random message";

$publicKey = OpenPGP_Message::parse(OpenPGP::unarmor($pubkeyASCII, 'PGP PUBLIC KEY BLOCK'));

$data = new OpenPGP_LiteralDataPacket($message, array('format' => 'u'));

$compressed = new OpenPGP_CompressedDataPacket($data);
$encrypted = OpenPGP_Crypt_Symmetric::encrypt($publicKey, new OpenPGP_Message([$compressed]));

//$encrypted = OpenPGP_Crypt_Symmetric::encrypt($publicKey, new OpenPGP_Message(array($data)));
//$encryptedmessage = OpenPGP::enarmor($encrypted->to_bytes(), 'PGP MESSAGE');

$keyEncrypted = OpenPGP_Message::parse(OpenPGP::unarmor($keyASCII , 'PGP PRIVATE KEY BLOCK'));

// Try each secret key packet
foreach($keyEncrypted as $p) {
        //if(!($p instanceof OpenPGP_SecretKeyPacket)) continue;
  if(!(get_class($p) == 'OpenPGP_SecretKeyPacket')) continue;

  $key = OpenPGP_Crypt_Symmetric::decryptSecretKey($pass, $p);

        //$msg = OpenPGP_Message::parse(OpenPGP::unarmor($encryptedmessage, 'PGP MESSAGE'));
  $msg = $encrypted;

        $decryptor = new OpenPGP_Crypt_RSA($key);
        $decrypted = $decryptor->decrypt($msg);

        //var_dump($decrypted);
}

We then called it in a loop repeatedly breaking out when the error above occurs, changing various parameters.

We've been unable to find a cause for this inconsistent behaviour, and its probably worth noting that the generated messages we have tested have been decryptable by other means (ie gpg on MacOS), so unless you were openpgp-php -> openpgp-php this may not manifest.

singpolyma commented 3 weeks ago

Can you send a specific example of a key and message that could not decrypt with this library but did work with another tool?