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

Does this work out-the-box with laravel? #130

Open JTD420 opened 1 year ago

JTD420 commented 1 year ago

I've tried requiring this with composer but have been having problems getting laravel to import the classes regardless of how I write the use statements!

I've made sure to include the classmap in the autoload section of the composer.json for the laravel app.

Any chance I'm missing something obvious? Would love to be able to integrate this into my application as it's the only PHP implementation of PGP that I can find that's installable through composer! Any help is much appreciated! :)

singpolyma commented 1 year ago

I have no experience with laravel, but this library is pretty standard PHP with nothing too fancy so I'm not sure why it wouldn't work. If you require the files does it give any errors?

ruaq commented 1 year ago

I’ve tested it (quickly) in Laravel and everything seems to work fine. I tried the examples/verify.php example in a Controller.

I used

use OpenPGP_Crypt_RSA;
use OpenPGP_Message;

to import the classes, like PHPStorm automatically suggested.

So what exactly isn’t working for you?

JTD420 commented 1 year ago

Hi, thanks for the replies! I'm not exactly sure what was causing the problem but it was giving me an error telling me the class could not be found when I tried to import it.

I've taken the code out of my application for the time being however I will try re-implementing it again at some point as ruaq has confirmed that he was able to get it working! I'll close this issue for now and re-open it if the problem persists once I've tried reimplementing it and have more information to provide.

Thanks again!

JTD420 commented 1 year ago

Hi,

So I got the package working now and successfully generating public and private keys which I'm able to import into GPA so it seems to be working however I'm still having a few problems:

For one, I cant seem to get gpa to decrypt anything using the password I set however when I tried doing it on an online website it seemed to be working.. Strange!

And secondly, I've been trying to follow the examples to get decrypt and encrypt messages working but I can't seem to get that to work! If I could get that then I can confirm everything is working as expected.

Any chance anyone could point me out in the right direction if I paste in the test code I've got so far? I am simply trying to do the following: 1, Generate a Public/Private Keypair. 2, Encrypt the Private Key with a passphrase. 3, Set the generated keypair to the correct bits for Certify, Sign and Encrypt 4, Generate an encrypted PGP message using the corresponding generated public key 5, Decrypt the encrypted PGP message using the corresponding generated private key and the passphrase that was set. 6, Finally print out all the variables for review to verify they are as expected (public key, private key, encrypted pgp msg, decrypted pgp msg)

Thanks in advance for any help!!!

public function test(){

        $privateKey = RSA::createKey(2048);
        $publickey = $privateKey->getPublicKey();
        $privateKeyComponents = PKCS1::load($privateKey->toString('PKCS1'));

        $nkey = new OpenPGP_SecretKeyPacket(array(
            'n' => $privateKeyComponents["modulus"]->toBytes(),
            'e' => $privateKeyComponents["publicExponent"]->toBytes(),
            'd' => $privateKeyComponents["privateExponent"]->toBytes(),
            'p' => $privateKeyComponents["primes"][1]->toBytes(),
            'q' => $privateKeyComponents["primes"][2]->toBytes(),
            'u' => $privateKeyComponents["coefficients"][2]->toBytes()
        ));

        $packets = array($nkey);
        $wkey = new OpenPGP_Crypt_RSA($nkey);
        $fingerprint = $wkey->key()->fingerprint;
        $key = $wkey->private_key();
        $keyid = substr($fingerprint, -16);

        $uid = new OpenPGP_UserIDPacket('Test <test@example.com>');
        $packets[] = $uid;
        $sig = new OpenPGP_SignaturePacket(new OpenPGP_Message(array($nkey, $uid)), 'RSA', 'SHA256');
        $sig->signature_type = 0x13;
        $sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_KeyFlagsPacket(array(0x01 | 0x02 | 0x04)); // Certify + sign + encrypt bits
        $sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_IssuerPacket($keyid);

        $m = $wkey->sign_key_userid(array($nkey, $uid, $sig));
        $m[0] = OpenPGP_Crypt_Symmetric::encryptSecretKey("password", $nkey);

        // Serialize public key message
        $pubm = clone($m);
        $pubm[0] = new OpenPGP_PublicKeyPacket($pubm[0]);
        $public_bytes = $pubm->to_bytes();

        $privateEnarmorKey = OpenPGP::enarmor($m->to_bytes(), "PGP PRIVATE KEY BLOCK");
        $publicEnarmorKey = OpenPGP::enarmor($public_bytes, "PGP PUBLIC KEY BLOCK");

        $key = OpenPGP_Message::parse(OpenPGP::unarmor($publicEnarmorKey, "PGP PUBLIC KEY BLOCK"));
        $data = new OpenPGP_LiteralDataPacket('This is text.', array('format' => 'u'));
        $encrypted = OpenPGP_Crypt_Symmetric::encrypt($key, new OpenPGP_Message(array($data)));
        echo "<br />";
        var_dump($encrypted);

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

    }

