Sitemap

End-to-End Guide: Generating and Using PGP Keys with C# and OpenPGP.js

3 min readSep 5, 2025

--

PGP (Pretty Good Privacy) remains one of the most widely used encryption standards for secure communication. But if you’ve ever tried to generate keys in C# (using BouncyCastle) and then use them in JavaScript (OpenPGP.js), you’ll know it’s not as straightforward as it should be. After some trial and error, I’ve put together this guide so you don’t have to scramble around.

This tutorial will show you how to:

  1. Generate a 2048-bit PGP key pair in C#.
  2. Export the keys in ASCII-armored format.
  3. Use them directly in OpenPGP.js to encrypt and decrypt messages.

Step 1 — Generate PGP Keys in C# with BouncyCastle

We’ll use BouncyCastle in .NET to generate a keypair.

using System;
using System.IO;
using System.Text;
using Org.BouncyCastle.Bcpg;
using Org.BouncyCastle.Bcpg.OpenPgp;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Security;

namespace Keygen
{
class Program
{
static void Main(string[] args)
{
var keys = GeneratePgpKeys("test@example.com");

Console.WriteLine("----- PUBLIC KEY -----");
Console.WriteLine(keys.publicKey);
Console.WriteLine("----- PRIVATE KEY -----");
Console.WriteLine(keys.privateKey);
}

public static (string publicKey, string privateKey) GeneratePgpKeys(string identity)
{
// Generate master (signing) key
var rsaSign = new RsaKeyPairGenerator();
rsaSign.Init(new KeyGenerationParameters(new SecureRandom(), 2048));
AsymmetricCipherKeyPair signPair = rsaSign.GenerateKeyPair();

var masterKeyPair = new PgpKeyPair(PublicKeyAlgorithmTag.RsaGeneral, signPair, DateTime.UtcNow);

// Flags for signing master key
var masterPackets = new PgpSignatureSubpacketGenerator();
masterPackets.SetKeyFlags(false, PgpKeyFlags.CanSign | PgpKeyFlags.CanCertify);
char[] passphrase = "test@Passphrase".ToCharArray();

var keyRingGen = new PgpKeyRingGenerator(
PgpSignature.DefaultCertification,
masterKeyPair,
identity,
SymmetricKeyAlgorithmTag.Aes256,
passphrase,
true, // unprotected
masterPackets.Generate(),
null,
new SecureRandom()
);

// Generate encryption subkey
var rsaEnc = new RsaKeyPairGenerator();
rsaEnc.Init(new KeyGenerationParameters(new SecureRandom(), 2048));
AsymmetricCipherKeyPair encPair = rsaEnc.GenerateKeyPair();
var encPgpKeyPair = new PgpKeyPair(PublicKeyAlgorithmTag.RsaGeneral, encPair, DateTime.UtcNow);

var encPackets = new PgpSignatureSubpacketGenerator();
encPackets.SetKeyFlags(false, PgpKeyFlags.CanEncryptCommunications | PgpKeyFlags.CanEncryptStorage);

keyRingGen.AddSubKey(encPgpKeyPair, encPackets.Generate(), null);

// Export keys
var publicRing = keyRingGen.GeneratePublicKeyRing();
var secretRing = keyRingGen.GenerateSecretKeyRing();

string pubKey = EncodeKeyRing(publicRing);
string privKey = EncodeKeyRing(secretRing);

return (pubKey, privKey);
}

private static string EncodeKeyRing(object keyRing)
{
using (var mem = new MemoryStream())
{
using (var armor = new ArmoredOutputStream(mem))
{
armor.SetHeader("Version", null); // remove Version header

if (keyRing is PgpPublicKeyRing pub)
{
pub.Encode(armor);
}
else if (keyRing is PgpSecretKeyRing sec)
{
sec.Encode(armor);
}
else
{
throw new ArgumentException("Unsupported key ring type");
}

armor.Close(); // ensure END block is written
}

return Encoding.UTF8.GetString(mem.ToArray())
.Replace("\r\n", "\n"); // LF only
}
}
}
}

Here, I am printing the keys to the terminal so that i could copy them in my openpgpjs file later.

Get rowinC’s stories in your inbox

Join Medium for free to get updates from this writer.

You could also generate separate files for each of the keys just modify the Main like this:

        static void Main(string[] args)
{
var keys = GeneratePgpKeys("test@example.com");

// Write to console
Console.WriteLine("----- PUBLIC KEY -----");
Console.WriteLine(keys.publicKey);
Console.WriteLine("----- PRIVATE KEY -----");
Console.WriteLine(keys.privateKey);

// Write to files
File.WriteAllText("public.asc", keys.publicKey);
File.WriteAllText("private.asc", keys.privateKey);

Console.WriteLine("Keys saved to public.asc and private.asc");
}

This will give you two files:

  • public.asc → Public key (shareable)
  • private.asc → Private key (protected with test@Passphrase

Both files will be in ASCII-armored format (-----BEGIN PGP ... BLOCK-----).

Step 2 — Encrypt and Decrypt with OpenPGP.js

Once you have the keys, you can use them directly in JavaScript.

import * as openpgp from 'openpgp';

// Load the keys (paste from files)
const publicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK-----...`;
const privateKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK-----...`;

//Encrypt

const recipient = await openpgp.readKey({ armoredKey: publicKeyArmored });
const senderPrivate = await openpgp.readPrivateKey({
armoredKey: privateKeyArmored,
});
const unlockedPrivKey = await openpgp.decryptKey({
privateKey: senderPrivate ,
passphrase: "test@Passphrase", // empty string
});
const res = await openpgp.encrypt({
message: await openpgp.createMessage({ text: "Hello World!" }),
encryptionKeys: recipient,
signingKeys: unlockedPrivKey ,
format: "armored",
});


//Decrypt

const decrypted = await openpgp.decrypt({
message: await openpgp.readMessage({ armoredMessage: res }),
decryptionKeys: unlockedPrivKey
});

✅ If everything went right, you should see your original Hello World message.

Troubleshooting

  • Signing key not decrypted → You forgot to unlock the private key with decryptKey().
  • No valid encryption key packet → Ensure your BouncyCastle generator includes the correct key flags (EncryptComms | EncryptStorage | Sign | Certify ).
  • Insecure two-byte hash → Use a proper passphrase and SHA1+ checksum when generating keys.

Conclusion

That’s it! 🎉 You now have a workflow where you:

  • Generate strong 2048-bit PGP keys in C# using BouncyCastle.
  • Export them in a format usable by OpenPGP.js.
  • Encrypt & decrypt messages securely across platforms.

This setup is perfect if you’re building a cross-platform app where the back-end generates keys and the front-end handles encryption/decryption.

📚 Resources

--

--