PGP Encrypt & Decrypt — TypeScript source
Encrypt or decrypt messages with PGP public/private keys. Powered by OpenPGP.js, runs entirely in your browser.
This is the TypeScript implementation — the same logic the interactive tool runs, in a shareable, citable form.
import * as openpgp from 'openpgp';
export interface PGPKeyInfo {
userID: string;
fingerprint: string;
algorithm: string;
creationDate: string;
expiry: string | null;
isPrivate: boolean;
}
/** Format an expiration value (Date | Infinity) as an ISO date string, or null if never expires. */
function formatExpiry(expiration: Date | Infinity | null): string | null {
if (!expiration || expiration === Infinity) return null;
return expiration.toISOString().split('T')[0];
}
/**
* Read a PGP key (public or private) and extract its metadata.
* Throws on invalid or unrecognized key material.
*/
export async function readKeyInfo(armoredKey: string): Promise<PGPKeyInfo> {
if (!armoredKey || typeof armoredKey !== 'string') {
throw new Error('Key input is empty or invalid.');
}
const trimmed = armoredKey.trim();
// Detect key type from armor header
const isPrivateKey =
trimmed.includes('-----BEGIN PGP PRIVATE KEY BLOCK-----') ||
trimmed.includes('-----BEGIN PGP SECRET KEY BLOCK-----');
try {
const key = await openpgp.readKey({ armoredKey: trimmed });
const users = key.getUserIDs();
const userID = users[0] || 'unknown';
const expiration = await key.getExpirationTime();
return {
userID,
fingerprint: key.getFingerprint().toUpperCase(),
algorithm: key.getAlgorithmInfo().algorithm,
creationDate: key.getCreationTime().toISOString().split('T')[0],
expiry: formatExpiry(expiration),
isPrivate: isPrivateKey,
};
} catch (e) {
throw new Error(
`Invalid PGP key: ${e instanceof Error ? e.message : 'unrecognized key format'}`
);
}
}
/**
* Encrypt a plaintext message for a recipient's public key.
* Returns an ASCII-armored PGP message.
*
* Optionally signs with the sender's private key (signingPrivateKeyArmored + passphrase).
*/
export async function pgpEncrypt(
message: string,
publicKeyArmored: string,
signingPrivateKeyArmored?: string,
passphrase?: string
): Promise<string> {
if (!message) throw new Error('Message must not be empty.');
if (!publicKeyArmored) throw new Error('Recipient public key must not be empty.');
const publicKey = await openpgp.readKey({
armoredKey: publicKeyArmored.trim(),
});
let signingKeys: openpgp.PrivateKey | undefined;
if (signingPrivateKeyArmored) {
const raw = await openpgp.readPrivateKey({
armoredKey: signingPrivateKeyArmored.trim(),
});
signingKeys = passphrase
? await openpgp.decryptKey({ privateKey: raw, passphrase })
: raw;
}
const encrypted = await openpgp.encrypt({
message: await openpgp.createMessage({ text: message }),
encryptionKeys: publicKey,
...(signingKeys ? { signingKeys } : {}),
});
return encrypted;
}
/**
* Decrypt an ASCII-armored PGP message with the recipient's private key.
* Returns the plaintext message.
*/
export async function pgpDecrypt(
armoredMessage: string,
privateKeyArmored: string,
passphrase?: string
): Promise<string> {
if (!armoredMessage) throw new Error('Armored message must not be empty.');
if (!privateKeyArmored) throw new Error('Private key must not be empty.');
const privateKey = await openpgp.readPrivateKey({
armoredKey: privateKeyArmored.trim(),
});
const decryptedKey = passphrase
? await openpgp.decryptKey({ privateKey, passphrase })
: privateKey;
const message = await openpgp.readMessage({
armoredMessage: armoredMessage.trim(),
});
const { data } = await openpgp.decrypt({
message,
decryptionKeys: decryptedKey,
});
return data;
}
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 →