I want to use PGP encryption to encrypt a CSV files, I am generating through a PHP script and then send that file to client via email. Client will give me the encryption key, which I need to use for encryption files.
I Googled about PGP and found it is Pretty Good Privacy, also I found OpenPGP http://www.openpgp.org/ and GnuPG http://www.gnupg.org/ What are these two types of PGP? and which one should I use?
Also how to encrypt a files using PGP in PHP with the key that my client will provide?
I have heard this term first time, can anyone please help in understanding this and implementing this in PHP.
Question 1: About PGP
PGP (Pretty Good Privacy) is a product and trademark of Symantec Corporation (they bought it some years ago).
OpenPGP is the standard used by PGP.
GnuPG (Gnu Privacy Guard) is a free and open source implementation of PGP.
So what you want to do is encrypt to an OpenPGP key. Which implementation of OpenPGP your client uses to decrypt the data is not important for you. With PHP, commonly GnuPG is used and there are interfaces built-in.
Question 2: Using GnuPG in PHP
Use the GnuPG interface, which is an extension that can be installed for PHP.
At first, import the key, where $keydata is the ASCII armored public key:
<?php
$gpg = new gnupg();
$info = $gpg -> import($keydata);
print_r($info);
?>
Then use this key to encrypt the data, this time using the client's key's fingerprint:
<?php
$gpg = new gnupg();
$gpg -> addencryptkey("8660281B6051D071D94B5B230549F9DC851566DC");
$enc = $gpg -> encrypt("just a test");
echo $enc;
?>
If you want to encrypt files, read and pass them to encrypt(). Be sure to use at least long key IDs (eg. DEADBEEFDEADBEEF), better fingerprints (as in the example) when referencing keys; and never use short key IDs (DEADBEEF), as those are vulnerable to collision attacks.
The is a more comprehensive example for doing both added by a user in the PHP manual.
Going to leave an answer here as many examples across the net for PHP GnuPG are very bare bones and hopefully this saves someone some frustration.
Basically, it mirrors how the GnuPG command line tool works. You need to import a key if it's not already in gpg's key ring then you need to select the recipient's key to use for encryption/decryption.
gpg --import recipients-public-key.asc
gpg -r recipient --encrypt test.txt
If you did what I did and passed in the key as the recipient it doesn't work!
It's not clear what this field is in either the GPG manual or PHP documentation which refers to this field as "fingerprint". Check gpg's key ring for your freshly imported key with:
gpg --list-keys
This will output something like this:
pub rsa2048 2019-04-14 [SC] [expires: 2021-04-14]
0DAA2C747B1974BE9EB9E6DCF7EE249AD00A46AA
uid [ultimate] Dean Or
sub rsa2048 2019-04-14 [E] [expires: 2021-04-14]
This will give you the UID and on the second line the fingerprint associated with every key. As far as I can tell you can use the UID and fingerprint as the recipient.
So your PHP code to encrypt might look like this:
// Encrypt
$gpg = new gnupg();
$gpg->seterrormode(gnupg::ERROR_EXCEPTION);
// Check key ring for recipient public key, otherwise import it
$keyInfo = $gpg->keyinfo('0DAA2C747B1974BE9EB9E6DCF7EE249AD00A46AA');
if (empty($keyInfo)) {
$gpg->import('recipients-public-key.asc');
}
$gpg->addencryptkey('0DAA2C747B1974BE9EB9E6DCF7EE249AD00A46AA');
echo $gpg->encrypt('This is a test!');
Then the recipient's code will look like this:
// Decrypt
$gpg = new gnupg();
$gpg->seterrormode(gnupg::ERROR_EXCEPTION);
// Check key ring for recipient private key, otherwise import it
$keyInfo = $gpg->keyinfo('0DAA2C747B1974BE9EB9E6DCF7EE249AD00A46AA');
if (empty($keyInfo)) {
$gpg->import('recipients-private-key.asc');
}
$gpg->adddecryptkey('0DAA2C747B1974BE9EB9E6DCF7EE249AD00A46AA', '');
echo $gpg->decrypt($encyptedMessage);
Note the fingerprints are the same for both the recipient's public and private key.
There is also a known issue with adddecryptkey not taking a passphrase! You either need to remove the passphrase or change your version of GnuPG.
Related
I have a NodeJS application generating JSON Web Tokens with the PS256 algorithm. I want to try and verify the signatures in these tokens in a PHP application.
So far I've got the following:
My JWT:
eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwMTBiYjllYS00YTg0LTQ1ZTMtOTg5My0wYzNhNDYxZmQzMGUiLCJpYXQiOjE2MDU4OTI5NjcsImV4cCI6MTYwNjQ5Nzc2NywiYXVkIjoiNzBiYzcxMTQ1MWM2NDBjOTVlZjgzYjdhNDliMWE0MWIiLCJpc3MiOiIyM2ZhYTRiNC0wNmVlLTRlNGEtYTVjZC05NjJmOTRhMjEzYmYiLCJqdGkiOiI1MTNiYjczZC0zOTY3LTQxYzUtODMwOS00Yjc1ZDI4ZGU3NTIifQ.kLtaSYKyhqzx7Dc7UIz7tqU8TsXabRLxGiaqw21lgCcuf_eBvpiLkFOuXpUs-V8XQunQg8jV-bKlKUIb0pLvipjhRP50IwKDClQgNtIwn4yyX5RyDNGJur0qHNnkHMLaF11NsXGPyhvh-6ogSZjWgyZnkQJkXpz4jggBetwqz1hnicapGfNb6C-UdRcOLyCaiMD4OmvniFVCY6YoKlC6eHdwxsgHAxOSgD1QKiiQX_yAe39ja_axZD2Ii3QaNgO0WXzfWMbqRg_yl0y3kjQFys9iXGvQ1JIKDMLffR3rKVL5PgKSU3e472xcPKf6PNSJzphPi1G_xH2gqg1VVXo3Lg
Decoded:
Header:
(
[alg] => PS256
[typ] => JWT
)
Body:
(
[sub] => 010bb9ea-4a84-45e3-9893-0c3a461fd30e
[iat] => 1605892967
[exp] => 1606497767
[aud] => 70bc711451c640c95ef83b7a49b1a41b
[iss] => 23faa4b4-06ee-4e4a-a5cd-962f94a213bf
[jti] => 513bb73d-3967-41c5-8309-4b75d28de752
)
sub is a GUID user ID (we utilize GUIDs so that if a user's ID is leaked no information can be extrapolated, like the number of users in our system or when a user signed up)
iat is the epoch time that the token was issued (UTC)
exp is the epoch time that the token will expire (UTC)
aud doesn't conform to the JWT spec. I abused this claim to mitigate the effects of stolen tokens. It's the MD5 hash of data sent with every client request that would be difficult for someone to guess. So if someone were to steal this token and use it without sending the appropriate passphrase, the token would be automatically revoked
iss also doesn't conform to the JWT spec. I abused this claim to list the ID of the key used for signing the JWT. This way I can rotate my public-private key pair and know which key to use when validating signatures
jti is a GUID uniquely identifying the JWT. Compared against an in-memory store of revoked tokens
I went with the PS256 algorithm over RS256 because I read on a blog post that it's more secure. Honestly I don't know the difference.
I went with the PS256 algorithm over ES256 because upon testing I found that while ES256 generated smaller signatures (and therefore smaller tokens), it took about 3x longer to compute. My goal is to make this app as scalable as possible, so long computation time is to be avoided.
My public key:
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA0wO7By66n38BCOOPqxnj78gj8jMKACO5APe+M9wbwemOoipeC9DR
CGxC9q+n/Ki0lNKSujjCpZfnKe5xepL2klyKF7dGQwecgf3So7bve8vvb+vD8C6l
oqbCYEOHdLCDoC2UXEWIVRcV5H+Ahawym+OcE/0pzWlNV9asowIFWj/IXgDVKCFQ
nj164UFlxW1ITqLOQK1WlxqHAIoh20RzpeJTlX9PYx3DDja1Pw7TPomHChMeRNsw
Z7zJiavYrBCTvYE+tm7JrPfbIfc1a9fCY3LlwCTvaBkL2F5yeKdH7FMAlvsvBwCm
QhPE4jcDINUds8bHu2on5NU5VmwHjQ46xwIDAQAB
-----END RSA PUBLIC KEY-----
Using jsonwebtoken for NodeJS I can verify this token and authorize requests made using it. So all of the data seems good, the key works, and the math checks out.
However I've run into two problems when trying to verify the token in PHP:
1. The public key doesn't seem to be valid?
$key = openssl_pkey_get_public($pem);
print_r($key);
die();
This code prints out "false" - suggesting that the key could not be read from the PEM text posted above. Googling around I found this comment in the PHP manual which provided a solution. I did as instructed (removed new-lines from my key, prepended MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A, then wrapped to 64 characters) and for some reason openssl_pkey_get_public($pem) actually returned an OpenSSL Public Key now. I'm not really keen on using copy/paste solutions I don't understand, though, and the comment mentioned that this will only work for 2048-bit keys, which concerns me if we ever want to upgrade our security in the future.
After making the changes suggested to my key the new key looks like this:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0wO7By66n38BCOOPqxnj
78gj8jMKACO5APe+M9wbwemOoipeC9DRCGxC9q+n/Ki0lNKSujjCpZfnKe5xepL2
klyKF7dGQwecgf3So7bve8vvb+vD8C6loqbCYEOHdLCDoC2UXEWIVRcV5H+Ahawy
m+OcE/0pzWlNV9asowIFWj/IXgDVKCFQnj164UFlxW1ITqLOQK1WlxqHAIoh20Rz
peJTlX9PYx3DDja1Pw7TPomHChMeRNswZ7zJiavYrBCTvYE+tm7JrPfbIfc1a9fC
Y3LlwCTvaBkL2F5yeKdH7FMAlvsvBwCmQhPE4jcDINUds8bHu2on5NU5VmwHjQ46
xwIDAQAB
-----END PUBLIC KEY-----
(note that this is the same key, just with 32 magic bytes prepended to the beginning of it and "BEGIN RSA PUBLIC KEY" replaced with "BEGIN PUBLIC KEY")
2. The signature fails to verify (possibly because I'm using PS256 and not RS256)
Ignoring the issues with #1 for now and moving on to the next step, I tried to verify my signature like so:
$success = openssl_verify($jwtHeader . "." . $jwtBody, $jwtSignature, $key, OPENSSL_ALGO_SHA256);
This returned false. Meaning the signature was not valid. But I know the signature was valid because it worked fine in NodeJS. So I suspect the issue here revolves around my choice of algorithm.
How do I get PHP to properly verify this token?
Update 1
Here's the code that I'm using to verify my tokens in NodeJS. This is a HapiJS + TypeScript project, but you should be able to make sense of it. jwt is just defined as import * as jwt from "jsonwebtoken";:
jwt.verify(
token,
server.plugins["bf-jwtAuth"].publicKeys[tokenData.iss].key,
{
algorithms: [options.algorithm],
audience: userHash,
maxAge: options.tokenMaxAge
},
err =>
{
// We can disregard the "decoded" parameter
// because we already decoded it earlier
// We're just interested in the error
// (implying a bad signature)
if (err !== null)
{
request.log([], err);
return reject(Boom.unauthorized());
}
return resolve(h.authenticated({
credentials: {
user: {
id: tokenData.sub
}
}
}));
}
);
There's not too much to see here, because I just relied on a third-party tool to do all of the validation for me. jwt.verify(...) and it worked like magic.
Update 2
Assuming that my issue lie in the algorithm being used (PS256 vs RS256) I started searching around and found this StackOverflow post which pointed me at phpseclib
We actually coincidentally already had phpseclib installed via Composer as a dependency of Google's auth SDK, so I bumped it up to a top-level dependency and gave it a try. Unfortunately I still ran into an issue. Here's my code so far:
use phpseclib\Crypt\RSA;
// Setup:
$rsa = new RSA();
$rsa->setHash("sha256");
$rsa->setMGFHash("sha256");
$rsa->setSignatureMode(RSA::SIGNATURE_PSS);
// The variables I'm working with:
$jwt = explode(".", "..."); // [Header, Body, Signature]
$key = "..."; // This is my PEM-encoded string, from above
// Attempting to verify:
$rsa->loadKey($key);
$valid = $rsa->verify($jwt[0] . "." . $jwt[1], base64_decode($jwt[2]));
if ($valid) { die("Valid"); } else { die("Invalid"); }
Neither die() statement is reached as I hit an error on the $rsa->verify() line with the following:
ErrorException: Invalid signature
at
/app/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php(2693)
Looking at this line in the library, it looks like it's failing at the "length checking" step:
if (strlen($s) != $this->k) {
user_error("Invalid signature");
}
I'm not sure what length it's expecting, though. I passed the raw signature directly from the JWT
After messing with this all day I finally figured out the missing piece.
First, some quick notes on the original question (already touched on in the updates):
To do RSA signatures with PSS padding ("PS256") you will need a third-party library, because the OpenSSL functions in PHP don't support this. A common recommendation is phpseclib
The 32 magic bytes I had to add to the key were only a quirk of PHP's OpenSSL functions and don't need to be utilized with phpseclib
With that said, my final problem (with the signature being "invalid") was:
JWT signatures are base64URL encoded, not base64 encoded
I wasn't even aware there was an RFC specification for base64URL encoding. It turns out you just replace every + with a - and every / with an _. So instead of:
$signature = base64_decode($jwt[2]);
It should be:
$signature = base64_decode(strtr($jwt[2], "-_", "+/"));
This works and my signature finally validates!
Is there a coldfusion alternaitive to this php function: openssl_verify:
openssl_verify() verifies that the signature is correct for the
specified data using the public key associated with pub_key_id. This
must be the public key corresponding to the private key used for
signing.
I've looked all over but there doesn't seem to be any. Thanks in advance for any info?
There are no built in functions, AFAIK. However, java supports signature verification, which you could adapt with a bit of java code.
Convert the data you want to verify into binary. The exact steps depends on what you are verifying, but say it is a physical file:
dataBytes = fileReadBinary( "c:\path\someFile.zip" );
Decode the signature value into binary. Again, the "how" depends on the signature format. If it is a base64 encoded string:
signatureBytes = binaryDecode( base64SignatureString, "base64" );
Load the certificate from your keystore (or from a file) and extract the public key:
// Example: "C:\ColdFusion\jre\lib\security\cacerts"
fis = createObject("java", "java.io.FileInputStream").init( pathToKeyStore );
keyStore = createObject("java", "java.security.KeyStore").getInstance("JKS");
// Default keystore password is "changeit" (do not keep the default. change it)
keyStore.load(fis, keyStorePassword.toCharArray());
publicKey = keyStore.getCertificate( "yourCertAlias" ).getPublicKey();
Create a Signature object to perform the verification. Initialize it with the appropriate algorithm (ie SHA1withRSA, etcetera), public key and the data to verify:
sign = createObject("java", "java.security.Signature").getInstance("SHA1withRSA");
sign.initVerify( publicKey );
sign.update( dataBytes );
Finally, feed in the signature and verify its status:
isVerified = sign.verify(signatureBytes);
writeDump( isVerified );
For more details, see Lesson: Generating and Verifying Signatures and Weaknesses and Alternatives.
You could attempt using cfexecute along with the OpenSSL CLI. https://www.openssl.org/docs/manmaster/apps/verify.html
Update
Apparently, even though I thought I was generating keys that did not have a password, gnupg still expected a password for them (which the gnupg extension no longer supports). I regenerated a new keypair using Kleopatra on Windows and bypassed all the "no passphrase" warnings and I was able to successfully sign/encrypt with those keys.
So, the bottom line is be very sure that your key does not have a passphrase.
I am attempting to sign a message using PHP's gnupg extension. I have the environment setup correctly, and I can successfully import the key, and adding it using gnupg_addsignkey is successful (returns true).
When I attempt to sign the message using gnupg_sign($res, "my message"), I get the following error and gnupg_sign returns false:
gnupg_sign(): data signing failed
I can't seem to find any way to get more verbose information to figure out why it's failing.
I've tried the procedural methods, as well as the OO methods, and get the same result. The permission are all correct on the server.
Here's the OO code I've used:
# /tmp/.gnupg is there (but empty if that helps figure out the problem)
putenv("GNUPGHOME=/tmp/.gnupg");
$gpg = new gnupg();
$gpg->seterrormode(GNUPG_ERROR_WARNING);
$ascii = file_get_contents('/etc/my.key'); // Yes, this reads successfully
$start = strpos($ascii, '-----BEGIN PGP PRIVATE KEY BLOCK-----');
$end = strpos($ascii, '-----END PGP PRIVATE KEY BLOCK-----')+34;
$key = substr($ascii, $start, ($end-$start));
$info = $gpg->import($key); // Fingerprint is there and everything seems OK
$gpg->addsignkey($info['fingerprint']);
$signed = $gpg->sign("test!"); // fails with any string I try
$signed is false, and I get the PHP warning gnupg::sign(): data signing failed
Is your private key password protected?
According to pecl/gnupg documentation you cannot pass a plaintext password for gnupg ≥ version 2.
So all you can do is use a private key that has no password set, I guess.
IMO pecl/gnupg errors are quite misleading.
I want to use PGP encryption to encrypt a CSV files, I am generating through a PHP script and then send that file to client via email. Client will give me the encryption key, which I need to use for encryption files.
I Googled about PGP and found it is Pretty Good Privacy, also I found OpenPGP http://www.openpgp.org/ and GnuPG http://www.gnupg.org/ What are these two types of PGP? and which one should I use?
Also how to encrypt a files using PGP in PHP with the key that my client will provide?
I have heard this term first time, can anyone please help in understanding this and implementing this in PHP.
Question 1: About PGP
PGP (Pretty Good Privacy) is a product and trademark of Symantec Corporation (they bought it some years ago).
OpenPGP is the standard used by PGP.
GnuPG (Gnu Privacy Guard) is a free and open source implementation of PGP.
So what you want to do is encrypt to an OpenPGP key. Which implementation of OpenPGP your client uses to decrypt the data is not important for you. With PHP, commonly GnuPG is used and there are interfaces built-in.
Question 2: Using GnuPG in PHP
Use the GnuPG interface, which is an extension that can be installed for PHP.
At first, import the key, where $keydata is the ASCII armored public key:
<?php
$gpg = new gnupg();
$info = $gpg -> import($keydata);
print_r($info);
?>
Then use this key to encrypt the data, this time using the client's key's fingerprint:
<?php
$gpg = new gnupg();
$gpg -> addencryptkey("8660281B6051D071D94B5B230549F9DC851566DC");
$enc = $gpg -> encrypt("just a test");
echo $enc;
?>
If you want to encrypt files, read and pass them to encrypt(). Be sure to use at least long key IDs (eg. DEADBEEFDEADBEEF), better fingerprints (as in the example) when referencing keys; and never use short key IDs (DEADBEEF), as those are vulnerable to collision attacks.
The is a more comprehensive example for doing both added by a user in the PHP manual.
Going to leave an answer here as many examples across the net for PHP GnuPG are very bare bones and hopefully this saves someone some frustration.
Basically, it mirrors how the GnuPG command line tool works. You need to import a key if it's not already in gpg's key ring then you need to select the recipient's key to use for encryption/decryption.
gpg --import recipients-public-key.asc
gpg -r recipient --encrypt test.txt
If you did what I did and passed in the key as the recipient it doesn't work!
It's not clear what this field is in either the GPG manual or PHP documentation which refers to this field as "fingerprint". Check gpg's key ring for your freshly imported key with:
gpg --list-keys
This will output something like this:
pub rsa2048 2019-04-14 [SC] [expires: 2021-04-14]
0DAA2C747B1974BE9EB9E6DCF7EE249AD00A46AA
uid [ultimate] Dean Or
sub rsa2048 2019-04-14 [E] [expires: 2021-04-14]
This will give you the UID and on the second line the fingerprint associated with every key. As far as I can tell you can use the UID and fingerprint as the recipient.
So your PHP code to encrypt might look like this:
// Encrypt
$gpg = new gnupg();
$gpg->seterrormode(gnupg::ERROR_EXCEPTION);
// Check key ring for recipient public key, otherwise import it
$keyInfo = $gpg->keyinfo('0DAA2C747B1974BE9EB9E6DCF7EE249AD00A46AA');
if (empty($keyInfo)) {
$gpg->import('recipients-public-key.asc');
}
$gpg->addencryptkey('0DAA2C747B1974BE9EB9E6DCF7EE249AD00A46AA');
echo $gpg->encrypt('This is a test!');
Then the recipient's code will look like this:
// Decrypt
$gpg = new gnupg();
$gpg->seterrormode(gnupg::ERROR_EXCEPTION);
// Check key ring for recipient private key, otherwise import it
$keyInfo = $gpg->keyinfo('0DAA2C747B1974BE9EB9E6DCF7EE249AD00A46AA');
if (empty($keyInfo)) {
$gpg->import('recipients-private-key.asc');
}
$gpg->adddecryptkey('0DAA2C747B1974BE9EB9E6DCF7EE249AD00A46AA', '');
echo $gpg->decrypt($encyptedMessage);
Note the fingerprints are the same for both the recipient's public and private key.
There is also a known issue with adddecryptkey not taking a passphrase! You either need to remove the passphrase or change your version of GnuPG.
I have a small string of some data (less than 1kb) that I would like to have user agents pass to other sites when they are sent from my site. In order for the other sites to verify that I was the one that created the string I though of two options.
The server pings me back to confirm (like paypal, openid, etc..)
I use public/private keys to prove I sent the message (like PGP, DKIM, etc..)
I don't want to setup HMAC because that would mean I have to use custom keys for each site which would be a pain.
Out of those two choices it seems that #2 would save on bandwidth which makes it seem like a better choice.
So how can you setup public/private key cryptography using PHP and are there any downsides?
Creating a private and public key pair using the PHP Openssl functions:
// Configuration settings for the key
$config = array(
"digest_alg" => "sha512",
"private_key_bits" => 4096,
"private_key_type" => OPENSSL_KEYTYPE_RSA,
);
// Create the private and public key
$res = openssl_pkey_new($config);
// Extract the private key into $private_key
openssl_pkey_export($res, $private_key);
// Extract the public key into $public_key
$public_key = openssl_pkey_get_details($res);
$public_key = $public_key["key"];
You can then encrypt and decrypt using the private and public keys like this:
// Something to encrypt
$text = 'This is the text to encrypt';
echo "This is the original text: $text\n\n";
// Encrypt using the public key
openssl_public_encrypt($text, $encrypted, $public_key);
$encrypted_hex = bin2hex($encrypted);
echo "This is the encrypted text: $encrypted_hex\n\n";
// Decrypt the data using the private key
openssl_private_decrypt($encrypted, $decrypted, $private_key);
echo "This is the decrypted text: $decrypted\n\n";
I would create S/MIME public/private keypairs using OpenSSL and then use the OpenSSL command to do the encryption & decryption. I believe that this is superior to using PGP because openssl is included with most linux operating systems and PGP isn't. OpenSSL is also standards-based and generally easier to work with, once you have the commands down.
I recommended against a "pure-PHP" solution (by pure-PHP I mean doing the crypto in PHP, rather than using PHP to call an existing library or a separate executable). You don't want to do bulk crypto in PHP. Too slow. And you want to use OpenSSL, because it's high performance and the security is well understood.
Here's the magic.
To make an X.509 key:
$subj="/C=US/ST=California/L=Remote/O=Country Govt./OU=My Dept/CN=Mr. Agent/emailAddress=agent#investiations.com"
openssl req -x509 -newkey rsa:1024 -keyout mycert.key -out mycert.pem -nodes -subj $subj
That puts the private key in mycert.key and the public key in mycert.pem. The private key is not password protected.
Now, to sign a message with S/MIME:
openssl smime -sign -signer mycert.pem -inkey mycert.key <input >output
To encrypt a message with S/MIME:
openssl smime -encrypt -recip yourcert.pem <input >output
To decrypt a message with S/MIME:
openssl smime -decrypt -inkey mycert.key -certfile mycert.pem <input >output
I also have some demos on using OpenSSL from the C language bindings, but not from PHP.
Rule 1: Don't implement it yourself, use a library.
Which library? Here are my recommended PHP public-key cryptography libraries:
Halite, depends on libsodium (but emphasizes simplicity and ease-of-use in addition to security).
libsodium, from PECL
EasyRSA, which implements secure public-key encryption and public-key signatures using RSA in the most secure modes (NOT PKCS1v1.5, ever!)
phpseclib, which EasyRSA piggybacks off of.
In general, you'll want libsodium if security is your goal. Whether or not you use Halite is a matter of taste.
PGP is a good option - it is implemented properly and completely (i.e. you have little room for security mistakes with PGP). I think this SO question will help you with interfacing with GnuPG. The question is whether and how the other sites will verify your signature. You need to either conform to their verification mechanism requirements or provide your own module that those sites will use for verification.
Also it's possible that you can use OAuth or OpenID to identify users on those other sites, but I am not an expert in these technologies.