Last active
June 3, 2022 03:07
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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