Pretty Good Privacy (PGP) is an encryption program privacy and authentication for data communication. PGP is used for signing encrypting, and decrypting texts, e-mails, files, directories, and whole disk partitions and to increase the security of e-mail communications.
PGP encryption uses a serial combination of hashing, data compression, symmetric-key cryptography, and finally public-key cryptography; each step uses one of several supported algorithms. Each public key is bound to a username or an e-mail address. The first version of this system was generally known as a web of trust to contrast with the X.509 system, which uses a hierarchical approach based on certificate authority and which was added to PGP implementations later. Current versions of PGP encryption include both options through an automated key management server.
A public key fingerprint is a shorter version of a public key. From a fingerprint, someone can validate the correct corresponding public key. A fingerprint like C3A6 5E46 7B54 77DF 3C4C 9790 4D22 B3CA 5B32 FF66 can be printed on a business card.[3][4]
As PGP evolves, versions that support newer features and are able to create encrypted messages that older PGP systems cannot decrypt, even with a valid private key. Therefore, it is essential that partners in PGP communication understand each other's capabilities or at least agree on PGP settings.
PGP can be used to send messages confidentially. For this, PGP uses hybrid cryptosystem by combining symmetric-key encryption and public-key encryption. The message is encrypted using a symmetric encryption algorithm, which requires a symmetric key generated by the sender. The symmetric key is used only once and is also called a session key. The message and its session key are sent to the receiver. The session key must be sent to the receiver so they know how to decrypt the message, but to protect it during transmission it is encrypted with the receiver's public key. Only the private key belonging to the receiver can decrypt the session key, and use it to symmetrically decrypt the message.
PGP supports message authentication and integrity checking. The latter is used to detect whether a message has been altered since it was completed (the message integrity property) and the former, to determine whether it was actually sent by the person or entity claimed to be the sender (a digital signature). Because the content is encrypted, any changes in the message will result in failure of the decryption with the appropriate key. The sender uses PGP to create a digital signature for the message with either the RSA or DSA algorithms. To do so, PGP computes a hash (also called a message digest) from the plaintext and then creates the digital signature from that hash using the sender's private key.
PGP requires a pair of keys: private and public. Use the public key to encrypt the message. Use the private key to decrypt. The private key can be protected by the passphrase.
Both when encrypting messages and when verifying signatures, it is critical that the public key used to send messages to someone or some entity actually does 'belong' to the intended recipient. Simply downloading a public key from somewhere is not a reliable assurance of that association; deliberate (or accidental) impersonation is possible. From its first version, PGP has always included provisions for distributing users' public keys in an identity certification, which is also constructed cryptographically so that any tampering (or accidental garble) is readily detectable. However, merely making a certificate which is impossible to modify without being detected is insufficient; this can prevent corruption only after the certificate has been created, not before. Users must also ensure by some means that the public key in a certificate actually does belong to the person or entity claiming it. A given public key (or more specifically, information binding a user name to a key) may be digitally signed by a third party user to attest to the association between someone (actually a user name) and the key. There are several levels of confidence which can be included in such signatures. Although many programs read and write this information, few (if any) include this level of certification when calculating whether to trust a key.
As time goes on, you will accumulate keys from other people that you may want to designate as trusted introducers. Everyone else will each choose their own trusted introducers. And everyone will gradually accumulate and distribute with their key a collection of certifying signatures from other people, with the expectation that anyone receiving it will trust at least one or two of the signatures. This will cause the emergence of a decentralized fault-tolerant web of confidence for all public keys.
The web of trust mechanism has advantages over a centrally managed public key infrastructure scheme such as that used by S/MIME but has not been universally used. Users have to be willing to accept certificates and check their validity manually or have to simply accept them. No satisfactory solution has been found for the underlying problem.
PGP requires a pair of keys: private and public. Use the public key to encrypt the message. Use the private key to decrypt. The private key can be protected by the passphrase.
If you already have a pair of keys simply upload them to the internal storage.
Step 1. Create a new Server storage connection.
Step 2. Use Explorer to upload keys.
To generate a pair of keys, use the following JavaScript code:
importPackage(com.toolsverse.util.encryption.pgp);
importPackage(com.toolsverse.config);
// the user, the password, the key length
var publicPrivate = PgpKeyPairGenerator.generateKeyPair("user", "password",
2048);
etlConfig.log("Public key: " + publicPrivate.getKey());
etlConfig.log("Private key: " + publicPrivate.getValue());
To encrypt/decrypt the message using the keys stored in the file, use the following JavaScript code:
importPackage(com.toolsverse.util.encryption.pgp);
importPackage(com.toolsverse.config);
var publicKeyFileName = SystemConfig.instance().getDataFolderName() +
"public_key_file_name";
var privateKeyFileName = SystemConfig.instance().getDataFolderName() +
"private_key_file_name";
var encryped = PgpUtils.encryptStringWithPublicKyeFileName(originalString,
publicKeyFileName);
var decrypted = PgpUtils.decryptStringWithPrivateKeyFileName(encryped,
privateKeyFileName, "password");
To encrypt/decrypt the message using generated keys, use the following JavaScript code:
importPackage(com.toolsverse.util.encryption.pgp);
importPackage(com.toolsverse.config);
// the user, the password, the key length
var publicPrivate = PgpKeyPairGenerator.generateKeyPair("user", "password",
2048);
var encryped = PgpUtils.encryptStringWithPublicKey(originalString,
publicPrivate.getKey());
var decrypted = PgpUtils.decryptStringWithPrivateKey(encryped,
publicPrivate.getValue(),"password");
npm install --save openpgp
bower install --save openpgp
Or just fetch a minified build under dist.
Here are some examples of how to use the v2.x+ API. For more elaborate examples and working code, please check out the public API unit tests. If you're upgrading from v1.x it might help to check out the documentation.
const openpgp = require('openpgp');
Copy dist/openpgp.min.js
or dist/compat/openpgp.min.js
(depending on the browser support you need, see Platform Support) to your project folder, and load it in a script tag:
If you want to use the built-in Web Worker, to offload cryptographic operations off the main thread:
await openpgp.initWorker({ path: 'openpgp.worker.js' }); // set the relative web worker path
On logout, be sure to destroy the worker again, to clear private keys from memory:
await openpgp.destroyWorker();
Alternatively, you can also implement a Web Worker in your application and load OpenPGP.js from there. This can be more performant if you store or fetch keys and messages directly inside the Worker, so that they don't have to be postMessage
d there.
If you want to use the lightweight build (which is smaller, and lazily loads non-default curves on demand), copy dist/lightweight/openpgp.min.js
and dist/lightweight/elliptic.min.js
, load the former in a script tag, and point openpgp.config.indutny_elliptic_path
to the latter:
<script src="lightweight/openpgp.min.js"></script>
<script>
openpgp.config.indutny_elliptic_path = 'lightweight/elliptic.min.js';
</script>
To test whether the lazy loading works, try:
await openpgp.generateKey({ curve: 'brainpoolP512r1', userIds: [{
name: 'Test', email: 'test@test.com' }] });
For more examples of how to generate a key, see Generate new key pair. It is recommended to use curve25519
instead of brainpoolP512r1
by default.
Encryption will use the algorithm specified in config.encryption_cipher (defaults to aes256), and decryption will use the algorithm used for encryption.
(async () => {
const { message } = await openpgp.encrypt({
message: openpgp.message.fromBinary(new Uint8Array([0x01, 0x01, 0x01])), // input as Message object
passwords: ['secret stuff'], // multiple passwords possible
armor: false // don't ASCII armor (for Uint8Array output)
});
const encrypted = message.packets.write(); // get raw encrypted packets as Uint8Array
const { data: decrypted } = await openpgp.decrypt({
message: await openpgp.message.read(encrypted), // parse encrypted bytes
passwords: ['secret stuff'], // decrypt with password
format: 'binary' // output as Uint8Array
});
console.log(decrypted); // Uint8Array([0x01, 0x01, 0x01])
})();
Encryption will use the algorithm preferred by the public key (defaults to aes256 for keys generated in OpenPGP.js), and decryption will use the algorithm used for encryption.
const openpgp = require('openpgp'); // use as CommonJS, AMD, ES6 module or via window.openpgp
(async () => {
await openpgp.initWorker({ path: 'openpgp.worker.js' }); // set the relative web worker path
// put keys in backtick (``) to avoid errors caused by spaces or tabs
const publicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK-----
...
-----END PGP PUBLIC KEY BLOCK-----`;
const privateKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK-----
...
-----END PGP PRIVATE KEY BLOCK-----`; // encrypted private key
const passphrase = `yourPassphrase`; // what the private key is encrypted with
const { keys: [privateKey] } = await openpgp.key.readArmored(privateKeyArmored);
await privateKey.decrypt(passphrase);
const { data: encrypted } = await openpgp.encrypt({
message: openpgp.message.fromText('Hello, World!'), // input as Message object
publicKeys: (await openpgp.key.readArmored(publicKeyArmored)).keys, // for encryption
privateKeys: [privateKey] // for signing (optional)
});
console.log(encrypted); // '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----'
const { data: decrypted } = await openpgp.decrypt({
message: await openpgp.message.readArmored(encrypted), // parse armored message
publicKeys: (await openpgp.key.readArmored(publicKeyArmored)).keys, // for verification (optional)
privateKeys: [privateKey] // for decryption
});
console.log(decrypted); // 'Hello, World!'
})();
Encrypt with multiple public keys:
(async () => {
const publicKeysArmored = [
`-----BEGIN PGP PUBLIC KEY BLOCK-----
...
-----END PGP PUBLIC KEY BLOCK-----`,
`-----BEGIN PGP PUBLIC KEY BLOCK-----
...
-----END PGP PUBLIC KEY BLOCK-----`
];
const privateKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK-----
...
-----END PGP PRIVATE KEY BLOCK-----`; // encrypted private key
const passphrase = `yourPassphrase`; // what the private key is encrypted with
const message = 'Hello, World!';
const { keys: [privateKey] } = await openpgp.key.readArmored(privateKeyArmored);
await privateKey.decrypt(passphrase)
const publicKeys = await Promise.all(publicKeysArmored.map(async (key) => {
return (await openpgp.key.readArmored(key)).keys[0];
}));
const { data: encrypted } = await openpgp.encrypt({
message: openpgp.message.fromText(message), // input as Message object
publicKeys, // for encryption
privateKeys: [privateKey] // for signing (optional)
});
console.log(encrypted); // '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----'
})();
By default, encrypt
will not use any compression. It's possible to override that behavior in two ways:
Either set the compression
parameter in the options object when calling encrypt
.
(async () => {
const encrypted = await openpgp.encrypt({
message: openpgp.message.fromBinary(new Uint8Array([0x01, 0x02, 0x03])), // or .fromText('string')
passwords: ['secret stuff'], // multiple passwords possible
compression: openpgp.enums.compression.zip // compress the data with zip
});
})();
Or, override the config to enable compression:
openpgp.config.compression = openpgp.enums.compression.zlib;
Where the value can be any of:
openpgp.enums.compression.zip
openpgp.enums.compression.zlib
(async () => {
const readableStream = new ReadableStream({
start(controller) {
controller.enqueue(new Uint8Array([0x01, 0x02, 0x03]));
controller.close();
}
});
const { message } = await openpgp.encrypt({
message: openpgp.message.fromBinary(readableStream), // input as Message object
passwords: ['secret stuff'], // multiple passwords possible
armor: false // don't ASCII armor (for Uint8Array output)
});
const encrypted = message.packets.write(); // get raw encrypted packets as ReadableStream<Uint8Array>
// Either pipe the above stream somewhere, pass it to another function,
// or read it manually as follows:
const reader = openpgp.stream.getReader(encrypted);
while (true) {
const { done, value } = await reader.read();
if (done) break;
console.log('new chunk:', value); // Uint8Array
}
// Or, in Node.js, you can pipe the above stream as follows:
const nodeStream = openpgp.stream.webToNode(encrypted);
nodeStream.pipe(nodeWritableStream);
})();
For more information on creating ReadableStreams, see the MDN Documentation on new ReadableStream()
. For more information on reading streams using openpgp.stream
, see the documentation of the web-stream-tools dependency, particularly its Reader class.
(async () => {
const publicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK-----
...
-----END PGP PUBLIC KEY BLOCK-----`; // Public key
const [privateKeyArmored] = `-----BEGIN PGP PRIVATE KEY BLOCK-----
...
-----END PGP PRIVATE KEY BLOCK-----`; // Encrypted private key
const passphrase = `yourPassphrase`; // Password that private key is encrypted with
const privateKey = (await openpgp.key.readArmored([privateKeyArmored])).keys[0];
await privateKey.decrypt(passphrase);
const readableStream = new ReadableStream({
start(controller) {
controller.enqueue('Hello, world!');
controller.close();
}
});
const encrypted = await openpgp.encrypt({
message: openpgp.message.fromText(readableStream), // input as Message object
publicKeys: (await openpgp.key.readArmored(publicKeyArmored)).keys, // for encryption
privateKeys: [privateKey] // for signing (optional)
});
const ciphertext = encrypted.data; // ReadableStream containing '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----'
const decrypted = await openpgp.decrypt({
message: await openpgp.message.readArmored(ciphertext), // parse armored message
publicKeys: (await openpgp.key.readArmored(publicKeyArmored)).keys, // for verification (optional)
privateKeys: [privateKey] // for decryption
});
const plaintext = await openpgp.stream.readToEnd(decrypted.data); // 'Hello, World!'
})();
ECC keys:
Possible values for curve
are: curve25519
, ed25519
, p256
, p384
, p521
, secp256k1
, brainpoolP256r1
, brainpoolP384r1
, or brainpoolP512r1
. Note that both the curve25519
and ed25519
options generate a primary key for signing using Ed25519 and a subkey for encryption using Curve25519.
(async () => {
const { privateKeyArmored, publicKeyArmored, revocationCertificate } = await openpgp.generateKey({
userIds: [{ name: 'Jon Smith', email: 'jon@example.com' }], // you can pass multiple user IDs
curve: 'ed25519', // ECC curve name
passphrase: 'super long and hard to guess secret' // protects the private key
});
console.log(privateKeyArmored); // '-----BEGIN PGP PRIVATE KEY BLOCK ... '
console.log(publicKeyArmored); // '-----BEGIN PGP PUBLIC KEY BLOCK ... '
console.log(revocationCertificate); // '-----BEGIN PGP PUBLIC KEY BLOCK ... '
})();
RSA keys:
(async () => {
const key = await openpgp.generateKey({
userIds: [{ name: 'Jon Smith', email: 'jon@example.com' }], // you can pass multiple user IDs
rsaBits: 4096, // RSA key size
passphrase: 'super long and hard to guess secret' // protects the private key
});
})();
Using a revocation certificate:
(async () => {
const { publicKeyArmored: revokedKeyArmored } = await openpgp.revokeKey({
key: (await openpgp.key.readArmored(publicKeyArmored)).keys[0],
revocationCertificate
});
console.log(revokedKeyArmored); // '-----BEGIN PGP PUBLIC KEY BLOCK ... '
})();
Using the private key:
(async () => {
const { publicKeyArmored, publicKey } = await openpgp.revokeKey({
key: (await openpgp.key.readArmored(privateKeyArmored)).keys[0]
});
})();
(async () => {
var hkp = new openpgp.HKP(); // Defaults to https://keyserver.ubuntu.com, or pass another keyserver URL as a string
let publicKeyArmored = await hkp.lookup({
query: 'alice@example.com'
});
var { keys: [publicKey] } = await openpgp.key.readArmored(publicKeyArmored);
})();
(async () => {
var hkp = new openpgp.HKP('https://pgp.mit.edu');
var publicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK-----
...
-----END PGP PUBLIC KEY BLOCK-----`;
await hkp.upload(publicKeyArmored);
})();
(async () => {
const publicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK-----
...
-----END PGP PUBLIC KEY BLOCK-----`;
const privateKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK-----
...
-----END PGP PRIVATE KEY BLOCK-----`; // encrypted private key
const passphrase = `yourPassphrase`; // what the private key is encrypted with
const { keys: [privateKey] } = await openpgp.key.readArmored(privateKeyArmored);
await privateKey.decrypt(passphrase);
const { data: cleartext } = await openpgp.sign({
message: openpgp.cleartext.fromText('Hello, World!'), // CleartextMessage or Message object
privateKeys: [privateKey] // for signing
});
console.log(cleartext); // '-----BEGIN PGP SIGNED MESSAGE ... END PGP SIGNATURE-----'
const verified = await openpgp.verify({
message: await openpgp.cleartext.readArmored(cleartext), // parse armored message
publicKeys: (await openpgp.key.readArmored(publicKeyArmored)).keys // for verification
});
const { valid } = verified.signatures[0];
if (valid) {
console.log('signed by key id ' + verified.signatures[0].keyid.toHex());
} else {
throw new Error('signature could not be verified');
}
})();
(async () => {
const publicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK-----
...
-----END PGP PUBLIC KEY BLOCK-----`;
const privateKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK-----
...
-----END PGP PRIVATE KEY BLOCK-----`; // encrypted private key
const passphrase = `yourPassphrase`; // what the private key is encrypted with
const { keys: [privateKey] } = await openpgp.key.readArmored(privateKeyArmored);
await privateKey.decrypt(passphrase);
const { signature: detachedSignature } = await openpgp.sign({
message: openpgp.cleartext.fromText('Hello, World!'), // CleartextMessage or Message object
privateKeys: [privateKey], // for signing
detached: true
});
console.log(detachedSignature);
const verified = await openpgp.verify({
message: openpgp.cleartext.fromText('Hello, World!'), // CleartextMessage or Message object
signature: await openpgp.signature.readArmored(detachedSignature), // parse detached signature
publicKeys: (await openpgp.key.readArmored(publicKeyArmored)).keys // for verification
});
const { valid } = verified.signatures[0];
if (valid) {
console.log('signed by key id ' + verified.signatures[0].keyid.toHex());
} else {
throw new Error('signature could not be verified');
}
})();
(async () => {
var readableStream = new ReadableStream({
start(controller) {
controller.enqueue(new Uint8Array([0x01, 0x02, 0x03]));
controller.close();
}
});
const publicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK-----
...
-----END PGP PUBLIC KEY BLOCK-----`;
const privateKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK-----
...
-----END PGP PRIVATE KEY BLOCK-----`; // encrypted private key
const passphrase = `yourPassphrase`; // what the private key is encrypted with
const { keys: [privateKey] } = await openpgp.key.readArmored(privateKeyArmored);
await privateKey.decrypt(passphrase);
const { data: signatureArmored } = await openpgp.sign({
message: openpgp.message.fromBinary(readableStream), // or .fromText(readableStream: ReadableStream<String>)
privateKeys: [privateKey] // for signing
});
console.log(signatureArmored); // ReadableStream containing '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----'
const verified = await openpgp.verify({
message: await openpgp.message.readArmored(signatureArmored), // parse armored signature
publicKeys: (await openpgp.key.readArmored(publicKeyArmored)).keys // for verification
});
await openpgp.stream.readToEnd(verified.data);
// Note: you *have* to read `verified.data` in some way or other,
// even if you don't need it, as that is what triggers the
// verification of the data.
const { valid } = verified.signatures[0];
if (valid) {
console.log('signed by key id ' + verified.signatures[0].keyid.toHex());
} else {
throw new Error('signature could not be verified');
}
})();
A jsdoc build of our code comments is available at doc/index.html. Public calls should generally be made through the OpenPGP object doc/openpgp.html.
For the documentation of openpgp.stream
, see the documentation of the web-stream-tools dependency.
To date the OpenPGP.js code base has undergone two complete security audits from Cure53. The first audit's report has been published here.
We can use kbpgp in encryption and decryption process
kbpgp is Keybase's implementation of PGP in JavaScript. It's easy to use, designed for concurrency, and stable in both Node.js and the browser. It's actively maintained and yours forever under a BSD license.
npm install kbpgp
git clone https://github.com/keybase/kbpgp
<script src="kbpgp-2.1.0.js"></script>
var kbpgp = require('kbpgp');
Before you can perform any crypto, you need a KeyManager.
A KeyManager contains a public key and possibly the secret key and subkeys for a given person. Once you have a KeyManager instance, you can perform actions with the keys inside. For a sign-and-encrypt action, you'll need two KeyManagers: one containing the private key (for the signer), and one containing the public key (for the recipient).
For example, assuming we have two KeyManager instances, alice
and chuck
, we might perform an encryption.
var params = {
encrypt_for: chuck,
sign_with: alice,
msg: "Hey Chuck - my bitcoin address is 1alice12345234..."
};
kbpgp.box(params, function(err, result_string, result_buffer) {
console.log(err, result_string, result_buffer);
});
kbpgp's box
function performs all the work. Note that it calls back with both a string and a Buffer representation. The Buffer is either a Node.js Buffer or, a browser-friendly object with similar features.
Pretty simple, right? So, how do you get a KeyManager? There are 2 ways:
- Loading a key or key pair
- Generating a new key pair
The following examples walk through the conversion of a PGP key string (in classic armored format) to a KeyManager.
var alice_pgp_key = "-----BEGIN PGP PUBLIC ... etc.";
kbpgp.KeyManager.import_from_armored_pgp({
armored: alice_pgp_key
}, function(err, alice) {
if (!err) {
console.log("alice is loaded");
}
});
Now let's assume instead that we have alice's private key. Recall this includes her public key, so it's all we need.
var alice_pgp_key = "-----BEGIN PGP PRIVATE ... etc.";
var alice_passphrase = "martian-dung-flinger";
kbpgp.KeyManager.import_from_armored_pgp({
armored: alice_pgp_key
}, function(err, alice) {
if (!err) {
if (alice.is_pgp_locked()) {
alice.unlock_pgp({
passphrase: alice_passphrase
}, function(err) {
if (!err) {
console.log("Loaded private key with passphrase");
}
});
} else {
console.log("Loaded private key w/o passphrase");
}
}
});
The above example (#2) can be performed in two steps. You can create a KeyManager instance with alice's public key, and then add her private key to it afterwards. This will generate an error if her private key does not match her public key.
var alice_public_key = "-----BEGIN PGP PUBLIC ... etc.";
var alice_private_key = "-----BEGIN PGP PRIVATE ... etc.";
var alice_passphrase = "ovarian fred savage ";
kbpgp.KeyManager.import_from_armored_pgp({
armored: alice_public_key
}, function(err, alice) {
if (!err) {
alice.merge_pgp_private({
armored: alice_private_key
}, function(err) {
if (!err) {
if (alice.is_pgp_locked()) {
alice.unlock_pgp({
passphrase: alice_passphrase
}, function(err) {
if (!err) {
console.log("Loaded private key with passphrase");
}
});
} else {
console.log("Loaded private key w/o passphrase");
}
}
});
}
});
At the end of the below process, we'll have a KeyManager instance, alice
, which can be used for any crypto action.
To illustrate a common use case, we'll generate subkeys for both signing and encryption. And, by the way, when kbpgp performs actions with KeyManagers, it automatically picks the appropriate subkey(s).
var F = kbpgp["const"].openpgp;
var opts = {
userid: "User McTester (Born 1979) <user@example.com>",
primary: {
nbits: 4096,
flags: F.certify_keys | F.sign_data | F.auth | F.encrypt_comm | F.encrypt_storage,
expire_in: 0 // never expire
},
subkeys: [
{
nbits: 2048,
flags: F.sign_data,
expire_in: 86400 * 365 * 8 // 8 years
}, {
nbits: 2048,
flags: F.encrypt_comm | F.encrypt_storage,
expire_in: 86400 * 365 * 8
}
]
};
kbpgp.KeyManager.generate(opts, function(err, alice) {
if (!err) {
// sign alice's subkeys
alice.sign({}, function(err) {
console.log(alice);
// export demo; dump the private with a passphrase
alice.export_pgp_private ({
passphrase: 'booyeah!'
}, function(err, pgp_private) {
console.log("private key: ", pgp_private);
});
alice.export_pgp_public({}, function(err, pgp_public) {
console.log("public key: ", pgp_public);
});
});
}
});
The above parameters are reasonable. If you're happy with them, you can simply call the KeyManager::generate_rsa
shortcut:
kbpgp.KeyManager.generate_rsa({ userid : "Bo Jackson <user@example.com>" }, function(err, charlie) {
charlie.sign({}, function(err) {
console.log("done!");
});
});
Kbpgp has support for Elliptic Curve PGP (see RFC-6637 for more details). You can provide the ecc : true
option to the above generate call to make an ECC key pair rather than the standard PGP keypair. Keep in mind, though, that most GPG clients in the wild do not currently support ECC.
var F = kbpgp["const"].openpgp;
var opts = {
userid: "User McTester (Born 1979) <user@example.com>",
ecc: true,
primary: {
nbits: 384,
flags: F.certify_keys | F.sign_data | F.auth | F.encrypt_comm | F.encrypt_storage,
expire_in: 0 // never expire
},
subkeys: [
{
nbits: 256,
flags: F.sign_data,
expire_in: 86400 * 365 * 8 // 8 years
}, {
nbits: 256,
flags: F.encrypt_comm | F.encrypt_storage,
expire_in: 86400 * 365 * 8
}
]
};
kbpgp.KeyManager.generate(opts, function(err, alice) {
// as before...
});
To use these default parameters, we also provide the shortcut:
kbpgp.KeyManager.generate_ecc({ userid : "<user@example.com>" }, function(err, charlie) {
charlie.sign({}, function(err) {
console.log("done!");
});
});
All kbpgp functions support passing an ASync Package
(ASP) object, for monitoring. Your ASP can have a progress_hook function, which will get called with info about its progress. This is especially important with RSA key generation, as it can take a little while. If this is in any kind of client app, you'll want to (a) show some indicator that you're doing work, and (b) have a cancel button.
var my_asp = new kbpgp.ASP({
progress_hook: function(o) {
console.log("I was called with progress!", o);
}
});
var opts = {
asp: my_asp,
userid: "user@example.com",
primary: {
nbits: 4096
},
subkeys: []
};
kbpgp.KeyManager.generate(opts, some_callback_function);
If you pass an ASP object, as described above, you can use it to cancel your process.
kbpgp.KeyManager.generate(opts, some_callback_function);
// oh, heck, let's give up if it takes more than a second
setTimeout((function() {
my_asp.canceler.cancel();
}), 1000);
In the above example, if the generation has not completed within one second, work will halt and some_callback_function
will immediately get called with err, null
.
Security for the masses
The steps to encrypt, sign, or both are all the same in kbpgp. The only difference is what KeyManagers you'll need. To sign something, you'll need a KeyManager containing a private key. And to encrypt something, you'll need one containing the public key of the recipient. If your KeyManagers contain subkeys, kbpgp will automatically use the appropriate ones.
Assumption: we have a KeyManager instance, chuck
, for the recipient.
var params = {
msg: "Chuck chucky, bo-bucky!",
encrypt_for: chuck
};
kbpgp.box(params, function(err, result_string, result_buffer) {
console.log(err, result_armored_string, result_raw_buffer);
});
Along the same lines, it's easy to sign a cleartext message. Just provide a sign_with
KeyManager but leave off the encrypt_for
.
var params = {
msg: "Here is my manifesto",
sign_with: alice
};
kbpgp.box (params, function(err, result_string, result_buffer) {
console.log(err, result_string, result_buffer);
});
Assumption: we also have a KeyManager instance, alice
, for the sender.
var params = {
msg: "Chuck chucky, bo-bucky! This is Alice here!",
encrypt_for: chuck,
sign_with: alice
};
kbpgp.box (params, function(err, result_string, result_buffer) {
console.log(err, result_string, result_buffer);
});
kbpgp can take Node.js Buffers as input, instead of strings. The following reads a .png file and writes a new encrypted copy of it. For more info, check out the kbpgp buffers documentation.
var kbpgp = require('kbpgp');
var fs = require('fs');
var buffer = fs.readFileSync('dirty_deeds.png');
var params = {
msg: buffer,
encrypt_for: chuck,
sign_with: alice
};
kbpgp.box (params, function(err, result_string, result_buffer) {
fs.writeFileSync('dirty_deeds.encrypted', result_buffer);
});
Buffers are available in the browser, too, for doing HTML5 things with files. kbpgp.Buffer
provides a browser-implementation that matches Node.js's.
Most kbpgp function can take a kbpgp.ASP
object, which is used to monitor progress and check for cancelation requests.
var asp = new kbpgp.ASP({
progress_hook: function(info) {
console.log("progress...", info);
}
});
var params = {
msg: "a secret not worth waiting for",
encrypt_for: chuck,
asp: asp
};
kbpgp.box(params, function(err, result_string, result_buffer) {
console.log("Done!", err, result_string, result_buffer);
});
// sometime before it's done
asp.canceler().cancel();
Decrypting and verifying are slightly more complicated than encrypting or signing, because often, you don't know ahead of time which KeyManagers are required. For PGP messages that are signed and encrypted, you only know which verification key is needed after a successful decryption. Also, messages in PGP can be encrypted for multiple receivers, and any given receiver might only have access to one of many possible decryption keys.
In kbpgp, the unbox
function handles the nitty-gritty of decryption and verification. You need to pass it a PGP message (encrypted, signed or both), and also a way to fetch keys midstream --- a kbpgp.KeyFetcher
object. You can use one of ours out-of-the-box or subclass your own (say, if you want to fetch keys from your server).
The first example of a KeyFetcher we'll consider is a KeyRing --- a object that you can load ahead of time with a bunch of KeyManagers.
var ring = new kbpgp.keyring.KeyRing();
var kms = [ alice, bob, charlie ];
for (var i in kms) {
ring.add_key_manager(kms[i]);
}
For convenience, the KeyManager
class also implements the KeyFetcher interface. If you know ahead of time that you'll only need one KeyManager in a decryption/verification, then you can use it as a KeyFetcher.
Decrypt and verify via the unbox
function. Pass the message, the KeyFetcher (like ring
above), an ASP if you intend to cancel or monitor progress, and a callback to fire when done:
var ring = new kbpgp.keyring.KeyRing;
var kms = [alice, bob, charlie];
var pgp_msg = "---- BEGIN PGP MESSAGE ----- ....";
var asp = /* as in Encryption ... */;
for (var i in kms) {
ring.add_key_manager(kms[i]);
}
kbpgp.unbox({keyfetch: ring, armored: pgp_msg, asp }, function(err, literals) {
if (err != null) {
return console.log("Problem: " + err);
} else {
console.log("decrypted message");
console.log(literals[0].toString());
var ds = km = null;
ds = literals[0].get_data_signer();
if (ds) { km = ds.get_key_manager(); }
if (km) {
console.log("Signed by PGP fingerprint");
console.log(km.get_pgp_fingerprint().toString('hex'));
}
}
});
unbox
calls back with two arguments: an Error if something went wrong, and an array of Literals
if not. Literal
objects support the toString(enc)
and toBuffer()
methods. The former call takes an optional parameter which is an encoding; if none is supplied, kbpgp will use the encoding specified in the PGP message; you can specify 'utf8', 'ascii', 'binary', 'base64' or 'hex' if you want to override that encoding.
This example shows that unbox
handles both decryption and verification. To check if parts of the message were signed, make a get_data_signer
call on each Literal
in the message. Note that the same KeyManager that you loaded into your KeyFetcher shows up here. So if you augment that KeyManager with custom fields, they will be available here.
In a more general decryption/verification scenario, you might need to fetch the appropriate decryption and/or verification keys from secondary or remote storage. In this situation, you shouldn't use the KeyRing described above, but should instead provide a custom KeyFetcher.
All usable KeyFetchers must implement one method: fetch
. Given several PGP key IDs, and a flag specifying which operation is requested, the fetch method should call back with a KeyManager
, if it could find one.
fetch(ids,ops,cb)
is called with three arguments:
- ids --- An array of Buffers, each one containing a 64-bit ID of a PGP key. These keys might refer to subkeys, which are often employed in encrypting and signing messages.
- ops --- Which crypto options are required of this key; a bitwise OR of constants from
kbpgp.const.ops
, which are:- encrypt : 0x1
- decrypt : 0x2
- verify : 0x4
- sign : 0x8
- cb --- A callback that when done, calls back with a triple:
(err,km,i)
- err is an Error explaining what went wrong, or
null
on success. - km is, in the case of success, a KeyManager that meets the given requirements
- i is, in the case of success, an integer indiciating which of the keys was found in the lookup. If
0
is returned here, thenids[0]
is the 64-bit ID of a key insidekm
.
- err is an Error explaining what went wrong, or
In most of the examples, we've been dealing with string plaintexts and ciphertexts. Of course, sometimes you want to read and write files and convert to interesting strings such as hex or base64.
Recall when we were encrypting, we expected a string for the message:
var params = {
msg: "Chuck chucky, bo-bucky!",
encrypt_for: chuck // a KeyManager instance
};
In Node.js we can pass a Node.js Buffer instead. This could come from a file. Keep in mind this file's buffer and output need to fit easily in memory. (For arbitrarily large files, streams will come soon in kbpgp's future.)
fs.readFile('foo.png', function(err, buff) {
var params = {
msg: buff,
encrypt_for: chuck
};
});
In the browser, we have a similar Buffer available, kbpgp.Buffer
. It behaves exactly the same as a Node.js buffer, thanks to [native-buffer-browserify]
// using a string as a Buffer, in the browser
var params = {
msg: kbpgp.Buffer.from("Chuck chucky, bo-bucky!"),
encrypt_for: chuck
};
kbpgp's burn function calls back with both a result_string (armored, when encrypting or signing), and a result_buffer (just raw binary data). The latter is either a Node.js Buffer, as discussed above, or, in the browser, a kbpgp.Buffer
.
kbpgp.burn(params, function(err, result_string, result_buffer) {
console.log(result_buffer.toString('hex'));
console.log(result_buffer.toString('base64'));
// etc...
// ...these work in both the browser and Node.js
});
If you want to support file processing in the browser, you can use an HTML5 FileReader
and convert a file's contents to a Buffer, right in the client side. Depending on the browser, you'll have memory constraints.
var f = some_html_5_file_object;
var r = new FileReader(); // modern browsers have this
r.readAsBinaryString(f);
r.onloadend = function(file) {
var buffer = kbpgp.Buffer.from(r.result);
// ... now process it using kbpgp
};
const fs = require('fs');
const openpgp = require('openpgp');
const async = require('async');
const encryptFuntion = () => {
async.waterfall([
(a_cb) => {
fs.readFile('pub.asc', 'utf8', (err, pubkey) => {
if (!err) {
a_cb(null, pubkey);
} else {
a_cb(err);
}
});
},
(pubkey, a_cb) => {
fs.readFile('priv.asc', 'utf8', (err, privkey) => {
if (!err) {
const passphrase = `yourPassphrase`;
let privKeyObj = (await openpgp.key.readArmored(privkey)).keys[0];
await privKeyObj.decrypt(passphrase);
a_cb(null, pubkey, privKeyObj);
} else {
a_cb(err);
}
});
},
(pubkey, privKeyObj, a_cb) => {
fs.readFile('test.txt', 'utf8', (err, rawMessage) => {
if (!err) {
a_cb(null, pubkey, privKeyObj, rawMessage);
} else {
a_cb(err);
}
});
},
(pubkey, privKeyObj, message, a_cb) => {
let options = {
message: openpgp.message.fromText(message),
publicKeys: (await openpgp.key.readArmored(pubkey)).keys,
privateKeys: [privKeyObj],
armor: false
};
openpgp.encrypt(options)
.then((ciphertext) => {
a_cb(null, ciphertext);
});
}
], (err, ciphertext) => {
if (!err) {
fs.writeFile('test.txt.gpg', ciphertext.data, (err) => {
if (!err) {
console.log('Created GPG file!');
} else {
console.log(err);
}
});
} else {
console.log(err);
}
});
};
module.exports = {
encryptFuntion
};
const fs = require('fs');
const openpgp = require('openpgp');
const async = require('async');
const decryptFuntion = () => {
async.waterfall([
(a_cb) => {
fs.readFile('test.txt.gpg', (err, encrypted) => {
if (!err) {
a_cb(null, encrypted);
} else {
a_cb(err);
}
});
},
(encrypted, a_cb) => {
fs.readFile('pub.asc', 'utf8', (err, pubkey) => {
if (!err) {
a_cb(null, encrypted, pubkey);
} else {
a_cb(err);
}
});
},
(encrypted, pubkey, a_cb) => {
fs.readFile('priv.asc', 'utf8', (err, privkey) => {
if (!err) {
const passphrase = `yourPassphrase`;
let privKeyObj = (await openpgp.key.readArmored(privkey)).keys[0];
await privKeyObj.decrypt(passphrase);
a_cb(null, encrypted, pubkey, privKeyObj);
} else {
a_cb(err);
}
});
},
(encrypted, pubkey, privKeyObj, a_cb) => {
let options = {
message: await openpgp.message.readArmored(encrypted),
publicKeys: (await openpgp.key.readArmored(pubkey)).keys,
privateKeys: [privKeyObj]
};
openpgp.decrypt(options)
.then((plaintext) => {
a_cb(null, plaintext);
});
}
], (err, plaintext) => {
if (!err) {
fs.writeFile('test-decrypted.txt', plaintext, 'utf8', (err) => {
if (!err) {
console.log('Created txt file!');
} else {
console.log(err);
}
});
} else {
console.log(err);
}
});
};
module.exports = {
decryptFuntion
};
- FreeCodeCamp - Handling Front End Encryption
- The Wikipedia
- OpenpgpGuide
- etlworks support
- openpgpjs
- kbpgp.js
- For the documentation of
openpgp.stream
, see the documentation of the web-stream-tools dependency.