Overview
The @proton/crypto package interfaces Proton applications with the underlying OpenPGP crypto libraries (pmcrypto and OpenPGP.js) and the browser’s native WebCrypto API. It provides a unified CryptoProxy for handling cryptographic operations in web workers.
pmcrypto is deprecated. You should always use @proton/crypto instead of importing pmcrypto directly.
Installation
npm install @proton/crypto
Key Features
OpenPGP key management (import, export, generation)
Message encryption and decryption
Digital signatures and verification
Session key management
Web Worker integration for non-blocking operations
WebCrypto API utilities
Server time synchronization
Architecture
All crypto operations are handled through the CryptoProxy, which redirects requests to web workers to prevent UI blocking.
Quick Start
Setting Up CryptoProxy with Workers
import { CryptoProxy } from '@proton/crypto' ;
import { CryptoWorkerPool } from '@proton/crypto/lib/worker/workerPool' ;
async function setupCryptoWorker () {
await CryptoWorkerPool . init ();
CryptoProxy . setEndpoint (
CryptoWorkerPool ,
( endpoint ) => endpoint . destroy ()
);
}
// Call once at app startup
await setupCryptoWorker ();
Key Management
Importing Keys
import { CryptoProxy } from '@proton/crypto' ;
import type { PublicKeyReference , PrivateKeyReference } from '@proton/crypto' ;
// Import public key
const publicKey : PublicKeyReference = await CryptoProxy . importPublicKey ({
armoredKey: '-----BEGIN PGP PUBLIC KEY BLOCK-----...' ,
});
// Import private key
const privateKey : PrivateKeyReference = await CryptoProxy . importPrivateKey ({
armoredKey: '-----BEGIN PGP PRIVATE KEY BLOCK-----...' ,
passphrase: 'key-passphrase' ,
});
// Import from binary format
const binaryKey = await CryptoProxy . importPublicKey ({
binaryKey: uint8ArrayKey ,
});
When importing a private key, you must provide the passphrase. If the key is already decrypted (rare), use passphrase: null.
Exporting Keys
// Export public key
const armoredPublicKey = await CryptoProxy . exportPublicKey ({
key: privateKey , // Extracts only public key material
format: 'armored' , // or 'binary'
});
// Export private key (will be encrypted with passphrase)
const armoredPrivateKey = await CryptoProxy . exportPrivateKey ({
key: privateKey ,
passphrase: 'new-encryption-passphrase' ,
format: 'armored' , // or 'binary'
});
Generating Keys
// Generate new key pair
const { privateKey , publicKey } = await CryptoProxy . generateKey ({
userIDs: [{ name: 'User Name' , email: '[email protected] ' }],
type: 'ecc' , // or 'rsa'
curve: 'curve25519' , // for ECC
// rsaBits: 4096, // for RSA
passphrase: 'key-passphrase' ,
});
Clearing Keys from Memory
// Clear a specific key
await CryptoProxy . clearKey ({ key: privateKey });
// Clear all keys from the key store
await CryptoProxy . clearKeyStore ();
Encryption and Decryption
Encrypting Messages
// Encrypt and sign a message
const {
message : armoredMessage ,
signature : armoredSignature ,
encryptedSignature : armoredEncryptedSignature ,
} = await CryptoProxy . encryptMessage ({
textData: 'Secret message' , // or binaryData for Uint8Array
encryptionKeys: recipientPublicKey , // Can be array of keys
signingKeys: senderPrivateKey ,
detached: true , // Create detached signature
format: 'armored' , // or 'binary'
});
// Encrypt with password instead of keys
const { message } = await CryptoProxy . encryptMessage ({
textData: 'Secret message' ,
passwords: [ 'encryption-password' ],
format: 'armored' ,
});
Decrypting Messages
import { VERIFICATION_STATUS } from '@proton/crypto' ;
// Decrypt and verify
const {
data : decryptedData ,
verificationStatus ,
verificationErrors ,
} = await CryptoProxy . decryptMessage ({
armoredMessage , // or binaryMessage
armoredEncryptedSignature , // or armoredSignature/binarySignature/binaryEncryptedSignature
decryptionKeys: recipientPrivateKey , // Can be array or use passwords
verificationKeys: senderPublicKey ,
});
// Check verification status
if ( verificationStatus === VERIFICATION_STATUS . SIGNED_AND_VALID ) {
console . log ( 'Message is valid:' , decryptedData );
} else if ( verificationStatus === VERIFICATION_STATUS . SIGNED_AND_INVALID ) {
console . error ( 'Invalid signature:' , verificationErrors );
}
Session Keys
Session keys improve performance when encrypting the same data for multiple recipients.
Generating Session Keys
// Generate a session key
const sessionKey = await CryptoProxy . generateSessionKey ({
recipientKeys: recipientPublicKey ,
});
Encrypting with Session Keys
// Encrypt data with session key
const { message : armoredMessage } = await CryptoProxy . encryptMessage ({
textData: 'Message content' ,
sessionKey ,
encryptionKeys: recipientPublicKey , // Encrypts the session key
signingKeys: senderPrivateKey ,
});
Decrypting with Session Keys
// If you have the session key directly
const { data } = await CryptoProxy . decryptMessage ({
armoredMessage ,
sessionKeys: sessionKey ,
verificationKeys: senderPublicKey ,
});
Encrypting Session Keys Separately
// Encrypt just the session key
const armoredEncryptedSessionKey = await CryptoProxy . encryptSessionKey ({
... sessionKey ,
encryptionKeys: recipientPublicKey ,
format: 'armored' ,
});
// Decrypt the session key
const sessionKey = await CryptoProxy . decryptSessionKey ({
armoredMessage: armoredEncryptedSessionKey ,
decryptionKeys: recipientPrivateKey ,
});
Digital Signatures
Signing Data
// Sign without encryption
const { signature : armoredSignature } = await CryptoProxy . signMessage ({
textData: 'Data to sign' , // or binaryData
signingKeys: privateKey ,
detached: true ,
format: 'armored' ,
});
Verifying Signatures
// Verify detached signature
const { verificationStatus , verificationErrors } = await CryptoProxy . verifyMessage ({
textData: 'Original data' ,
armoredSignature , // or binarySignature
verificationKeys: publicKey ,
});
if ( verificationStatus === VERIFICATION_STATUS . SIGNED_AND_VALID ) {
console . log ( 'Signature is valid' );
}
Web Worker Integration
Using Worker Pool (Recommended)
import { CryptoWorkerPool } from '@proton/crypto/lib/worker/workerPool' ;
import { CryptoProxy } from '@proton/crypto' ;
async function setupCryptoWorker () {
await CryptoWorkerPool . init ();
CryptoProxy . setEndpoint (
CryptoWorkerPool ,
( endpoint ) => endpoint . destroy ()
);
}
Custom Worker Endpoint
If you have an existing app-specific worker:
// In your custom worker
import { expose , transferHandlers } from 'comlink' ;
import { CryptoProxy , PrivateKeyReference , PublicKeyReference } from '@proton/crypto' ;
import { Api as CryptoApi } from '@proton/crypto/lib/worker/api' ;
import { workerTransferHandlers } from '@proton/crypto/lib/worker/transferHandlers' ;
class CustomWorkerApi extends CryptoApi {
constructor () {
super ();
CryptoProxy . setEndpoint ( this );
}
async customCryptoOperation ( params : any ) {
// Your custom crypto operations
}
}
// Set up transfer handlers
workerTransferHandlers . forEach (({ name , handler }) => {
transferHandlers . set ( name , handler );
});
await CustomWorkerApi . init ();
expose ( CustomWorkerApi );
// In main thread
import { wrap , transferHandlers } from 'comlink' ;
import { mainThreadTransferHandlers } from '@proton/crypto/lib/worker/transferHandlers' ;
import { CryptoProxy } from '@proton/crypto' ;
const RemoteCustomWorker = wrap < typeof CustomWorkerApi >(
new Worker ( new URL ( './customWorker.ts' , import . meta . url ))
);
mainThreadTransferHandlers . forEach (({ name , handler }) => {
transferHandlers . set ( name , handler );
});
const customWorkerInstance = await new RemoteCustomWorker ();
CryptoProxy . setEndpoint ( customWorkerInstance );
Using CryptoApi Directly in Worker
For workers that need crypto operations without going through another worker:
import { CryptoProxy } from '@proton/crypto' ;
import { Api as CryptoApi } from '@proton/crypto/lib/worker/api' ;
// Inside a worker
CryptoProxy . setEndpoint (
new CryptoApi (),
( endpoint ) => endpoint . clearKeyStore ()
);
// Now use CryptoProxy as normal
const key = await CryptoProxy . importPrivateKey ({ ... });
CryptoApi should not be imported in the main thread as it includes OpenPGP.js which is large. Use dynamic imports if needed.
Utility Functions
Utility functions from pmcrypto are available under @proton/crypto/lib/utils.
import {
uint8ArrayToBinaryString ,
binaryStringToUint8Array ,
encodeBase64 ,
decodeBase64 ,
encodeUtf8 ,
decodeUtf8 ,
} from '@proton/crypto/lib/utils' ;
// Convert between formats
const binary = uint8ArrayToBinaryString ( uint8Array );
const array = binaryStringToUint8Array ( binaryString );
// Base64 encoding
const base64 = encodeBase64 ( uint8Array );
const decoded = decodeBase64 ( base64 );
// UTF-8 encoding
const utf8 = encodeUtf8 ( 'Hello, world!' );
const text = decodeUtf8 ( utf8 );
Server Time
Synchronize with server time for accurate timestamp operations.
import { serverTime } from '@proton/crypto/lib/serverTime' ;
// Get server time
const time = serverTime ();
// Update server time offset
serverTime . set ( new Date ( serverTimeMillis ));
Constants
import {
VERIFICATION_STATUS ,
KEY_FLAG ,
SIGNATURE_TYPES ,
} from '@proton/crypto/lib/constants' ;
// Verification statuses
VERIFICATION_STATUS . NOT_SIGNED
VERIFICATION_STATUS . SIGNED_AND_VALID
VERIFICATION_STATUS . SIGNED_AND_INVALID
VERIFICATION_STATUS . SIGNED_NO_PUB_KEY
TypeScript Types
import type {
PublicKeyReference ,
PrivateKeyReference ,
SessionKey ,
EncryptMessageResult ,
DecryptMessageResult ,
VerificationStatus ,
KeyPair ,
Data ,
MaybeArray ,
} from '@proton/crypto' ;
WebCrypto API Integration
Access browser’s native crypto for certain operations:
import { subtle } from '@proton/crypto/lib/subtle' ;
// Use Web Crypto API through the package
const key = await subtle . generateKey (
{ name: 'AES-GCM' , length: 256 },
true ,
[ 'encrypt' , 'decrypt' ]
);
Error Handling
import { CryptoProxy } from '@proton/crypto' ;
try {
await CryptoProxy . decryptMessage ({ ... });
} catch ( error ) {
if ( error . message . includes ( 'passphrase' )) {
console . error ( 'Invalid passphrase' );
} else if ( error . message . includes ( 'verification' )) {
console . error ( 'Signature verification failed' );
} else {
console . error ( 'Decryption error:' , error );
}
}
Testing
# Run tests (requires Chrome and Firefox)
yarn test
# Run tests in CI
yarn test:ci
# Use custom Chrome binary
CHROME_BIN = /path/to/chrome yarn test
Tests run in actual browsers (Chrome, Firefox) using Karma to ensure real-world compatibility.
Dependencies
pmcrypto (@protontech/pmcrypto) - OpenPGP.js wrapper
comlink - Web Worker communication
Always use web workers for crypto operations. Running crypto operations on the main thread will freeze the UI.
Initialize CryptoWorkerPool once at app startup
Reuse key references instead of re-importing
Use session keys for bulk encryption
Clear unused keys from memory with clearKey()
Security Best Practices
Security Guidelines
Never log or expose private keys
Always verify signatures when decrypting
Use strong passphrases for key encryption
Clear sensitive data from memory when done
Validate key fingerprints before importing
@proton/srp SRP authentication
@proton/shared Shared utilities
@proton/pass Password manager using crypto