Server Implementations
Capsule servers handle content encryption and DEK wrapping. Implementations are available for Node.js and PHP, with more languages coming soon.
Node.js
The Node.js implementation uses the built-in crypto module for maximum performance and minimal dependencies.
Installation
npm install capsuleBasic Usage
import { ArticleEncryptor } from 'capsule';
// Encrypt content for a specific client
const encryptor = new ArticleEncryptor(clientPublicKey);
const encrypted = await encryptor.encrypt(content);
// Result: { encryptedContent, iv, encryptedDek }API Routes (Next.js Example)
// app/api/unlock/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { publicEncrypt, constants } from 'crypto';
export async function POST(request: NextRequest) {
const { tier, publicKey } = await request.json();
// Get the DEK for this subscription tier
const dek = getSubscriptionDek(tier);
// Convert Base64 SPKI to PEM
const publicKeyPem = convertToPem(publicKey);
// Wrap DEK with client's public key
const encryptedDek = publicEncrypt(
{
key: publicKeyPem,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256'
},
dek
);
return NextResponse.json({
encryptedDek: encryptedDek.toString('base64'),
tier
});
}Pre-Encrypting Content
import { createCipheriv, randomBytes } from 'crypto';
function encryptArticle(content: string, dek: Buffer) {
const iv = randomBytes(12); // 96-bit IV
const cipher = createCipheriv('aes-256-gcm', dek, iv, {
authTagLength: 16
});
const encrypted = Buffer.concat([
cipher.update(content, 'utf8'),
cipher.final(),
cipher.getAuthTag()
]);
return {
encryptedContent: encrypted.toString('base64'),
iv: iv.toString('base64'),
tier: 'premium'
};
}
// At build time or when content is published:
const articles = [
{ id: '1', content: '...' },
{ id: '2', content: '...' }
];
const premiumDek = randomBytes(32); // Generate once per tier
const encrypted = articles.map(article =>
encryptArticle(article.content, premiumDek)
);PHP
PHP implementation using OpenSSL for cryptographic operations.
Installation
composer require capsule/capsule-phpBasic Usage
<?php
use Capsule\ArticleEncryptor;
// Encrypt content
$dek = random_bytes(32); // AES-256 key
$iv = random_bytes(12); // GCM IV
$encrypted = openssl_encrypt(
$content,
'aes-256-gcm',
$dek,
OPENSSL_RAW_DATA,
$iv,
$tag
);
$result = [
'encryptedContent' => base64_encode($encrypted . $tag),
'iv' => base64_encode($iv),
'tier' => 'premium'
];Key Exchange Endpoint
<?php
// api/unlock.php
header('Content-Type: application/json');
$input = json_decode(file_get_contents('php://input'), true);
$tier = $input['tier'];
$publicKey = $input['publicKey'];
// Get DEK for tier
$dek = getSubscriptionDek($tier);
// Convert SPKI to PEM
$publicKeyPem = convertSpkiToPem($publicKey);
// Wrap DEK with RSA-OAEP
$encryptedDek = '';
openssl_public_encrypt(
$dek,
$encryptedDek,
$publicKeyPem,
OPENSSL_PKCS1_OAEP_PADDING
);
echo json_encode([
'encryptedDek' => base64_encode($encryptedDek),
'tier' => $tier
]);
function convertSpkiToPem($base64Spki) {
$der = base64_decode($base64Spki);
$pem = "-----BEGIN PUBLIC KEY-----\n";
$pem .= chunk_split(base64_encode($der), 64);
$pem .= "-----END PUBLIC KEY-----";
return $pem;
}Python
Python support using the cryptography library.
Installation
pip install capsule-pyBasic Usage
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes
import os
import base64
# Encrypt content
def encrypt_article(content: str, dek: bytes) -> dict:
iv = os.urandom(12)
aesgcm = AESGCM(dek)
ciphertext = aesgcm.encrypt(iv, content.encode(), None)
return {
'encryptedContent': base64.b64encode(ciphertext).decode(),
'iv': base64.b64encode(iv).decode(),
'tier': 'premium'
}
# Wrap DEK
def wrap_dek(dek: bytes, public_key_spki: str) -> str:
# Load public key from SPKI
public_key = serialization.load_der_public_key(
base64.b64decode(public_key_spki)
)
# Wrap with RSA-OAEP
encrypted = public_key.encrypt(
dek,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
return base64.b64encode(encrypted).decode()Coming Soon
- 🔨 Go implementation
- 🔨 Ruby implementation
- 🔨 Rust implementation
- 🔨 .NET implementation
Want to contribute an implementation? Check out the GitHub repository.