skiff-crypto-v2
skiff-crypto-v2 is the next generation of Skiff’s cryptographic library, providing enhanced encryption capabilities with an improved datagram system that separates headers and bodies, along with comprehensive PGP support for email encryption.
Version 2.0.0 introduces breaking changes from v1, particularly in the datagram API structure.
Features
- Enhanced Datagram System: Separate header/body encryption with protobuf support
- PGP/OpenPGP Integration: Full support for PGP key management and email encryption
- Improved AEAD: ChaCha20Poly1305 with enhanced metadata handling
- WKD Support: Web Key Directory integration for automatic key discovery
- Backward Compatibility: Re-exports core functions from skiff-crypto v1
Installation
npm install skiff-crypto-v2
Key Differences from v1
Datagram API Changes
The primary difference is the datagram system now supports separate header and body encryption:
v1 (skiff-crypto):
// Single content parameter
encryptSymmetric<T>(content: T, symmetricKey: string, datagram: Datagram<T>)
v2 (skiff-crypto-v2):
// Separate header and body parameters
encryptSymmetric<Header, Body>(
header: Header,
body: Body,
symmetricKey: string,
datagram: Datagram<Header, Body>
)
V2 returns additional metadata with decryption results:
// V2 returns metadata along with decrypted content
{
header: Header;
body: Body;
metadata: AADMeta; // Additional authenticated data metadata
}
Core Encryption Functions
encryptSymmetric
Symmetric encryption with separate header and body support.
function encryptSymmetric<Header, Body>(
header: Header,
body: Body,
symmetricKey: string,
datagram: Datagram<Header, Body>
): string
The object header being serialized (typically metadata)
The object body being serialized (typically the main content)
Base64-encoded symmetric key
datagram
Datagram<Header, Body>
required
Mechanism to serialize/deserialize header and body
Returns: Base64-encoded encrypted payload
Example:
import { encryptSymmetric, createProtoWrapperDatagramV2 } from 'skiff-crypto-v2';
const datagram = createProtoWrapperDatagramV2(
'ddl://document',
HeaderProto,
BodyProto
);
const encrypted = encryptSymmetric(
{ version: '1.0', type: 'document' },
{ content: 'Hello, World!' },
symmetricKey,
datagram
);
Source: libs/skiff-crypto-v2/src/utils.ts:41
decryptSymmetric
Symmetric decryption with metadata extraction.
function decryptSymmetric<Header, Body>(
message: string,
symmetricKey: string,
DatagramType: Datagram<Header, Body>
): { header: Header; body: Body; metadata: AADMeta }
Base64-encoded encrypted payload
Base64-encoded key used for decryption
DatagramType
Datagram<Header, Body>
required
The datagram type for deserialization
Returns:
Additional authenticated data metadata
Example:
const { header, body, metadata } = decryptSymmetric(
encrypted,
symmetricKey,
datagram
);
console.log('Header:', header);
console.log('Body:', body);
console.log('Metadata:', metadata);
Source: libs/skiff-crypto-v2/src/utils.ts:82
rawEncryptSymmetric
Symmetric encryption returning raw bytes without base64 encoding.
function rawEncryptSymmetric<Header, Body>(
header: Header,
body: Body,
symmetricKey: string,
datagram: Datagram<Header, Body>
): Uint8Array
The object header being serialized
The object body being serialized
Base64-encoded symmetric key
datagram
Datagram<Header, Body>
required
Mechanism to serialize header and body
Returns: Uint8Array encrypted payload
Source: libs/skiff-crypto-v2/src/utils.ts:18
rawDecryptSymmetric
Symmetric decryption from raw bytes.
function rawDecryptSymmetric<Header, Body>(
message: Uint8Array,
symmetricKey: string,
DatagramType: Datagram<Header, Body>
): { header: Header; body: Body; metadata: AADMeta }
Encrypted payload as raw bytes
Base64-encoded key used for decryption
DatagramType
Datagram<Header, Body>
required
The datagram type for deserialization
Returns: Object with decrypted header, body, and metadata
Source: libs/skiff-crypto-v2/src/utils.ts:60
Session Key Management
encryptSessionKey
Encrypt a document session key for sharing.
function encryptSessionKey(
sessionKey: SessionKey,
myPrivateKey: string,
myPublicKey: PublicKey,
theirPublicKey: PublicKey
): EncryptedKey
Document session key to encrypt
Encryption private key used to encrypt session key
Current user’s encryption public key
Other user’s encryption public key
Returns:
The encrypted session key
Public key of the user who encrypted it
Source: libs/skiff-crypto-v2/src/core.ts:22
decryptSessionKey
Decrypt a document session key.
function decryptSessionKey(
encryptedSessionKey: string,
myPrivateKey: string,
theirPublicKey: Pick<PublicKey, 'key'>
): SessionKey
Encrypted copy of the document session key
Current user’s encryption private key
User who encrypted key’s encryption public key
Returns: Decrypted session key string
Source: libs/skiff-crypto-v2/src/core.ts:43
signEncryptedSessionKey
Sign an encrypted document session key.
function signEncryptedSessionKey(
encryptedKey: string,
mySigningPrivateKey: string
): string
Encrypted key field to sign
Signing private key used to generate signature
Returns: Base64-encoded signature
Source: libs/skiff-crypto-v2/src/core.ts:60
Datagram V2 Functions
encryptDatagramV2
Encrypt a datagram with header and body.
function encryptDatagramV2<Header, Body>(
datagram: Datagram<Header, Body>,
header: Header,
body: Body,
sessionKey: string
): EncryptedDataOutput
datagram
Datagram<Header, Body>
required
Datagram definition for serialization
Datagram header (typically metadata)
Datagram body (main content)
Symmetric document encryption key
Returns:
Base64-encoded encrypted datagram
Source: libs/skiff-crypto-v2/src/core.ts:74
decryptDatagramV2
Decrypt a datagram.
function decryptDatagramV2<Header, Body>(
datagram: Datagram<Header, Body>,
sessionKey: string,
encryptedDatagram: string
): { header: Header; body: Body; metadata: AADMeta }
datagram
Datagram<Header, Body>
required
Datagram definition for deserialization
Symmetric document encryption key
Returns: Object with decrypted header, body, and metadata
Source: libs/skiff-crypto-v2/src/core.ts:93
createProtoWrapperDatagramV2
Create a datagram that uses Protocol Buffers for serialization.
function createProtoWrapperDatagramV2<Header, Body>(
type: string,
headerClass: ProtoClass<Header>,
bodyClass: ProtoClass<Body>,
version?: string,
versionConstraint?: Range
): Datagram<Header, Body>
Protobuf class for header serialization
Protobuf class for body serialization
Datagram version constraint
Returns: Datagram instance with protobuf serialization
Example:
import { createProtoWrapperDatagramV2 } from 'skiff-crypto-v2';
import { DocumentHeader, DocumentBody } from './generated/protos';
const DocumentDatagram = createProtoWrapperDatagramV2(
'ddl://skiff/document',
DocumentHeader,
DocumentBody,
'1.0.0'
);
Source: libs/skiff-crypto-v2/src/datagramClasses.ts:7
PGP Support
generatePGPKey
Generate a PGP key pair.
async function generatePGPKey(
userIDs: UserID[],
type?: GenerateKeyOptions['type'],
curve?: GenerateKeyOptions['curve']
): Promise<PGPKeyPair>
Array of user IDs for the key (name and email)
Key type (ecc, rsa, etc.)
curve
string
default:"curve25519"
ECC curve name
Returns:
Binary format private key
User IDs associated with the key
Example:
import { generatePGPKey } from 'skiff-crypto-v2';
const { privateKey, publicKey } = await generatePGPKey([
{ name: 'John Doe', email: '[email protected]' }
]);
Source: libs/skiff-crypto-v2/src/pgp/keyManagement.ts:28
exportPGPKey
Export a PGP key in armored format.
async function exportPGPKey(
userIDs: UserID[],
currentPrivateKey: PrivateKey,
createdAt: Date,
passphrase?: string
): Promise<{ privateKey: string; publicKey: string }>
Optional passphrase to encrypt the exported key
Returns: Object with armored private and public keys
Source: libs/skiff-crypto-v2/src/pgp/keyManagement.ts:55
readPrivateKey
Read a PGP private key from binary format.
async function readPrivateKey(
privateKeyData: Uint8Array
): Promise<PrivateKey>
Returns: PrivateKey object
Source: libs/skiff-crypto-v2/src/pgp/keyManagement.ts:82
readArmoredPrivateKey
Read a PGP private key from armored format.
async function readArmoredPrivateKey(
privateKeyData: string,
passphrase?: string | null
): Promise<PrivateKey>
Armored private key string
Passphrase to decrypt the key if encrypted
Returns: PrivateKey object
Source: libs/skiff-crypto-v2/src/pgp/keyManagement.ts:92
readPublicKey / readArmoredPublicKey
Read PGP public keys from binary or armored format.
async function readPublicKey(key: Uint8Array): Promise<PublicKey>
async function readArmoredPublicKey(key: string): Promise<PublicKey>
Source: libs/skiff-crypto-v2/src/pgp/keyManagement.ts:105, 117
PGP Message Encryption
encryptMessage
Encrypt a message for multiple recipients using PGP.
async function encryptMessage(
binary: Uint8Array | ReadableStream,
sender: SenderPGPInfo,
recipients: RecipientPGPInfo[]
): Promise<ReadableStream<string>>
binary
Uint8Array | ReadableStream
required
Message content to encrypt
Sender information with private key for signing
recipients
RecipientPGPInfo[]
required
Array of recipients with public keys
Returns: ReadableStream of armored encrypted message
Example:
import { encryptMessage } from 'skiff-crypto-v2';
const encrypted = await encryptMessage(
new TextEncoder().encode('Hello, World!'),
{
name: 'Alice',
email: '[email protected]',
privateKey: alicePrivateKey,
keyID: aliceKeyID
},
[
{
name: 'Bob',
email: '[email protected]',
publicKey: bobPublicKey,
keyID: bobKeyID
}
]
);
Source: libs/skiff-crypto-v2/src/pgp/encrypt.ts:28
decryptMessage
Decrypt a PGP message and verify signature.
async function decryptMessage(
message: Message<string>,
senderPublicKey: PublicKey,
recipientPrivateKey: PrivateKey
): Promise<{
decrypted: string | Uint8Array;
signatures: Signature[];
signatureVerified: boolean;
}>
Encrypted message to decrypt
Sender’s public key for signature verification
Recipient’s private key for decryption
Returns:
decrypted
string | Uint8Array
required
Decrypted message content
Whether the signature was successfully verified
Source: libs/skiff-crypto-v2/src/pgp/encrypt.ts:84
parseEncryptedMessage
Parse an armored encrypted message.
async function parseEncryptedMessage(
encryptedText: string
): Promise<{
encryptedMessage: Message;
encryptedKeyIDs: KeyID[];
}>
Armored encrypted message text
Returns: Parsed message and key IDs
Source: libs/skiff-crypto-v2/src/pgp/encrypt.ts:75
Web Key Directory (WKD)
fetchWKDKey
Fetch a recipient’s public key from Web Key Directory.
async function fetchWKDKey(
emailAddress: string,
originUrl: URL
): Promise<PublicKey | null>
Email address to query for PGP key
Base proxy URL for WKD queries
Returns: PublicKey if found, null otherwise
Example:
import { fetchWKDKey } from 'skiff-crypto-v2';
const publicKey = await fetchWKDKey(
'[email protected]',
new URL('https://api.skiff.com')
);
if (publicKey) {
console.log('Found PGP key for user');
}
Source: libs/skiff-crypto-v2/src/pgp/keyManagement.ts:160
computeWKDHash
Compute the WKD hash for an email address local part.
async function computeWKDHash(localPart: string): Promise<string>
Local part of email address (before @)
Returns: Z-Base-32 encoded SHA-1 hash (32 characters)
Source: libs/skiff-crypto-v2/src/pgp/keyManagement.ts:128
verifyWKDHash
Verify that a provided hash matches the computed hash.
async function verifyWKDHash(
localPart: string,
providedHash: string
): Promise<boolean>
Local part of email address
Returns: Boolean indicating if hash is valid
Source: libs/skiff-crypto-v2/src/pgp/keyManagement.ts:148
publishWKDKey
Convert a public key into WKD format for publishing.
async function publishWKDKey(armoredKey: string): Promise<Uint8Array>
Armored public key to publish
Returns: Binary representation of the public key
Source: libs/skiff-crypto-v2/src/pgp/keyManagement.ts:217
Migration from v1
Basic Encryption Migration
Before (v1):
import { encryptSymmetric, createJSONWrapperDatagram } from 'skiff-crypto';
const datagram = createJSONWrapperDatagram('ddl://data');
const encrypted = encryptSymmetric(content, key, datagram);
After (v2):
import { encryptSymmetric, createProtoWrapperDatagramV2 } from 'skiff-crypto-v2';
const datagram = createProtoWrapperDatagramV2(
'ddl://data',
HeaderProto,
BodyProto
);
const encrypted = encryptSymmetric(header, body, key, datagram);
Decryption Migration
Before (v1):
const decrypted = decryptSymmetric(encrypted, key, datagram);
// Returns: T (the content directly)
After (v2):
const { header, body, metadata } = decryptSymmetric(encrypted, key, datagram);
// Returns: { header: Header, body: Body, metadata: AADMeta }
Using Backward Compatible Functions
V2 re-exports all v1 functions for backward compatibility:
import {
generatePublicPrivateKeyPair,
generateSymmetricKey,
stringEncryptAsymmetric,
stringDecryptAsymmetric,
generateHash
} from 'skiff-crypto-v2';
// These work exactly as in v1
const keypair = generatePublicPrivateKeyPair();
const hash = generateHash('data');
Complete Example
import {
generatePGPKey,
encryptMessage,
decryptMessage,
parseEncryptedMessage,
readPublicKey
} from 'skiff-crypto-v2';
// Generate PGP keys for sender and recipient
const sender = await generatePGPKey([
{ name: 'Alice', email: '[email protected]' }
]);
const recipient = await generatePGPKey([
{ name: 'Bob', email: '[email protected]' }
]);
// Encrypt a message
const messageContent = new TextEncoder().encode('Secret message!');
const encryptedStream = await encryptMessage(
messageContent,
{
name: 'Alice',
email: '[email protected]',
privateKey: await readPrivateKey(sender.privateKey),
keyID: (await readPrivateKey(sender.privateKey)).getKeyIDs()[0]
},
[
{
name: 'Bob',
email: '[email protected]',
publicKey: await readPublicKey(recipient.publicKey),
keyID: (await readPublicKey(recipient.publicKey)).getKeyIDs()[0]
}
]
);
// Convert stream to string
const encryptedText = await getStringFromStream(encryptedStream);
// Decrypt the message
const { encryptedMessage } = await parseEncryptedMessage(encryptedText);
const { decrypted, signatureVerified } = await decryptMessage(
encryptedMessage,
await readPublicKey(sender.publicKey),
await readPrivateKey(recipient.privateKey)
);
console.log('Decrypted:', new TextDecoder().decode(decrypted));
console.log('Signature verified:', signatureVerified);
Dependencies
- @stablelib/chacha20poly1305: ChaCha20-Poly1305 AEAD encryption
- openpgp: OpenPGP.js for PGP operations
- protobufjs: Protocol Buffers support for datagrams
- skiff-crypto: Re-exports core v1 functions
- zbase32: Z-Base-32 encoding for WKD
Security Considerations
- All PGP keys support ECC (Curve25519) and RSA (minimum 2048 bits)
- ElGamal and DSA algorithms are explicitly rejected for security
- WKD queries use CORS-safe proxy endpoints
- Signature verification prevents re-interpretation attacks via context tags
- ChaCha20-Poly1305 provides authenticated encryption with additional data (AEAD)
License
skiff-crypto-v2 is MIT licensed. It is part of the Skiff open-source project.