sequenceDiagram
actor Initiator
actor Recipient
participant KEM
participant KDF
participant AEAD
Initiator->>KEM: Generate ephemeral key pair
KEM->>Initiator: Ephemeral public key
KEM->>Recipient: Encapsulated shared secret
Initiator->>KDF: Derive shared secret from ephemeral key pair
KDF->>Initiator: Shared secret
Initiator->>AEAD: Encrypt message with shared secret
AEAD->>Initiator: Encrypted message
Initiator->>Recipient: Encrypted message, Ephemeral public key, Encapsulated shared secret
Recipient->>KEM: Decrypt encapsulated shared secret using private key
KEM->>Recipient: Shared secret
Recipient->>KDF: Derive shared secret from ephemeral key pair
KDF->>Recipient: Shared secret
Recipient->>AEAD: Decrypt message with shared secret
AEAD->>Recipient: Plaintext message
Primitive | Purpose | Input | Output |
---|---|---|---|
KEM | Derive shared from public key pair | Public key | Shared secret |
KDF | Derive one or more keys from input keying material | Input keying material | One or more keys |
AEAD | Encrypt and authenticate plaintext | Plaintext, associated data, key | Ciphertext, authentication tag |
// Define the KEM, KDF, and AEAD algorithms to use
const kem = 'ECDH-ES';
const kdf = 'HKDF-SHA256';
const aead = 'AES-GCM-128';
// Generate an ephemeral key pair for the initiator
const initiatorKeyPair = crypto.generateKeyPairSync('ec', { namedCurve: 'secp256k1' });
const initiatorPublicKey = initiatorKeyPair.publicKey.export({ format: 'spki', type: 'pem' });
// Generate a shared secret using the KEM
const sharedSecret = crypto.generateSharedSecret(initiatorKeyPair.privateKey, kem);
// Derive a key from the shared secret using the KDF
const key = crypto.pbkdf2Sync(sharedSecret, 'salt', 10000, 32, kdf);
// Encrypt the message using the AEAD
const message = Buffer.from('Hello, world!');
const encryptedMessage = crypto.subtle.encrypt({ name: aead }, key, message);
// Send the encrypted message and the initiator's public key to the recipient
const recipientPublicKey = ...; // Get the recipient's public key
// Decrypt the message using the recipient's private key
const recipientKeyPair = crypto.generateKeyPairSync('ec', { namedCurve: 'secp256k1' });
const recipientPrivateKey = recipientKeyPair.privateKey.import({ format: 'pkcs8', type: 'pem' });
const decryptedMessage = await crypto.subtle.decrypt({ name: aead }, recipientPrivateKey, encryptedMessage);
// Decrypt the message using the shared secret and the KDF
const derivedKey = crypto.pbkdf2Sync(sharedSecret, 'salt', 10000, 32, kdf);
const decryptedMessageBuffer = crypto.subtle.decrypt({ name: aead }, derivedKey, encryptedMessage);
// Convert the decrypted message to a string
const decryptedMessageString = decryptedMessageBuffer.toString();
console.log('Decrypted message:', decryptedMessageString);
https://developer.apple.com/documentation/cryptokit/hpke
https://www.franziskuskiefer.de/p/tldr-hybrid-public-key-encryption/