Golfing end-to-end encryption
Node.js (372 423+94=517 513 bytes)
Golfed
Linebreaks added for "readability".
chat.js (423 419 bytes)
No line breaks
[n,c,p]=["net","crypto","process"].map(require);r="rc4",a="create",h="DiffieHellman",z="pipe",w="write",o=128,g=p.argv;s=e=d=0,y=c[a+h](8*o),k=y.generateKeys();v=n.connect(9,g[2],_=>{g[3]&&(v[w](y.getPrime()),v[w](k));v.on("data",b=>{s||(g[3]||(y=c[a+h](b.slice(0,o)),k=y.generateKeys(),v[w](k),b=b.slice(o)),s=y.computeSecret(b),e=c[a+"Cipher"](r,s),p.stdin[z](e)[z](v),d=c[a+"Decipher"](r,s),v[z](d)[z](p.stdout))})})
Line breaks
[n,c,p]=["net","crypto","process"].map(require);
r="rc4",a="create",h="DiffieHellman",z="pipe",w="write",o=128,g=p.argv;
s=e=d=0,y=c[a+h](8*o),k=y.generateKeys();
v=n.connect(9,g[2],_=>{g[3]&&(v[w](y.getPrime()),v[w](k));
v.on("data",b=>{s||(g[3]||(y=c[a+h](b.slice(0,o)),k=y.generateKeys(),
v[w](k),b=b.slice(o)),s=y.computeSecret(b),e=c[a+"Cipher"](r,s),p.stdin[z](e)[z](v)
,d=c[a+"Decipher"](r,s),v[z](d)[z](p.stdout))})})
echo_server.js (94 bytes)
c=[],require("net").createServer(a=>{c.forEach(b=>{a.pipe(b),b.pipe(a)});c.push(a)}).listen(9);
Ungolfed
Node has built-in networking and crypto capabilities. This uses TCP for networking (because it's simpler than Node's interface for HTTP, and it plays nicely with streams).
I use a stream cipher (RC4) instead of AES to avoid having to deal with block sizes. Wikipedia seems to think it can be vulnerable, so if anyone has any insights into which ciphers are preferred, that would be great.
Run the echo server node echo_server.js
which will listen on port 9.
Run two instances of this program with node chat.js <server IP>
and node chat.js <server IP> 1
(the last argument just sets which one sends a prime). Each instance connects to the echo server. The first message handles the key generation, and subsequent messages use the stream cipher.
The echo server just sends everything back to all the connected clients except the original.
Client
var net = require('net');
var crypto = require('crypto');
var process = require('process');
let [serverIP, first] = process.argv.slice(2);
var keys = crypto.createDiffieHellman(1024); // DH key exchange
var prime = keys.getPrime();
var k = keys.generateKeys();
var secret;
var cipher; // symmetric cipher
var decipher;
// broadcast prime
server = net.connect(9, serverIP, () => {
console.log('connect')
if(first) {
server.write(prime);
console.log('prime length', prime.length)
server.write(k);
}
server.on('data', x => {
if(!secret) { // if we still need to get the ciphers
if(!first) { // generate a key with the received prime
keys = crypto.createDiffieHellman(x.slice(0,128)); // separate prime and key
k = keys.generateKeys();
server.write(k);
x = x.slice(128)
}
// generate the secret
console.log('length x', x.length);
secret = keys.computeSecret(x);
console.log('secret', secret, secret.length) // verify that secret key is the same
cipher = crypto.createCipher('rc4', secret);
process.stdin.pipe(cipher).pipe(server);
decipher = crypto.createDecipher('rc4', secret);
server.pipe(decipher).pipe(process.stdout);
}
else {
console.log('sent text ', x.toString()) // verify that text is sent encrypted
}
});
})
Echo server
var net = require('net');
clients = [];
net.createServer(socket => {
clients.forEach(c=>{socket.pipe(c); c.pipe(socket)});
clients.push(socket);
}).listen(9)
Thanks Dave for all the tips + feedback!