Also sorry if I should have created a new issue! I wasn't really sure which was better so opted to re-open this one rather than open a fresh issue...

JTD420 commented 1 year ago

UPDATE:

I think I've got it working now! Would appreciate it if someone could verify I've not gotten any parts of it wrong as I still have issues decrypting messages using the generated key inside of GPA on windows however the code itself does seem to be working as I'm now able to print out the decrypted message! 🙌

This is what I've got:

public function test(){
        $key_length = 2048;

        // Generate a key pair
        $privateKey = RSA::createKey($key_length);
        $privateKeyComponents = PKCS1::load($privateKey->toString('PKCS1'));

        $secretKeyPacket = new OpenPGP_SecretKeyPacket(array(
            'n' => $privateKeyComponents["modulus"]->toBytes(),
            'e' => $privateKeyComponents["publicExponent"]->toBytes(),
            'd' => $privateKeyComponents["privateExponent"]->toBytes(),
            'p' => $privateKeyComponents["primes"][1]->toBytes(),
            'q' => $privateKeyComponents["primes"][2]->toBytes(),
            'u' => $privateKeyComponents["coefficients"][2]->toBytes()
        ));

        // Assemble packets for the private key
        $packets = array($secretKeyPacket);

        $wkey = new OpenPGP_Crypt_RSA($secretKeyPacket);
        $fingerprint = $wkey->key()->fingerprint;
        $key = $wkey->private_key();
        $key = $key->withHash('sha256');
        $keyid = substr($fingerprint, -16);

        // Add a user ID packet
        $uid = new OpenPGP_UserIDPacket('Test <test@example.com>');
        $packets[] = $uid;

        // Add a signature packet to certify the binding between the user ID and the key
        $sig = new OpenPGP_SignaturePacket(new OpenPGP_Message(array($secretKeyPacket, $uid)), 'RSA', 'SHA256');
        $sig->signature_type = 0x13;
        $sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_KeyFlagsPacket(array(0x01 | 0x02 | 0x04)); // Certify + sign + encrypt bits
        $sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_IssuerPacket($keyid);
        $m = $wkey->sign_key_userid(array($secretKeyPacket, $uid, $sig));

        // Append the signature to the private key packets
        $packets[] = $m->packets[2];

        // Assemble packets for the public key
        $publicPackets = array(new OpenPGP_PublicKeyPacket($secretKeyPacket));
        $publicPackets[] = $uid;
        $publicPackets[] = $sig;

        // Encrypt the private key with a passphrase
        $encryptedSecretKeyPacket = OpenPGP_Crypt_Symmetric::encryptSecretKey("password", $secretKeyPacket);

        // Assemble the private key message
        $privateMessage = new OpenPGP_Message($packets);
        $privateMessage[0] = $encryptedSecretKeyPacket;

        // Enarmor the private key message
        $privateEnarmorKey = OpenPGP::enarmor($privateMessage->to_bytes(), "PGP PRIVATE KEY BLOCK");

        // Assemble the public key message
        $publicMessage = new OpenPGP_Message($publicPackets);

        // Enarmor the public key message
        $publicEnarmorKey = OpenPGP::enarmor($publicMessage->to_bytes(), "PGP PUBLIC KEY BLOCK");

        echo 'public' . $publicEnarmorKey . '<br />' . 'private' . $privateEnarmorKey . '<br />' ;

        // Replace 'public.asc' with $publicEnarmorKey
        $recipientPublicKey = OpenPGP_Message::parse(OpenPGP::unarmor($publicEnarmorKey, 'PGP PUBLIC KEY BLOCK'));

        // Replace 'sekret.asc' with $privateEnarmorKey
        $encryptedPrivateKey = OpenPGP_Message::parse(OpenPGP::unarmor($privateEnarmorKey, 'PGP PRIVATE KEY BLOCK'));

        // Set the passphrase to the one you specified when creating the private key
        $privateKeyPassphrase = 'password';

        // Decrypt the private key using the passphrase
        $key = OpenPGP_Crypt_Symmetric::decryptSecretKey($privateKeyPassphrase, $encryptedPrivateKey[0]);

        // Sign the data
        $signer = new OpenPGP_Crypt_RSA($key);
        $data = new OpenPGP_LiteralDataPacket("Testing Encryption", ['format' => 'u']);
        $signed = $signer->sign($data);

        // Compress the signed data
        $compressed = new OpenPGP_CompressedDataPacket($signed);

        // Encrypt the compressed data using the recipient's public key and your private key
        $encrypted = OpenPGP_Crypt_Symmetric::encrypt([$recipientPublicKey, $key], new OpenPGP_Message([$compressed]));

        // Enarmor the encrypted data
        $encryptedMessage = OpenPGP::enarmor($encrypted->to_bytes(), 'PGP MESSAGE');
        echo OpenPGP::enarmor($encrypted->to_bytes(), 'PGP MESSAGE');
        echo '<br />';

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

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

            $keyd = OpenPGP_Crypt_Symmetric::decryptSecretKey($privateKeyPassphrase, $p);

            $msg = OpenPGP_Message::parse(OpenPGP::unarmor($encryptedMessage, 'PGP MESSAGE'));

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

            $data_packet = $decrypted->packets[0]->data->packets[1];
            echo $data_packet->data;;
        }
}
JTD420 commented 1 year ago

Update:

I've integrated this into my project with success now however the issue with being unable to use these keys inside of GPA is still a thing and I'm not quite sure why.

This is the error message:

[GPA 0.10.0, GPGME 1.18.1-beta13, GnuPG 2.3.8]
gpg: encrypted with rsa2048 key, ID 60E3AAC764F4F559, created 2023-01-09
      "someUser <someUser@1.com>"
gpg: public key decryption failed: Wrong secret key used
gpg: decryption failed: Wrong secret key used

I am 100% sure I am using the correct secret key. The weird thing is I am able to decrypt the message seemingly with no issues using this package so I'm not sure where the discrepency is but I'm hoping someone will figure it out at some point! Till then, this code does what I need it too.

For now, I'll share in case anyone wants to build off what I've got so far and soon I'll submit a PR to add it as an example if no one can offer any improvements to it before then!

Using Laravel and putting everything inside of it's own controller:

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use OpenPGP;
use OpenPGP_CompressedDataPacket;
use OpenPGP_Crypt_RSA;
use OpenPGP_Crypt_Symmetric;
use OpenPGP_LiteralDataPacket;
use OpenPGP_Message;
use OpenPGP_PublicKeyPacket;
use OpenPGP_SecretKeyPacket;
use OpenPGP_SignaturePacket;
use OpenPGP_SignaturePacket_IssuerPacket;
use OpenPGP_SignaturePacket_KeyFlagsPacket;
use OpenPGP_UserIDPacket;
use phpseclib3\Crypt\RSA;
use phpseclib3\Crypt\RSA\Formats\Keys\PKCS1;

