End-to-end encrypted message sharing using HPKE with COSE serialization. Encrypted messages travel in URL fragments and are decrypted entirely client-side.
Based on draft-ietf-cose-hpke
- HPKE-7 Cipher Suite: P-256 + HKDF-SHA256 + AES-256-GCM
- COSE Serialization: Standards-compliant CBOR Object Signing and Encryption
- URL Fragment Transport: Encrypted data in fragment (never sent to server)
- Compression: Automatic deflate compression for smaller URLs
- Multi-recipient: Encrypt to one (COSE_Encrypt0) or many (COSE_Encrypt) recipients
- Browser Support: Full client-side encryption/decryption
bun add cose-hpke# Display keys in CDDL diagnostic notation
bun run cose-hpke keygen
# Save keys to files
bun run cose-hpke keygen --output-public alice.pub --output-private alice.key# Encrypt to a recipient (outputs shareable URL)
bun run cose-hpke encrypt "Hello, World!" -r alice.pub
# Encrypt to multiple recipients
bun run cose-hpke encrypt "Secret message" -r alice.pub -r bob.pub
# Save to file instead of URL
bun run cose-hpke encrypt "Hello" -r alice.pub -o message.cose# Decrypt from URL
bun run cose-hpke decrypt "https://example.com#..." -k alice.key
# Decrypt from file
bun run cose-hpke decrypt message.cose -k alice.key
# Decrypt from stdin
cat message.cose | bun run cose-hpke decrypt - -k alice.keyimport {
generateKeyPair,
encrypt,
decrypt,
toDiagnostic,
createShareableUrl,
parseShareableUrl,
} from 'cose-hpke';
// Generate a keypair
const { publicKey, privateKey } = await generateKeyPair();
// View key in CDDL diagnostic notation
console.log(toDiagnostic(publicKey));
// {
// 1: 2, / kty: EC2 /
// -1: 1, / crv: P-256 /
// -2: h'...', / x /
// -3: h'...', / y /
// }
// Encrypt a message
const plaintext = new TextEncoder().encode('Hello, World!');
const ciphertext = await encrypt(plaintext, [publicKey]);
// Create shareable URL
const url = await createShareableUrl(ciphertext);
// https://cose-hpke.github.io/decrypt#v1.eJz...
// Parse URL and decrypt
const received = await parseShareableUrl(url);
const decrypted = await decrypt(received, privateKey);
console.log(new TextDecoder().decode(decrypted));
// Hello, World!import { fromJwk, toJwk, coseKeyToCryptoKey } from 'cose-hpke';
// Import from JWK
const coseKey = fromJwk({
kty: 'EC',
crv: 'P-256',
x: '...',
y: '...',
});
// Export to JWK
const jwk = toJwk(publicKey);
// Convert to WebCrypto key
const cryptoKey = await coseKeyToCryptoKey(publicKey);A browser-based demo is available at the GitHub Pages URL. It allows you to:
- Generate keypairs in the browser
- Save keys to localStorage with friendly names
- Encrypt messages to recipients
- Share via QR code or URL
- Decrypt messages from URL fragments
cd demo && python3 -m http.server 8000
# Open http://localhost:8000Encrypted messages are transported in URL fragments:
https://example.com/decrypt#v1.eJzLSM3JyQcABiwCFQ
├──┘└─────────────────┘
│ Base64url encoded
│ deflate-compressed
│ COSE message
│
Version prefix (v1 = compressed)
The fragment is never sent to the server, ensuring end-to-end encryption.
| Component | Algorithm |
|---|---|
| KEM | DHKEM(P-256, HKDF-SHA256) |
| KDF | HKDF-SHA256 |
| AEAD | AES-256-GCM |
| COSE Algorithm ID | -1 (HPKE-7) |
- Single recipient: COSE_Encrypt0 (tag 16) with integrated encryption
- Multiple recipients: COSE_Encrypt (tag 96) with key encryption
- RFC 9180 - Hybrid Public Key Encryption
- RFC 9052 - CBOR Object Signing and Encryption
- draft-ietf-cose-hpke - COSE HPKE
# Install dependencies
bun install
# Run tests
bun test
# Type check
bun run typecheck
# Build browser bundle
bun run build:demoMIT