Skip to content

Instantly share code, notes, and snippets.

@themikefuller
Last active June 3, 2022 03:07
Show Gist options
  • Save themikefuller/6dbf53773bf53d57f7798a612b98e57f to your computer and use it in GitHub Desktop.
Save themikefuller/6dbf53773bf53d57f7798a612b98e57f to your computer and use it in GitHub Desktop.
Sealed Envelope function demonstrating end-to-end encryption with deniability using Starbase Cryptic
// Envelope Function
// End-to-End Encrypted Messaging with Deniability
// Demo showing off the features of Starbase Cryptic
// NOTE: This is just an example. A more secure messaging system would include a forward secrecy layer.
// This is a simplified version of the Sealed Envelope feature implemented within Starbase Encryption.
// Starbase Encryption is based on (inspired by) the Signal Protocol's Double Ratchet and Triple Diffie-Hellman key exchange.
function Envelope(cryptic = null) {
if (!cryptic) {
throw ("An instance of Starbase Cryptic is required.")
}
// A message can be encrypt in a "sealed envelope" that conceals the identity of the sender.
// The sending user does not retain the ephemeral key (ek) and thus cannot decrypt the contents.
// A third party could only decrypt the message by compromising the recipient's private key.
const sealEnvelope = async (user, to, msg) => {
const ek = await cryptic.createECDH();
const envDH = await cryptic.ecdh(ek.key, to);
const envBits = await cryptic.kdf(cryptic.decode(envDH), new Uint8Array(32), cryptic.fromText("ENVELOPE"), 512);
const envKey = await cryptic.decode(envBits).slice(0, 32);
const envChain = await cryptic.decode(envBits).slice(32, 64);
const envSeal = await cryptic.encrypt(user.pub, envKey);
const userDH = await cryptic.ecdh(user.key, to);
const msgKey = await cryptic.kdf(cryptic.decode(userDH), envChain, cryptic.fromText("ENCRYPT"), 256);
const ciphertext = await cryptic.encrypt(msg, cryptic.decode(msgKey));
return {
"type": "sealedEnvelope",
"to": to,
"ek": ek.pub,
"seal": envSeal,
"ciphertext": ciphertext
};
};
// Only the recipient can decrypt the sender's "identity" and message contents.
// The recipient will be unable to decrypt a message if the sealed identity is forged by a third party.
// The outcome is that either the "alleged sender" or the "intended recipient" could have created the message.
const openEnvelope = async (user, envelope) => {
const envDH = await cryptic.ecdh(user.key, envelope.ek);
const envBits = await cryptic.kdf(cryptic.decode(envDH), new Uint8Array(32), cryptic.fromText("ENVELOPE"), 512);
const envKey = await cryptic.decode(envBits).slice(0, 32);
const envChain = await cryptic.decode(envBits).slice(32, 64);
const from = await cryptic.decrypt(envelope.seal, envKey);
const userDH = await cryptic.ecdh(user.key, from);
const msgKey = await cryptic.kdf(cryptic.decode(userDH), envChain, cryptic.fromText("ENCRYPT"), 256);
const plaintext = await cryptic.decrypt(envelope.ciphertext, cryptic.decode(msgKey));
return {
"type": "openedEnvelope",
"to": user.pub,
"from": from,
"plaintext": plaintext
};
};
// This simple protocol provides natural deniability.
// A user can "forge" the sealed identity of an envelope, but only one addressed to themselves.
// Therefore a third party cannot obtain the decryption key for a message and cannot verify the true sender.
const forgeEnvelope = async (user, fakeFrom, msg) => {
const ek = await cryptic.createECDH();
const envDH = await cryptic.ecdh(ek.key, user.pub);
const envBits = await cryptic.kdf(cryptic.decode(envDH), new Uint8Array(32), cryptic.fromText("ENVELOPE"), 512);
const envKey = await cryptic.decode(envBits).slice(0, 32);
const envChain = await cryptic.decode(envBits).slice(32, 64);
const envSeal = await cryptic.encrypt(fakeFrom, envKey);
const userDH = await cryptic.ecdh(user.key, fakeFrom);
const msgKey = await cryptic.kdf(cryptic.decode(userDH), envChain, cryptic.fromText("ENCRYPT"), 256);
const ciphertext = await cryptic.encrypt(msg, cryptic.decode(msgKey));
return {
"type": "sealedEnvelope",
"to": user.pub,
"ek": ek.pub,
"seal": envSeal,
"ciphertext": ciphertext
};
};
return {
"createUser":cryptic.createECDH,
sealEnvelope,
openEnvelope,
forgeEnvelope
};
}
// Tests for Envelope Function
// node.js
const Starbase = require('@starbase/starbase');
const starbase = Starbase();
// Web Browser
// Elsewhere above... <script src="/path/to/starbase.min.js"></script>
const starbase = Starbase();
// Instance of Cryptic an Envelope
const cryptic = starbase.Cryptic();
const env = Envelope(cryptic);
// Mike creates a user and "shares" his public key (card) with Ghost.
const mike = await cryptic.createECDH();
const mikeCard = mike.pub;
// Ghost creates a user and "shares" their public key (card) with Mike.
const ghost = await cryptic.createECDH();
const ghostCard = ghost.pub;
// Mike writes a message and seals it in an envelope addressed to Ghost.
const sealed = await env.sealEnvelope(mike, ghostCard, "Hello, Friend!");
// Ghost opens the envelope, reads the message, and is able to confirm it came from Mike's public key.
const opened = await env.openEnvelope(ghost, sealed);
// Mike sends a fake message to himself made to appear as if it is from Ghost
const fakeSealed = await env.forgeEnvelope(mike, ghostCard, "I am Ghost!!!");
// Only Mike can open the envelope, but he cannot use the protocol to prove the message came from Ghost.
const fakeOpened = await env.openEnvelope(mike, fakeSealed);
console.log(mike, ghost, sealed, opened, fakeSealed, fakeOpened);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment