UNPKG

26 kBMarkdownView Raw
1OpenPGP.js [![BrowserStack Status](https://automate.browserstack.com/badge.svg?badge_key=eEkxVVM1TytwOGJNWEdnTjk4Y0VNUUNyR3pXcEtJUGRXOVFBRjVNT1JpUT0tLTZYUlZaMWdtQWs4Z0ROS3grRXc2bFE9PQ==--4a9cac0d6ea009d81aff66de0dbb239edd1aef3c)](https://automate.browserstack.com/public-build/eEkxVVM1TytwOGJNWEdnTjk4Y0VNUUNyR3pXcEtJUGRXOVFBRjVNT1JpUT0tLTZYUlZaMWdtQWs4Z0ROS3grRXc2bFE9PQ==--4a9cac0d6ea009d81aff66de0dbb239edd1aef3c) [![Join the chat on Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/openpgpjs/openpgpjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
2==========
3
4[OpenPGP.js](https://openpgpjs.org/) is a JavaScript implementation of the OpenPGP protocol. It implements [RFC4880](https://tools.ietf.org/html/rfc4880) and parts of [RFC4880bis](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10).
5
6**Table of Contents**
7
8- [OpenPGP.js](#openpgpjs)
9 - [Platform Support](#platform-support)
10 - [Performance](#performance)
11 - [Getting started](#getting-started)
12 - [Node.js](#nodejs)
13 - [Browser (webpack)](#browser-webpack)
14 - [Browser (plain files)](#browser-plain-files)
15 - [Examples](#examples)
16 - [Encrypt and decrypt *Uint8Array* data with a password](#encrypt-and-decrypt-uint8array-data-with-a-password)
17 - [Encrypt and decrypt *String* data with PGP keys](#encrypt-and-decrypt-string-data-with-pgp-keys)
18 - [Encrypt with compression](#encrypt-with-compression)
19 - [Streaming encrypt *Uint8Array* data with a password](#streaming-encrypt-uint8array-data-with-a-password)
20 - [Streaming encrypt and decrypt *String* data with PGP keys](#streaming-encrypt-and-decrypt-string-data-with-pgp-keys)
21 - [Generate new key pair](#generate-new-key-pair)
22 - [Revoke a key](#revoke-a-key)
23 - [Sign and verify cleartext messages](#sign-and-verify-cleartext-messages)
24 - [Create and verify *detached* signatures](#create-and-verify-detached-signatures)
25 - [Streaming sign and verify *Uint8Array* data](#streaming-sign-and-verify-uint8array-data)
26 - [Documentation](#documentation)
27 - [Security Audit](#security-audit)
28 - [Security recommendations](#security-recommendations)
29 - [Development](#development)
30 - [How do I get involved?](#how-do-i-get-involved)
31 - [License](#license)
32
33### Platform Support
34
35* The `dist/openpgp.min.js` bundle works well with recent versions of Chrome, Firefox, Safari and Edge.
36
37* The `dist/node/openpgp.min.js` bundle works well in Node.js. It is used by default when you `require('openpgp')` in Node.js.
38
39* Currently, Chrome, Safari and Edge have partial implementations of the
40[Streams specification](https://streams.spec.whatwg.org/), and Firefox
41has a partial implementation behind feature flags. Chrome is the only
42browser that implements `TransformStream`s, which we need, so we include
43a [polyfill](https://github.com/MattiasBuelens/web-streams-polyfill) for
44all other browsers. Please note that in those browsers, the global
45`ReadableStream` property gets overwritten with the polyfill version if
46it exists. In some edge cases, you might need to use the native
47`ReadableStream` (for example when using it to create a `Response`
48object), in which case you should store a reference to it before loading
49OpenPGP.js. There is also the
50[web-streams-adapter](https://github.com/MattiasBuelens/web-streams-adapter)
51library to convert back and forth between them.
52
53### Performance
54
55* Version 3.0.0 of the library introduces support for public-key cryptography using [elliptic curves](https://wiki.gnupg.org/ECC). We use native implementations on browsers and Node.js when available. Elliptic curve cryptography provides stronger security per bits of key, which allows for much faster operations. Currently the following curves are supported:
56
57 | Curve | Encryption | Signature | NodeCrypto | WebCrypto | Constant-Time |
58 |:---------------:|:----------:|:---------:|:----------:|:---------:|:-----------------:|
59 | curve25519 | ECDH | N/A | No | No | Algorithmically** |
60 | ed25519 | N/A | EdDSA | No | No | Algorithmically** |
61 | p256 | ECDH | ECDSA | Yes* | Yes* | If native*** |
62 | p384 | ECDH | ECDSA | Yes* | Yes* | If native*** |
63 | p521 | ECDH | ECDSA | Yes* | Yes* | If native*** |
64 | brainpoolP256r1 | ECDH | ECDSA | Yes* | No | If native*** |
65 | brainpoolP384r1 | ECDH | ECDSA | Yes* | No | If native*** |
66 | brainpoolP512r1 | ECDH | ECDSA | Yes* | No | If native*** |
67 | secp256k1 | ECDH | ECDSA | Yes* | No | If native*** |
68
69 \* when available
70 \** the curve25519 and ed25519 implementations are algorithmically constant-time, but may not be constant-time after optimizations of the JavaScript compiler
71 \*** these curves are only constant-time if the underlying native implementation is available and constant-time
72
73* Version 2.x of the library has been built from the ground up with Uint8Arrays. This allows for much better performance and memory usage than strings.
74
75* If the user's browser supports [native WebCrypto](https://caniuse.com/#feat=cryptography) via the `window.crypto.subtle` API, this will be used. Under Node.js the native [crypto module](https://nodejs.org/api/crypto.html#crypto_crypto) is used.
76
77* The library implements the [IETF proposal](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10) for authenticated encryption using native AES-EAX, OCB, or GCM. This makes symmetric encryption up to 30x faster on supported platforms. Since the specification has not been finalized and other OpenPGP implementations haven't adopted it yet, the feature is currently behind a flag. **Note: activating this setting can break compatibility with other OpenPGP implementations, and also with future versions of OpenPGP.js. Don't use it with messages you want to store on disk or in a database.** You can enable it by setting `openpgp.config.aeadProtect = true`.
78
79 You can change the AEAD mode by setting one of the following options:
80
81 ```
82 openpgp.config.aeadMode = openpgp.enums.aead.eax // Default, native
83 openpgp.config.aeadMode = openpgp.enums.aead.ocb // Non-native
84 openpgp.config.aeadMode = openpgp.enums.aead.experimentalGcm // **Non-standard**, fastest
85 ```
86
87* For environments that don't provide native crypto, the library falls back to [asm.js](https://caniuse.com/#feat=asmjs) implementations of AES, SHA-1, and SHA-256.
88
89
90### Getting started
91
92#### Node.js
93
94Install OpenPGP.js using npm and save it in your dependencies:
95
96```sh
97npm install --save openpgp
98```
99
100And import it as a CommonJS module:
101
102```js
103const openpgp = require('openpgp');
104```
105
106Or as an ES6 module, from an .mjs file:
107
108```js
109import * as openpgp from 'openpgp';
110```
111
112#### Browser (webpack)
113
114Install OpenPGP.js using npm and save it in your devDependencies:
115
116```sh
117npm install --save-dev openpgp
118```
119
120And import it as an ES6 module:
121
122```js
123import * as openpgp from 'openpgp';
124```
125
126You can also only import the functions you need, as follows:
127
128```js
129import { readMessage, decrypt } from 'openpgp';
130```
131
132Or, if you want to use the lightweight build (which is smaller, and lazily loads non-default curves on demand):
133
134```js
135import * as openpgp from 'openpgp/lightweight';
136```
137
138To test whether the lazy loading works, try to generate a key with a non-standard curve:
139
140```js
141import { generateKey } from 'openpgp/lightweight';
142await generateKey({ curve: 'brainpoolP512r1', userIds: [{ name: 'Test', email: 'test@test.com' }] });
143```
144
145For more examples of how to generate a key, see [Generate new key pair](#generate-new-key-pair). It is recommended to use `curve25519` instead of `brainpoolP512r1` by default.
146
147
148#### Browser (plain files)
149
150Grab `openpgp.min.js` from [unpkg.com/openpgp/dist](https://unpkg.com/openpgp/dist/), and load it in a script tag:
151
152```html
153<script src="openpgp.min.js"></script>
154```
155
156Or, to load OpenPGP.js as an ES6 module, grab `openpgp.min.mjs` from [unpkg.com/openpgp/dist](https://unpkg.com/openpgp/dist/), and import it as follows:
157
158```html
159<script type="module">
160import * as openpgp from './openpgp.min.mjs';
161</script>
162```
163
164To offload cryptographic operations off the main thread, you can implement a Web Worker in your application and load OpenPGP.js from there. For an example Worker implementation, see `test/worker/worker_example.js`.
165
166### Examples
167
168Here are some examples of how to use OpenPGP.js v5. For more elaborate examples and working code, please check out the [public API unit tests](https://github.com/openpgpjs/openpgpjs/blob/master/test/general/openpgp.js). If you're upgrading from v4 it might help to check out the [changelog](https://github.com/openpgpjs/openpgpjs/wiki/V5-Changelog) and [documentation](https://github.com/openpgpjs/openpgpjs#documentation).
169
170#### Encrypt and decrypt *Uint8Array* data with a password
171
172Encryption will use the algorithm specified in config.encryptionCipher (defaults to aes256), and decryption will use the algorithm used for encryption.
173
174```js
175(async () => {
176 const message = openpgp.Message.fromBinary(new Uint8Array([0x01, 0x01, 0x01]));
177 const encrypted = await openpgp.encrypt({
178 message, // input as Message object
179 passwords: ['secret stuff'], // multiple passwords possible
180 armor: false // don't ASCII armor (for Uint8Array output)
181 });
182 console.log(encrypted); // Uint8Array
183
184 const encryptedMessage = await openpgp.readMessage({
185 binaryMessage: encrypted // parse encrypted bytes
186 });
187 const { data: decrypted } = await openpgp.decrypt({
188 message: encryptedMessage,
189 passwords: ['secret stuff'], // decrypt with password
190 format: 'binary' // output as Uint8Array
191 });
192 console.log(decrypted); // Uint8Array([0x01, 0x01, 0x01])
193})();
194```
195
196#### Encrypt and decrypt *String* data with PGP keys
197
198Encryption 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.
199
200```js
201const openpgp = require('openpgp'); // use as CommonJS, AMD, ES6 module or via window.openpgp
202
203(async () => {
204 // put keys in backtick (``) to avoid errors caused by spaces or tabs
205 const publicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK-----
206...
207-----END PGP PUBLIC KEY BLOCK-----`;
208 const privateKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK-----
209...
210-----END PGP PRIVATE KEY BLOCK-----`; // encrypted private key
211 const passphrase = `yourPassphrase`; // what the private key is encrypted with
212
213 const publicKey = await openpgp.readKey({ armoredKey: publicKeyArmored });
214
215 const privateKey = await openpgp.readKey({ armoredKey: privateKeyArmored });
216 await privateKey.decrypt(passphrase);
217
218 const encrypted = await openpgp.encrypt({
219 message: openpgp.Message.fromText('Hello, World!'), // input as Message object
220 publicKeys: publicKey, // for encryption
221 privateKeys: privateKey // for signing (optional)
222 });
223 console.log(encrypted); // '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----'
224
225 const message = await openpgp.readMessage({
226 armoredMessage: encrypted // parse armored message
227 });
228 const { data: decrypted } = await openpgp.decrypt({
229 message,
230 publicKeys: publicKey, // for verification (optional)
231 privateKeys: privateKey // for decryption
232 });
233 console.log(decrypted); // 'Hello, World!'
234})();
235```
236
237Encrypt with multiple public keys:
238
239```js
240(async () => {
241 const publicKeysArmored = [
242 `-----BEGIN PGP PUBLIC KEY BLOCK-----
243...
244-----END PGP PUBLIC KEY BLOCK-----`,
245 `-----BEGIN PGP PUBLIC KEY BLOCK-----
246...
247-----END PGP PUBLIC KEY BLOCK-----`
248 ];
249 const privateKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK-----
250...
251-----END PGP PRIVATE KEY BLOCK-----`; // encrypted private key
252 const passphrase = `yourPassphrase`; // what the private key is encrypted with
253 const message = 'Hello, World!';
254
255 const publicKeys = await Promise.all(publicKeysArmored.map(armoredKey => openpgp.readKey({ armoredKey })));
256
257 const privateKey = await openpgp.readKey({ armoredKey: privateKeyArmored });
258 await privateKey.decrypt(passphrase)
259
260 const message = openpgp.Message.fromText(message);
261 const encrypted = await openpgp.encrypt({
262 message:, // input as Message object
263 publicKeys, // for encryption
264 privateKeys: privateKey // for signing (optional)
265 });
266 console.log(encrypted); // '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----'
267})();
268```
269
270#### Encrypt with compression
271
272By default, `encrypt` will not use any compression. It's possible to override that behavior in two ways:
273
274Either set the `compression` parameter in the options object when calling `encrypt`.
275
276```js
277(async () => {
278 const message = openpgp.Message.fromBinary(new Uint8Array([0x01, 0x02, 0x03])); // or .fromText('string')
279 const encrypted = await openpgp.encrypt({
280 message,
281 passwords: ['secret stuff'], // multiple passwords possible
282 compression: openpgp.enums.compression.zip // compress the data with zip
283 });
284})();
285```
286
287Or, override the config to enable compression:
288
289```js
290openpgp.config.compression = openpgp.enums.compression.zlib;
291```
292
293Where the value can be any of:
294 * `openpgp.enums.compression.zip`
295 * `openpgp.enums.compression.zlib`
296
297
298#### Streaming encrypt *Uint8Array* data with a password
299
300```js
301(async () => {
302 const readableStream = new openpgp.stream.ReadableStream({
303 start(controller) {
304 controller.enqueue(new Uint8Array([0x01, 0x02, 0x03]));
305 controller.close();
306 }
307 });
308
309 const message = openpgp.Message.fromBinary(readableStream);
310 const encrypted = await openpgp.encrypt({
311 message, // input as Message object
312 passwords: ['secret stuff'], // multiple passwords possible
313 armor: false // don't ASCII armor (for Uint8Array output)
314 });
315 console.log(encrypted); // raw encrypted packets as ReadableStream<Uint8Array>
316
317 // Either pipe the above stream somewhere, pass it to another function,
318 // or read it manually as follows:
319 const reader = openpgp.stream.getReader(encrypted);
320 while (true) {
321 const { done, value } = await reader.read();
322 if (done) break;
323 console.log('new chunk:', value); // Uint8Array
324 }
325
326 // Or, in Node.js, you can pipe the above stream as follows:
327 const nodeStream = openpgp.stream.webToNode(encrypted);
328 nodeStream.pipe(nodeWritableStream);
329})();
330```
331
332For more information on creating ReadableStreams, see [the MDN Documentation on `new
333ReadableStream()`](https://developer.mozilla.org/docs/Web/API/ReadableStream/ReadableStream).
334For more information on reading streams using `openpgp.stream`, see the documentation of
335[the web-stream-tools dependency](https://openpgpjs.org/web-stream-tools/), particularly
336its [Reader class](https://openpgpjs.org/web-stream-tools/Reader.html).
337
338
339#### Streaming encrypt and decrypt *String* data with PGP keys
340
341```js
342(async () => {
343 const publicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK-----
344...
345-----END PGP PUBLIC KEY BLOCK-----`; // Public key
346 const [privateKeyArmored] = `-----BEGIN PGP PRIVATE KEY BLOCK-----
347...
348-----END PGP PRIVATE KEY BLOCK-----`; // Encrypted private key
349 const passphrase = `yourPassphrase`; // Password that private key is encrypted with
350
351 const publicKey = await openpgp.readKey({ armoredKey: publicKeyArmored });
352
353 const privateKey = await openpgp.readKey({ armoredKey: privateKeyArmored });
354 await privateKey.decrypt(passphrase);
355
356 const readableStream = new openpgp.stream.ReadableStream({
357 start(controller) {
358 controller.enqueue('Hello, world!');
359 controller.close();
360 }
361 });
362
363 const encrypted = await openpgp.encrypt({
364 message: openpgp.Message.fromText(readableStream), // input as Message object
365 publicKeys: publicKey, // for encryption
366 privateKeys: privateKey // for signing (optional)
367 });
368 console.log(encrypted); // ReadableStream containing '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----'
369
370 const message = await openpgp.readMessage({
371 armoredMessage: encrypted // parse armored message
372 });
373 const decrypted = await openpgp.decrypt({
374 message,
375 publicKeys: publicKey, // for verification (optional)
376 privateKeys: privateKey // for decryption
377 });
378 const plaintext = await openpgp.stream.readToEnd(decrypted.data);
379 console.log(plaintext); // 'Hello, World!'
380})();
381```
382
383
384#### Generate new key pair
385
386ECC keys (smaller and faster to generate):
387
388Possible values for `curve` are: `curve25519`, `ed25519`, `p256`, `p384`, `p521`,
389`brainpoolP256r1`, `brainpoolP384r1`, `brainpoolP512r1`, and `secp256k1`.
390Note that both the `curve25519` and `ed25519` options generate a primary key for signing using Ed25519
391and a subkey for encryption using Curve25519.
392
393```js
394(async () => {
395 const { privateKeyArmored, publicKeyArmored, revocationCertificate } = await openpgp.generateKey({
396 type: 'ecc', // Type of the key, defaults to ECC
397 curve: 'curve25519', // ECC curve name, defaults to curve25519
398 userIds: [{ name: 'Jon Smith', email: 'jon@example.com' }], // you can pass multiple user IDs
399 passphrase: 'super long and hard to guess secret' // protects the private key
400 });
401
402 console.log(privateKeyArmored); // '-----BEGIN PGP PRIVATE KEY BLOCK ... '
403 console.log(publicKeyArmored); // '-----BEGIN PGP PUBLIC KEY BLOCK ... '
404 console.log(revocationCertificate); // '-----BEGIN PGP PUBLIC KEY BLOCK ... '
405})();
406```
407
408RSA keys (increased compatibility):
409
410```js
411(async () => {
412 const key = await openpgp.generateKey({
413 type: 'rsa', // Type of the key
414 rsaBits: 4096, // RSA key size (defaults to 4096 bits)
415 userIds: [{ name: 'Jon Smith', email: 'jon@example.com' }], // you can pass multiple user IDs
416 passphrase: 'super long and hard to guess secret' // protects the private key
417 });
418})();
419```
420
421#### Revoke a key
422
423Using a revocation certificate:
424```js
425(async () => {
426 const { publicKeyArmored: revokedKeyArmored } = await openpgp.revokeKey({
427 key: await openpgp.readKey({ armoredKey: publicKeyArmored }),
428 revocationCertificate
429 });
430 console.log(revokedKeyArmored); // '-----BEGIN PGP PUBLIC KEY BLOCK ... '
431})();
432```
433
434Using the private key:
435```js
436(async () => {
437 const { publicKeyArmored, publicKey } = await openpgp.revokeKey({
438 key: await openpgp.readKey({ armoredKey: privateKeyArmored })
439 });
440})();
441```
442
443#### Sign and verify cleartext messages
444
445```js
446(async () => {
447 const publicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK-----
448...
449-----END PGP PUBLIC KEY BLOCK-----`;
450 const privateKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK-----
451...
452-----END PGP PRIVATE KEY BLOCK-----`; // encrypted private key
453 const passphrase = `yourPassphrase`; // what the private key is encrypted with
454
455 const publicKey = await openpgp.readKey({ armoredKey: publicKeyArmored });
456
457 const privateKey = await openpgp.readKey({ armoredKey: privateKeyArmored });
458 await privateKey.decrypt(passphrase);
459
460 const unsignedMessage = openpgp.CleartextMessage.fromText('Hello, World!');
461 const cleartextMessage = await openpgp.sign({
462 message: unsignedMessage, // CleartextMessage or Message object
463 privateKeys: privateKey // for signing
464 });
465 console.log(cleartextMessage); // '-----BEGIN PGP SIGNED MESSAGE ... END PGP SIGNATURE-----'
466
467 const signedMessage = await openpgp.readCleartextMessage({
468 cleartextMessage // parse armored message
469 });
470 const verified = await openpgp.verify({
471 message: signedMessage,
472 publicKeys: publicKey // for verification
473 });
474 const { valid } = verified.signatures[0];
475 if (valid) {
476 console.log('signed by key id ' + verified.signatures[0].keyid.toHex());
477 } else {
478 throw new Error('signature could not be verified');
479 }
480})();
481```
482
483#### Create and verify *detached* signatures
484
485```js
486(async () => {
487 const publicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK-----
488...
489-----END PGP PUBLIC KEY BLOCK-----`;
490 const privateKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK-----
491...
492-----END PGP PRIVATE KEY BLOCK-----`; // encrypted private key
493 const passphrase = `yourPassphrase`; // what the private key is encrypted with
494
495 const publicKey = await openpgp.readKey({ armoredKey: publicKeyArmored });
496
497 const privateKey = await openpgp.readKey({ armoredKey: privateKeyArmored });
498 await privateKey.decrypt(passphrase);
499
500 const cleartextMessage = openpgp.CleartextMessage.fromText('Hello, World!');
501 const detachedSignature = await openpgp.sign({
502 message: cleartextMessage, // CleartextMessage or Message object
503 privateKeys: privateKey, // for signing
504 detached: true
505 });
506 console.log(detachedSignature);
507
508 const signature = await openpgp.readSignature({
509 armoredSignature: detachedSignature // parse detached signature
510 });
511 const verified = await openpgp.verify({
512 message: cleartextMessage, // CleartextMessage or Message object
513 signature,
514 publicKeys: publicKey // for verification
515 });
516 const { valid } = verified.signatures[0];
517 if (valid) {
518 console.log('signed by key id ' + verified.signatures[0].keyid.toHex());
519 } else {
520 throw new Error('signature could not be verified');
521 }
522})();
523```
524
525#### Streaming sign and verify *Uint8Array* data
526
527```js
528(async () => {
529 var readableStream = new openpgp.stream.ReadableStream({
530 start(controller) {
531 controller.enqueue(new Uint8Array([0x01, 0x02, 0x03]));
532 controller.close();
533 }
534 });
535
536 const publicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK-----
537...
538-----END PGP PUBLIC KEY BLOCK-----`;
539 const privateKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK-----
540...
541-----END PGP PRIVATE KEY BLOCK-----`; // encrypted private key
542 const passphrase = `yourPassphrase`; // what the private key is encrypted with
543
544 const privateKey = await openpgp.readKey({ armoredKey: privateKeyArmored });
545 await privateKey.decrypt(passphrase);
546
547 const message = openpgp.Message.fromBinary(readableStream); // or .fromText(readableStream: ReadableStream<String>)
548 const signatureArmored = await openpgp.sign({
549 message,
550 privateKeys: privateKey // for signing
551 });
552 console.log(signatureArmored); // ReadableStream containing '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----'
553
554 const verified = await openpgp.verify({
555 message: await openpgp.readMessage({ armoredMessage: signatureArmored }), // parse armored signature
556 publicKeys: await openpgp.readKey({ armoredKey: publicKeyArmored }) // for verification
557 });
558
559 await openpgp.stream.readToEnd(verified.data);
560 // Note: you *have* to read `verified.data` in some way or other,
561 // even if you don't need it, as that is what triggers the
562 // verification of the data.
563
564 const { valid } = verified.signatures[0];
565 if (valid) {
566 console.log('signed by key id ' + verified.signatures[0].keyid.toHex());
567 } else {
568 throw new Error('signature could not be verified');
569 }
570})();
571```
572
573### Documentation
574
575The full documentation is available at [openpgpjs.org](https://openpgpjs.org/openpgpjs/).
576
577### Security Audit
578
579To date the OpenPGP.js code base has undergone two complete security audits from [Cure53](https://cure53.de). The first audit's report has been published [here](https://github.com/openpgpjs/openpgpjs/wiki/Cure53-security-audit).
580
581### Security recommendations
582
583It should be noted that js crypto apps deployed via regular web hosting (a.k.a. [**host-based security**](https://www.schneier.com/blog/archives/2012/08/cryptocat.html)) provide users with less security than installable apps with auditable static versions. Installable apps can be deployed as a [Firefox](https://developer.mozilla.org/en-US/Marketplace/Options/Packaged_apps) or [Chrome](https://developer.chrome.com/apps/about_apps.html) packaged app. These apps are basically signed zip files and their runtimes typically enforce a strict [Content Security Policy (CSP)](https://www.html5rocks.com/en/tutorials/security/content-security-policy/) to protect users against [XSS](https://en.wikipedia.org/wiki/Cross-site_scripting). This [blogpost](https://tankredhase.com/2014/04/13/heartbleed-and-javascript-crypto/) explains the trust model of the web quite well.
584
585It is also recommended to set a strong passphrase that protects the user's private key on disk.
586
587### Development
588
589To create your own build of the library, just run the following command after cloning the git repo. This will download all dependencies, run the tests and create a minified bundle under `dist/openpgp.min.js` to use in your project:
590
591 npm install && npm test
592
593For debugging browser errors, you can run `npm start` and open [`http://localhost:8080/test/unittests.html`](http://localhost:8080/test/unittests.html) in a browser, or run the following command:
594
595 npm run browsertest
596
597### How do I get involved?
598
599You want to help, great! It's probably best to send us a message on [Gitter](https://gitter.im/openpgpjs/openpgpjs) before you start your undertaking, to make sure nobody else is working on it, and so we can discuss the best course of action. Other than that, just go ahead and fork our repo, make your changes and send us a pull request! :)
600
601### License
602
603[GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0.en.html) (3.0 or any later version). Please take a look at the [LICENSE](LICENSE) file for more information.