1 The crypto package interfaces the apps with the underlying OpenPGP crypto libs of pmcrypto and OpenPGP.js, as well as the browser's native WebCrypto API.
3 > **`pmcrypto` no longer needs to be directly imported by the apps and other packages, you should always use `@proton/crypto` instead.**
7 The utils functions that `pmcrypto` exported (e.g. `arrayToBinaryString`) are now accessible under `@proton/crypto/lib/utils`.
9 Crypto-related functions runnable in web workers are handled by the `CryptoProxy`, which is initialized together with the apps (see [this section](web-worker-integration) for more info on the setup).
14 <summary><b>Importing/exporting public and private keys</b></summary>
16 #### Importing/exporting public and private keys
18 `OpenPGPKey` objects have been replaced by `PrivateKeyReference` and `PublicKeyReference` ones, as key material stored away from main thread.
23 const recipientPublicKey = await CryptoProxy.importPublicKey({ armoredKey: '...' }); // or `binaryKey`
24 // To import a private key, the passphrase must be known
25 // (otherwise, either wait for it to be available, or import as public key)
26 const senderPrivateKey = await CryptoProxy.importPrivateKey({
27 armoredKey: '...', // or `binaryKey`
28 passphrase: 'key decryption passphrase', // If the key is expected to be already decrypted (rare, but it can happen for keys uploaded by the user), you have to pass `passphrase: null`.
32 To export keys to be able to transfer them:
35 // on public key export, if a private key is given, only the public key material is extracted and serialized
36 const armoredPublicKey = await CryptoProxy.exportPublicKey({
37 key: senderPrivateKey,
38 format: 'armored', // or 'binary'
40 // on private key export, the key will be encrypted before serialization, using the given `passhrapse`
41 const armoredPrivateKey = await CryptoProxy.exportPrivateKey({
42 key: senderPrivateKey,
43 passphrase: 'key encryption passphrase',
44 format: 'armored', // or 'binary'
48 To delete the keys from memory once they are no longer needed:
51 // invalidate a specific key reference
52 await CryptoProxy.clearKey({ key: senderPrivateKey }); // after this, passing `senderPrivateKey` to the `CryptoProxy` will result in an error
54 // invalidate all keys previously imported and generated using the `CryptoProxy`
55 await CryptoProxy.clearKeyStore();
61 <summary><b>Encrypt/sign and decrypt/verify string or binary data using keys</b></summary>
63 #### Encrypt/sign and decrypt/verify string or binary data using keys
68 // import the required keys
69 const senderPublicKey = await CryptoProxy.importPublicKey(...);
70 const recipientPrivateKey = await CryptoProxy.importPrivateKey(...);
73 message: armoredMessage,
74 signature: armoredSignature,
75 encryptedSignature: armoredEncryptedSignature,
76 } = await CryptoProxy.encryptMessage({
77 textData: 'text data to encrypt', // or `binaryData` for Uint8Arrays
78 encryptionKeys: recipientPublicKey, // and/or `passwords`
79 signingKeys: senderPrivateKey,
81 format: 'armored' // or 'binary' to output a binary message and signature
84 // share `armoredMessage`
87 To decrypt and verify:
90 // import the required keys
91 const senderPublicKey = await CryptoProxy.importPublicKey(...);
92 const recipientPrivateKey = await CryptoProxy.importPrivateKey(...);
94 const { data: decryptedData, verified, verificationErrors } = await CryptoProxy.decryptMessage({
95 armoredMessage, // or `binaryMessage`
96 armoredEncryptedSignature, // or 'binaryEncryptedSignature'/'armoredSignature'/'binarySignature'
97 decryptionKeys: recipientPrivateKey // and/or 'passwords'/'sessionKey'
98 verificationKeys: senderPublicKey
101 if (verified === VERIFICATION_STATUS.SIGNED_AND_VALID) {
102 console.log(decryptedData)
103 } else if (verified === VERIFICATION_STATUS.SIGNED_AND_INVALID) {
104 console.log(verificationErrors)
111 <summary><b>Encrypt/decrypt using the session key</b></summary>
113 #### Encrypt/decrypt using the session key directly
116 // First generate the session key
117 const sessionKey = await CryptoProxy.generateSessionKey({ recipientKeys: recipientPublicKey });
119 // Then encrypt the data with it
120 const { message: armoredMessage } = await CryptoProxy.encryptMessage({
121 textData: 'text data to encrypt', // or `binaryData` for Uint8Arrays
123 encryptionKeys: recipientPublicKey, // and/or `passwords`, used to encrypt the session key
124 signingKeys: senderPrivateKey,
128 To decrypt, you can again provide the session key directly:
131 // Then encrypt the data with it
132 const { data } = await CryptoProxy.decryptMessage({
133 armoredMessage, // or `binaryMessage`
134 sessionKeys: sessionKey,
135 verificationKeys: senderPublicKey,
139 You can also encrypt the session key on its own:
142 const armoredEncryptedSessionKey = await encryptSessionKey({
144 encryptionKeys, // and/or passwords
145 format: 'armored', // or 'binary'
148 // And decrypt it with:
149 const sessionKey = await CryptoProxy.decryptSessionKey({
150 armoredMessage: armoredEncryptedSessionKey, // or `binaryMessage`
151 decryptionsKeys, // or `passwords`
157 ## Web Worker Integration
159 The CryptoProxy redirects crypto request to whatever endpoint is set via `CryptoProxy.setEndpoint`. Only one endpoint can be set at a time. To release an endpoint and possibly set a new one, call `CryptoProxy.releaseEndpoint`.
161 This package implements a worker pool `CryptoWorkerPool` that the apps can use as endpoint, out of the box:
164 import { CryptoWorkerPool } from '@proton/crypto/lib/worker/workerPool';
166 async function setupCryptoWorker() {
167 await CryptoWorkerPool.init(); // CryptoWorkerPool is a singleton
168 CryptoProxy.setEndpoint(
170 (endpoint) => endpoint.destroy() // destroy the CryptoWorkerPool when the CryptoProxy endpoint is released
175 Using workers is necessary since crypto operations are likely to freeze the UI if run in the main thread.
177 However, if you have an existing app-specific worker, you might not need to spawn separate workers, as described below.
179 <!-- ## App-specific workers -->
181 ### Setting up CryptoProxy inside a worker (with separate key store than the main thread)
183 If a custom app worker needs to call the CryptoProxy (even indirectly, to e.g. use `@proton/shared` functions), it can create and use a CryptoApi instance directly, thus avoiding going through a separate worker to resolve the requests:
186 import { Api: CryptoApi } from '@proton/crypto/lib/worker/api'
187 CryptoProxy.setEndpoint(new CryptoApi(), endpoint => endpoint.clearKeyStore());
190 Note that the CryptoApi imports OpenPGP.js, and it should not be used or imported in the main thread, but only inside workers (you might want to use dynamic imports in this sense).
192 The CryptoProxy initialized in this way is totally separate from the CryptoProxy initialized in the main thread, and it will not share key store with it. If you need a shared key store (which is preferable than trasferring keys manually to and from the worker), see the next section.
194 ### Using custom worker as CryptoProxy endpoint for the main thread (with shared key store)
196 To have a single app-specific worker that takes care of some app-specific requests, as well as the CryptoProxy ones from the main thread, it's possible to extend the CryptoApi.
201 // in `customWorker.ts`:
202 import { expose, transferHandlers } from 'comlink';
204 import { CryptoProxy, PrivateKeyReference, PublicKeyReference } from '@proton/crypto';
205 import { Api as CryptoApi } from '@proton/crypto/lib/worker/api';
206 import { workerTransferHandlers } from '@proton/crypto/lib/worker/transferHandlers';
208 class CustomWorkerApi extends CryptoApi {
211 CryptoProxy.setEndpoint(this); // if needed, set endpoint (e.g. for @proton/shared) in the worker itself
214 // decrypt and encrypt to a different key, saving some communication overhead
215 async reEncryptMessage({
220 armoredMessage: string,
221 decryptionKeys: PrivateKeyReference[],
222 encryptionKeys: PublicKeyReference[],
224 const { data: binaryData } = await this.decryptMessage({ armoredMessage, decryptionKeys, format: 'binary' });
225 return this.encryptMessage({ binaryData, encryptionKeys });
229 // set up transfer handlers for the CryptoApi (you might have to set up your own as well)
230 workerTransferHandlers.forEach(({ name, handler }) => transferHandlers.set(name, handler));
231 // initialize underlying crypto libraries
232 CustomWorkerApi.init();
234 expose(CustomWorkerApi);
239 import { wrap, transferHandlers } from 'comlink';
240 import { mainThreadTransferHandlers } from '@proton/crypto/lib/worker/transferHandlers';
241 import { CryptoProxy } from '@proton/crypto';
243 const RemoteCustomWorker = wrap<typeof CustomWorkerApi>(new Worker(new URL('./customWorker.ts', import.meta.url)));
244 // set up transfer handlers for the CryptoApi (you might have to set up your own as well)
245 mainThreadTransferHandlers.forEach(({ name, handler }) => transferHandlers.set(name, handler));
247 async function doStuff() {
249 const customWorkerInstance = await new RemoteCustomWorker();
250 // set it as CryptoProxy endpoint
251 CryptoProxy.setEndpoint(customWorkerInstance);
253 // the CryptoProxy requests will now be directed to your custom worker
254 const oldKey = await CryptoProxy.importPrivateKey(...); // or `customWorkerInstance.importPrivateKey`
255 const newKey = await CryptoProxy.generateKey(...); // or `customWorkerInstance.generateKey`
257 // the custom functions need to be referenced directly, since the CryptoProxy is not aware of them
258 await customWorkerInstance.reEncryptMessage({
259 armoredMessage: '...',
260 decryptionKeys: [oldKey],
261 encryptionKeys: [newKey]
268 Chrome and Firefox are used for tests. With Chrome and Firefox installed, running test should work out of the box. To use a different Chromium-based browser, set the environment variable CHROME_BIN to point to the corresponding executable.