class PGPcontroller extends Controller
{
    function generate_keypair($name, $email, $passphrase) {
        $key_length = 2048;

        // Generate a key pair
        $privateKey = RSA::createKey($key_length);
        $privateKeyComponents = PKCS1::load($privateKey->toString('PKCS1'));

        $secretKeyPacket = new OpenPGP_SecretKeyPacket(array(
            'n' => $privateKeyComponents["modulus"]->toBytes(),
            'e' => $privateKeyComponents["publicExponent"]->toBytes(),
            'd' => $privateKeyComponents["privateExponent"]->toBytes(),
            'p' => $privateKeyComponents["primes"][1]->toBytes(),
            'q' => $privateKeyComponents["primes"][2]->toBytes(),
            'u' => $privateKeyComponents["coefficients"][2]->toBytes()
        ));

        // Assemble packets for the private key
        $packets = array($secretKeyPacket);

        $wkey = new OpenPGP_Crypt_RSA($secretKeyPacket);
        $fingerprint = $wkey->key()->fingerprint;
        $key = $wkey->private_key();
        $key = $key->withHash('sha256');
        $keyid = substr($fingerprint, -16);

        // Add a user ID packet
        $uid = new OpenPGP_UserIDPacket("$name <$email>");
        $packets[] = $uid;

        // Add a signature packet to certify the binding between the user ID and the key
        $sig = new OpenPGP_SignaturePacket(new OpenPGP_Message(array($secretKeyPacket, $uid)), 'RSA', 'SHA256');
        $sig->signature_type = 0x13;
        $sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_KeyFlagsPacket(array(0x01 | 0x02 | 0x04)); // Certify + sign + encrypt bits
        $sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_IssuerPacket($keyid);
        $m = $wkey->sign_key_userid(array($secretKeyPacket, $uid, $sig));

        // Append the signature to the private key packets
        $packets[] = $m->packets[2];

        // Assemble packets for the public key
        $publicPackets = array(new OpenPGP_PublicKeyPacket($secretKeyPacket));
        $publicPackets[] = $uid;
        $publicPackets[] = $sig;

        // Encrypt the private key with a passphrase
        $encryptedSecretKeyPacket = OpenPGP_Crypt_Symmetric::encryptSecretKey($passphrase, $secretKeyPacket);

        // Assemble the private key message
        $privateMessage = new OpenPGP_Message($packets);
        $privateMessage[0] = $encryptedSecretKeyPacket;

        // Enarmor the private key message
        $privateEnarmorKey = OpenPGP::enarmor($privateMessage->to_bytes(), "PGP PRIVATE KEY BLOCK");

        // Assemble the public key message
        $publicMessage = new OpenPGP_Message($publicPackets);

        // Enarmor the public key message
        $publicEnarmorKey = OpenPGP::enarmor($publicMessage->to_bytes(), "PGP PUBLIC KEY BLOCK");

        return array(
            'public_key' => $publicEnarmorKey,
            'private_key' => $privateEnarmorKey
        );
    }

    function encrypt($public_key, $message) {
        $recipientPublicKey = OpenPGP_Message::parse(OpenPGP::unarmor($public_key, 'PGP PUBLIC KEY BLOCK'));
        $data = new OpenPGP_LiteralDataPacket($message, ['format' => 'u']);
        $compressed = new OpenPGP_CompressedDataPacket($data);
        $encrypted = OpenPGP_Crypt_Symmetric::encrypt($recipientPublicKey, new OpenPGP_Message([$compressed]));
        return OpenPGP::enarmor($encrypted->to_bytes(), 'PGP MESSAGE');
    }

    function decrypt($private_key, $encrypted_message, $passphrase) {
        try {
            $encryptedPrivateKey = OpenPGP_Message::parse(OpenPGP::unarmor($private_key, 'PGP PRIVATE KEY BLOCK'));
            // Try each secret key packet
            foreach ($encryptedPrivateKey as $p) {
                if (!($p instanceof OpenPGP_SecretKeyPacket)) continue;
                $keyd = OpenPGP_Crypt_Symmetric::decryptSecretKey($passphrase, $p);
                $msg = OpenPGP_Message::parse(OpenPGP::unarmor($encrypted_message, 'PGP MESSAGE'));
                $decryptor = new OpenPGP_Crypt_RSA($keyd);
                $decrypted = $decryptor->decrypt($msg);
                $data_packet = $decrypted->packets[0]->data->packets[0];
                return $data_packet->data;
            }
        } catch (\Exception $e) {
            return response()->json(['error' => 'Unfortunately we were unable to decrypt your message at this time. Please verify you are using the correct password and try again. This attempt has been logged.'], 403);
        }
    }
}
valsha commented 1 year ago

@JTD420 any luck with OpenPGP and Laravel? Thanks.

JTD420 commented 1 year ago

@JTD420 any luck with OpenPGP and Laravel? Thanks.

Hi, yes! I'm actually working on a package for it that's using this. I expect to release the first version of it hopefully by this time next week! www.github.com/jtd420/laravel-pgp

valsha commented 1 year ago

awesome

@JTD420 any luck with OpenPGP and Laravel? Thanks.

Hi, yes! I'm actually working on a package for it that's using this. I expect to release the first version of it hopefully by this time next week! www.github.com/jtd420/laravel-pgp

Awesome, great job!