pmcrypto
pmcrypto v8 introduces support for RFC9580 (via OpenPGP.js v6), which standardizes new OpenPGP packets and algorithms, such as v6 keys and AEAD encryption for keys and messages. The OpenPGP PQC RFC draft also builds on top of RFC9580.
The new entities are not supported across Proton clients yet, and most of the library changes compared to v7 are internal, and are about ensuring we don't unexpectedly produce (or accept, e.g. for key imports), artifacts that will break some of our existing clients.
API changes:
checkKeyCompatibility
:
- reject v5 keys (breaking change: these were supported by pmcrypto v7, as they were introduced in draft RFC4880bis, but we are dropping support as they haven't been standardized)
- add
v6KeysAllowed
argument (defaulting to false) to accept v6 keys
Usage
pmcrypto must be initialized using the init
function, to apply changes to the underlying OpenPGP.js configuration.
import { init } from 'pmcrypto';
init();
Examples
Encrypt/sign and decrypt/verify string or binary data using keys
Encrypt/sign and decrypt/verify string or binary data using keys
To parse and decrypt the keys
const recipientPublicKey = await readKey({ armoredKey: '...' });
const senderPrivateKey = await decryptKey({
privateKey: await readPrivateKey({ armoredKey: '...' }),
passphrase: 'personal key passphrase'
});
To encrypt and sign:
const {
message: armoredMessage,
encryptedSignature: armoredEncryptedSignature
} = await encryptMessage({
textData: 'text data to encrypt',
encryptionKeys: recipientPublicKey,
signingKeys: senderPrivateKey,
detached: true,
format: 'armored'
});
To decrypt and verify (non-streamed input):
const senderPublicKey = await readKey(...);
const recipientPrivateKey = await decryptKey(...);
const { data: decryptedData, verified } = await decryptMessage({
message: await readMessage({ armoredMessage }),
encryptedSignature: await readMessage({ armoredMessage: armoredEncryptedSignature })
decryptionKeys: recipientPrivateKey
verificationKeys: senderPublicKey
});
For streamed inputs:
to encrypt (and/or sign), pass the stream to textData
or binaryData
based on the streamed data type. Similarly, to decrypt and verify, the input options are the same as the non-streaming case. However, if armoredMessage
(or binaryMessage
) is a stream, the decryption result needs to be handled differently:
if (!globalThis.TransformStream) {
await import('web-streams-polyfill/es6');
}
const { data: dataStream, verified: verifiedPromise } = await decryptMessage({
message: await readMessage({ armoredMessage: streamedArmoredMessage }),
...
});
const decryptedData = await readToEnd(dataStream);
const verificationStatus = await verified;
Encrypt/decrypt using the session key
Encrypt/decrypt using the session key directly
In v6, encryptMessage
would return the generated session key if options.returnSessionKey: true
was given. This option is no longer supported. Instead:
const sessionKey = await generateSessionKey({ recipientKeys: recipientPublicKey });
const { message: armoredMessage } = await encryptMessage({
textData: 'text data to encrypt',
sessionKey,
encryptionKeys: recipientPublicKey,
signingKeys: senderPrivateKey,
});
To decrypt, you can again provide the session key directly:
const { data } = await decryptMessage({
message: await readMessage({ armoredMessage }),
sessionKeys: sessionKey,
verificationKeys: senderPublicKey,
});
You can also encrypt the session key on its own:
const armoredEncryptedSessionKey = await encryptSessionKey({
sessionKey,
encryptionKeys,
format: 'armored'
});
const sessionKey = await decryptSessionKey({
message: await readMessage({ armoredMessage: armoredEncryptedSessionKey }),
decryptionsKeys
});
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.
If you'd like to test on a subset of browsers, use e.g. npm test -- --browsers ChromeHeadless,FirefoxHeadless
.