PGP Key Generator — C++ source
Generate PGP key pairs (ECC or RSA) in your browser. Download your public and private keys. Powered by OpenPGP.js.
This is the C++ implementation — the same logic the interactive tool runs, in a shareable, citable form.
// pgp-keygen — OpenPGP key pair generation (ECC Curve25519, RSA-2048, RSA-4096).
//
// Language: C++17 (standard library + GPGME 1.x — the GnuPG project's official
// C binding, the native counterpart to the TS reference's openpgp.js)
// Ported from src/lib/pgp-keygen.ts (the canonical TypeScript implementation).
// display source — part of CosmoDev's polyglot tool pages.
//
// The TS reference is a thin wrapper around openpgp.js `generateKey`; this port
// keeps that shape and wraps GPGME instead — identity validation stays pure
// C++, key generation delegates to the library. Both produce the same four
// outputs: an ASCII-armored public key, an ASCII-armored private key, the
// 40-hex-char v4 fingerprint, and an armored revocation certificate.
//
// The private key never leaves the process: GPGME is driven against a
// caller-supplied empty GNUPGHOME (gpg writes the revocation certificate it
// generates into <GNUPGHOME>/openpgp-revocs.d/, which we re-read and armor),
// mirroring the TS island's guarantee that generation happens client-side.
//
// Build: c++ -std=c++17 pgp-keygen.cpp $(gpgme-config --cflags --libs)
#include <cctype>
#include <fstream>
#include <memory>
#include <regex>
#include <stdexcept>
#include <string>
#include <vector>
#include <gpgme.h>
namespace pgpkeygen {
/** Algorithm choices: ECC Curve25519 (default), RSA-2048, or RSA-4096. */
enum class PGPKeyGenAlgorithm { ECC, RSA2048, RSA4096 };
struct PGPKeyGenOptions {
std::string name; // goes into the key's user ID packet
std::string email; // goes into the key's user ID packet
std::string passphrase; // optional; the private key is encrypted with it
PGPKeyGenAlgorithm algorithm = PGPKeyGenAlgorithm::ECC;
};
struct PGPKeyPair {
std::string publicKey; // ASCII-armored public key (BEGIN PGP PUBLIC KEY BLOCK)
std::string privateKey; // ASCII-armored private key (BEGIN PGP PRIVATE KEY BLOCK)
std::string fingerprint; // 40 hex chars (v4), lowercase
std::string revocationCertificate; // ASCII-armored revocation certificate
};
// ── validation ─────────────────────────────────────────────────────────────
static std::string trim(const std::string& s) {
const size_t first = s.find_first_not_of(" \t\r\n");
if (first == std::string::npos) return "";
const size_t last = s.find_last_not_of(" \t\r\n");
return s.substr(first, last - first + 1);
}
/** Accepts foo@bar.tld-style addresses: one @, non-empty local + domain, a dot in the domain. */
static bool validEmail(const std::string& email) {
static const std::regex EMAIL_RE(R"(^[^\s@]+@[^\s@]+\.[^\s@]+$)");
return std::regex_match(email, EMAIL_RE);
}
/** Validate the identity that goes into the key's user ID. Throws on invalid input. */
void validateKeyGenIdentity(const std::string& name, const std::string& email) {
if (trim(name).empty()) throw std::runtime_error("Name is required.");
if (trim(email).empty()) throw std::runtime_error("Email is required.");
if (!validEmail(trim(email))) throw std::runtime_error("Invalid email address.");
}
// ── GPGME plumbing ─────────────────────────────────────────────────────────
struct Deleter {
void operator()(gpgme_ctx_t ctx) const { gpgme_release(ctx); }
void operator()(gpgme_data_t data) const { gpgme_data_release(data); }
};
using Context = std::unique_ptr<gpgme_ctx_t, Deleter>;
using Data = std::unique_ptr<gpgme_data_t, Deleter>;
static void check(gpgme_error_t err, const std::string& what) {
if (gpgme_err_code(err) != GPG_ERR_NO_ERROR) {
throw std::runtime_error(what + ": " + gpgme_strerror(err));
}
}
static Data dataFromString(const std::string& text) {
gpgme_data_t raw = nullptr;
if (gpgme_data_new_from_mem(&raw, text.data(), text.size(), 1 /* copy */) != GPG_ERR_NO_ERROR) {
throw std::runtime_error("GPGME data buffer allocation failed.");
}
return Data(raw);
}
static Data dataForOutput() {
gpgme_data_t raw = nullptr;
if (gpgme_data_new(&raw) != GPG_ERR_NO_ERROR) {
throw std::runtime_error("GPGME data buffer allocation failed.");
}
return Data(raw);
}
static std::string dataToString(gpgme_data_t data) {
static const size_t CHUNK = 4096;
std::string out;
std::vector<char> buffer(CHUNK);
size_t read = 0;
while ((read = gpgme_data_read(data, buffer.data(), CHUNK)) > 0) {
out.append(buffer.data(), read);
}
return out;
}
/** A passphrase handed to GPGME through its callback protocol. */
struct Passphrase {
std::string value;
bool used = false;
};
static gpgme_error_t passphraseCb(void* hook, const char* /*uid_hint*/, const char* /*passphrase_info*/,
int /*last_was_bad*/, int fd) {
auto* pass = static_cast<Passphrase*>(hook);
if (!pass->used) {
gpgme_io_writen(fd, pass->value.data(), pass->value.size());
pass->used = true;
}
gpgme_io_writen(fd, "\n", 1);
return 0;
}
/**
* Export one key (by fingerprint) as ASCII armor. `secret` selects the
* private-key export mode.
*/
static std::string exportKey(gpgme_ctx_t ctx, const std::string& fpr, bool secret) {
const Data out = dataForOutput();
gpgme_export_mode_t mode = secret
? static_cast<gpgme_export_mode_t>(GPGME_EXPORT_MODE_SECRET)
: static_cast<gpgme_export_mode_t>(0);
check(gpgme_op_export(ctx, fpr.c_str(), mode, out.get()), "key export failed");
const std::string armored = dataToString(out.get());
if (armored.empty()) throw std::runtime_error("key export produced no data");
return armored;
}
/** Read gpg's generated revocation certificate for `fpr` from the GNUPGHOME
* and re-armor it via an import/export round-trip through GPGME. */
static std::string revocationCertificate(gpgme_ctx_t ctx, const std::string& homeDir,
const std::string& fpr) {
std::string upper;
for (char c : fpr) upper += static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
const std::string path = homeDir + "/openpgp-revocs.d/" + upper + ".rev";
std::ifstream file(path, std::ios::binary);
if (!file) throw std::runtime_error("Revocation certificate not found at " + path);
const std::string raw((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
if (raw.empty()) throw std::runtime_error("Revocation certificate is empty.");
const Data importData = dataFromString(raw);
// A lone revocation signature packet imports as an update to the public key.
check(gpgme_op_import(ctx, importData.get()), "revocation import failed");
return exportKey(ctx, fpr, false);
}
// ── generation ─────────────────────────────────────────────────────────────
/**
* Generate an ASCII-armored PGP key pair. ECC (Curve25519) is fast; RSA-4096
* can take a few seconds. `homeDir` must be an empty ephemeral directory.
*/
PGPKeyPair generatePGPKeyPair(const PGPKeyGenOptions& options, const std::string& homeDir) {
validateKeyGenIdentity(options.name, options.email);
if (gpgme_check_version(nullptr) == nullptr) {
throw std::runtime_error("GPGME is not available.");
}
gpgme_ctx_t rawCtx = nullptr;
check(gpgme_new(&rawCtx), "gpgme_new failed");
Context ctx(rawCtx);
check(gpgme_set_protocol(ctx.get(), GPGME_PROTOCOL_OpenPGP), "protocol selection failed");
gpgme_set_armor(ctx.get(), 1);
gpgme_ctx_set_engine_info(ctx.get(), GPGME_PROTOCOL_OpenPGP, nullptr, homeDir.c_str());
// An empty-string passphrase would still encrypt the key; only a real
// passphrase should.
Passphrase pass{options.passphrase, false};
if (!options.passphrase.empty()) {
gpgme_set_passphrase_cb(ctx.get(), passphraseCb, &pass);
}
// Algorithm selector: "future-default" is GPGME's ECC Curve25519/Ed25519
// profile (the TS reference's type 'curve25519'); RSA uses explicit bit sizes.
const char* algo = options.algorithm == PGPKeyGenAlgorithm::ECC ? "future-default"
: options.algorithm == PGPKeyGenAlgorithm::RSA4096 ? "rsa4096"
: "rsa2048";
const unsigned flags =
GPGME_CREATE_SIGN | GPGME_CREATE_ENC |
(options.passphrase.empty() ? GPGME_CREATE_NOPASSWD : 0u);
const std::string userID = trim(options.name) + " <" + trim(options.email) + ">";
check(gpgme_op_createkey(ctx.get(), userID.c_str(), algo, 0 /*reserved*/, 0 /*never expires*/,
static_cast<gpgme_keygen_mode_t>(0), flags),
"key generation failed");
const gpgme_genkey_result_t result = gpgme_op_genkey_result(ctx.get());
if (result == nullptr || result->primary_fpr == nullptr) {
throw std::runtime_error("key generation produced no fingerprint");
}
const std::string fpr = result->primary_fpr;
return {
exportKey(ctx.get(), fpr, false),
exportKey(ctx.get(), fpr, true),
fpr,
revocationCertificate(ctx.get(), homeDir, fpr),
};
}
} // namespace pgpkeygen
Also available in 8 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →