npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@protontech/crypto

v2.0.1

Published

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.

Readme

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.

pmcrypto no longer needs to be directly imported by the apps and other packages, you should always use @protontech/crypto instead.

Usage

The utils functions that pmcrypto exported (e.g. uint8ArrayToBinaryString) are now accessible under @protontech/crypto/lib/utils.

Crypto-related functions runnable in web workers are handled by the CryptoProxy, which should be initialized together with the apps (see this section for more info on the setup).

The library uses some syntax and helpers that may not be available in older browsers; polyfill should be manually loaded by the library users if needed, by importing @protontech/crypto/polyfill. NB: polyfills should be loaded only once in a given global JS context. The web-clients apps already include the necessary polyfills in the main thread, but not in workers.

Examples

Importing/exporting public and private keys

OpenPGPKey objects have been replaced by PrivateKeyReference and PublicKeyReference ones, as key material stored away from main thread.

To import keys:

const recipientPublicKey = await CryptoProxy.importPublicKey({
    armoredKey: "...",
}); // or `binaryKey`
// To import a private key, the passphrase must be known
// (otherwise, either wait for it to be available, or import as public key)
const senderPrivateKey = await CryptoProxy.importPrivateKey({
    armoredKey: "...", // or `binaryKey`
    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`.
});

To export keys to be able to transfer them:

// on public key export, if a private key is given, only the public key material is extracted and serialized
const armoredPublicKey = await CryptoProxy.exportPublicKey({
    key: senderPrivateKey,
    format: "armored", // or 'binary'
});
// on private key export, the key will be encrypted before serialization, using the given `passhrapse`
const armoredPrivateKey = await CryptoProxy.exportPrivateKey({
    key: senderPrivateKey,
    passphrase: "key encryption passphrase",
    format: "armored", // or 'binary'
});

To delete the keys from memory once they are no longer needed:

// invalidate a specific key reference
await CryptoProxy.clearKey({ key: senderPrivateKey }); // after this, passing `senderPrivateKey` to the `CryptoProxy` will result in an error

// invalidate all keys previously imported and generated using the `CryptoProxy`
await CryptoProxy.clearKeyStore();

Encrypt/sign and decrypt/verify string or binary data using keys

To encrypt and sign:

// import the required keys
const senderPublicKey = await CryptoProxy.importPublicKey(...);
const recipientPrivateKey = await CryptoProxy.importPrivateKey(...);

const {
  message: armoredMessage,
  signature: armoredSignature,
  encryptedSignature: armoredEncryptedSignature,
} = await CryptoProxy.encryptMessage({
  textData: 'text data to encrypt', // or `binaryData` for Uint8Arrays
  encryptionKeys: recipientPublicKey, // and/or `passwords`
  signingKeys: senderPrivateKey,
  detached: true,
  format: 'armored' // or 'binary' to output a binary message and signature
});

// share `armoredMessage`

To decrypt and verify:

// import the required keys
const senderPublicKey = await CryptoProxy.importPublicKey(...);
const recipientPrivateKey = await CryptoProxy.importPrivateKey(...);

const { data: decryptedData, verificationStatus, verificationErrors } = await CryptoProxy.decryptMessage({
  armoredMessage, // or `binaryMessage`
  armoredEncryptedSignature, // or 'binaryEncryptedSignature'/'armoredSignature'/'binarySignature'
  decryptionKeys: recipientPrivateKey // and/or 'passwords'/'sessionKey'
  verificationKeys: senderPublicKey
});

if (verificationStatus === VERIFICATION_STATUS.SIGNED_AND_VALID) {
  console.log(decryptedData)
} else if (verificationStatus === VERIFICATION_STATUS.SIGNED_AND_INVALID) {
  console.log(verificationErrors)
}

Encrypt/decrypt using the session key directly

// First generate the session key
const sessionKey = await CryptoProxy.generateSessionKey({
    recipientKeys: recipientPublicKey,
});

// Then encrypt the data with it
const { message: armoredMessage } = await CryptoProxy.encryptMessage({
    textData: "text data to encrypt", // or `binaryData` for Uint8Arrays
    sessionKey,
    encryptionKeys: recipientPublicKey, // and/or `passwords`, used to encrypt the session key
    signingKeys: senderPrivateKey,
});

To decrypt, you can again provide the session key directly:

// Then encrypt the data with it
const { data } = await CryptoProxy.decryptMessage({
    armoredMessage, // or `binaryMessage`
    sessionKeys: sessionKey,
    verificationKeys: senderPublicKey,
});

You can also encrypt the session key on its own:

const armoredEncryptedSessionKey = await encryptSessionKey({
    ...sessionKey,
    encryptionKeys, // and/or passwords
    format: "armored", // or 'binary'
});

// And decrypt it with:
const sessionKey = await CryptoProxy.decryptSessionKey({
    armoredMessage: armoredEncryptedSessionKey, // or `binaryMessage`
    decryptionsKeys, // or `passwords`
});

Web Worker Integration

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.

This package implements a worker pool CryptoWorkerPool that the apps can use as endpoint, out of the box:

import { CryptoWorkerPool } from "@protontech/crypto/proxy/endpoint/workerPool/genericProvider.ts"; // or a specific provider

async function setupCryptoWorker() {
    await CryptoWorkerPool.init(); // CryptoWorkerPool is a singleton
    CryptoProxy.setEndpoint(
        CryptoWorkerPool,
        (endpoint) => endpoint.destroy(), // destroy the CryptoWorkerPool when the CryptoProxy endpoint is released
    );
}

Using workers is necessary since crypto operations are likely to freeze the UI if run in the main thread.

However, if you have an existing app-specific worker, you might not need to spawn separate workers, as described below.

Setting up CryptoProxy inside a worker (with separate key store than the main thread)

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:

import { Api: CryptoApi } from '@protontech/crypto/proxy/endpoint/api.ts'
CryptoProxy.setEndpoint(new CryptoApi(), endpoint => endpoint.clearKeyStore());

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).

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.

Using custom worker as CryptoProxy endpoint for the main thread (with shared key store)

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.

Example setup:

// in `customWorker.ts`:
import { expose, transferHandlers } from 'comlink';

import { CryptoProxy, PrivateKeyReference, PublicKeyReference } from '@protontech/crypto';
import { Api as CryptoApi } from '@protontech/crypto/proxy/endpoint/api.ts';
import { workerTransferHandlers } from '@protontech/crypto/proxy/endpoint/workerPool/transferHandlers/index.ts';

class CustomWorkerApi extends CryptoApi {
    constructor() {
        super();
        CryptoProxy.setEndpoint(this); // if needed, set endpoint (e.g. for @proton/shared) in the worker itself
    }

    // decrypt and encrypt to a different key, saving some communication overhead
    async reEncryptMessage({
        armoredMessage,
        decryptionKeys,
        encryptionKeys,
    }: {
        armoredMessage: string,
        decryptionKeys: PrivateKeyReference[],
        encryptionKeys: PublicKeyReference[],
    }) {
        const { data: binaryData } = await this.decryptMessage({ armoredMessage, decryptionKeys, format: 'binary' });
        return this.encryptMessage({ binaryData, encryptionKeys });
    }
}

// set up transfer handlers for the CryptoApi (you might have to set up your own as well)
workerTransferHandlers.forEach(({ name, handler }) => transferHandlers.set(name, handler));
// initialize underlying crypto libraries
CustomWorkerApi.init();

expose(CustomWorkerApi);
// in main thread:
import { wrap, transferHandlers } from 'comlink';
import { mainThreadTransferHandlers } from '@protontech/crypto/proxy/endpoint/workerPool/transferHandlers/index.ts';
import { CryptoProxy } from '@protontech/crypto';

const RemoteCustomWorker = wrap<typeof CustomWorkerApi>(new Worker(new URL('./customWorker.ts', import.meta.url)));
// set up transfer handlers for the CryptoApi (you might have to set up your own as well)
mainThreadTransferHandlers.forEach(({ name, handler }) => transferHandlers.set(name, handler));

async function doStuff() {
  // start the worker
  const customWorkerInstance = await new RemoteCustomWorker();
  // set it as CryptoProxy endpoint
  CryptoProxy.setEndpoint(customWorkerInstance);

  // the CryptoProxy requests will now be directed to your custom worker
  const oldKey = await CryptoProxy.importPrivateKey(...); // or `customWorkerInstance.importPrivateKey`
  const newKey = await CryptoProxy.generateKey(...); // or `customWorkerInstance.generateKey`

  // the custom functions need to be referenced directly, since the CryptoProxy is not aware of them
  await customWorkerInstance.reEncryptMessage({
    armoredMessage: '...',
    decryptionKeys: [oldKey],
    encryptionKeys: [newKey]
  });
}

Testing

Headless Chrome (or Chromium), Firefox and Webkit are used for the tests. To install any missing browsers automatically, you can run npx playwright install --with-deps <chromium|firefox|webkit>. Alternatively, you can install them manually as you normally would on your platform.