Enkripsi ujung ke ujung golf

16

Tantangan ini membawa hadiah 200 poin bagi yang pertama menjawab dan tetap tak terkalahkan selama setidaknya 3 hari. Diklaim oleh pengguna3080953 .

Ada banyak pembicaraan akhir-akhir ini tentang enkripsi ujung ke ujung, dan tekanan pada perusahaan untuk menghapusnya dari produk mereka. Saya tidak tertarik pada hak-dan-kesalahan itu, tapi saya bertanya-tanya: seberapa pendek kode yang bisa membuat perusahaan ditekan untuk tidak menggunakannya?

Tantangannya di sini adalah untuk mengimplementasikan pertukaran kunci Diffie Hellman antara dua sistem jaringan, kemudian memungkinkan pengguna untuk berkomunikasi bolak-balik menggunakan kunci simetris yang dihasilkan. Untuk tujuan tugas ini, tidak ada perlindungan lain yang diperlukan (mis. Tidak perlu memutar kunci, memverifikasi identitas, melindungi terhadap DoS, dll.) Dan Anda dapat menggunakan internet terbuka (semua port yang Anda dengarkan tersedia untuk semua orang). Penggunaan bawaan diizinkan dan dianjurkan!

Anda dapat memilih salah satu dari dua model:

  • Server dan klien: klien terhubung ke server, kemudian server atau klien dapat mengirim pesan ke yang lain. Pihak ketiga di antara keduanya harus tidak dapat membaca pesan. Contoh aliran dapat berupa:
    1. Pengguna A meluncurkan server
    2. Pengguna B meluncurkan klien dan mengarahkannya ke server pengguna A (mis. Melalui IP / port), program membuka koneksi
    3. Program Pengguna A mengakui koneksi (opsional meminta persetujuan terlebih dahulu kepada pengguna)
    4. Program Pengguna B memulai pembuatan rahasia DH, dan mengirimkan data yang diperlukan (kunci publik, prima, generator, apa pun yang dibutuhkan implementasi Anda) kepada Pengguna A
    5. Program Pengguna A menggunakan data yang dikirim untuk menyelesaikan pembuatan rahasia bersama dan mengirimkan kembali data yang diperlukan (kunci publik) kepada Pengguna B. Dari titik ini, Pengguna A dapat memasukkan pesan (misalnya melalui stdin) yang akan dienkripsi dan dikirim ke Pengguna B (misalnya ke stdout).
    6. Program Pengguna B menyelesaikan pembuatan rahasia bersama. Dari titik ini, Pengguna B dapat mengirim pesan ke Pengguna A.
  • Atau: Server dengan dua klien yang terhubung: setiap klien berbicara ke server, yang meneruskan pesan mereka ke klien lain. Server itu sendiri (dan pihak ketiga mana pun di antaranya) harus tidak dapat membaca pesan. Selain koneksi awal, prosesnya sama seperti yang dijelaskan dalam opsi pertama.

Aturan terperinci:

  • Anda dapat menyediakan satu program, atau beberapa program (mis. Server & klien). Skor Anda adalah ukuran kode total di semua program.
  • Program Anda secara teoritis harus mampu berkomunikasi melalui jaringan (tetapi untuk pengujian, localhost baik-baik saja). Jika bahasa pilihan Anda tidak mendukung jaringan, Anda dapat menggabungkannya dengan sesuatu yang mendukung (misalnya skrip shell); dalam hal ini skor Anda adalah ukuran kode total di semua bahasa yang digunakan.
  • Generasi kunci Diffie Hellman dapat menggunakan nilai-nilai "p" dan "g" yang dikodekan dengan keras.
  • Kunci bersama yang dihasilkan harus setidaknya 1024 bit.
  • Setelah kunci dibagikan, pilihan enkripsi kunci-simetris terserah Anda, tetapi Anda tidak boleh memilih metode yang saat ini diketahui memiliki serangan praktis terhadapnya (misal, pergeseran caesar sepele untuk dibalik tanpa mengetahui kunci tersebut. ). Contoh algoritma yang diizinkan:
    • AES (ukuran kunci apa saja)
    • RC4 (secara teori rusak, tetapi tidak ada serangan praktis yang dapat saya sebutkan, jadi diizinkan di sini)
  • Pengguna A dan B harus dapat mengirim pesan satu sama lain (komunikasi dua arah) secara interaktif (misalnya membaca baris dari stdin, terus menerus mendorong, atau acara seperti menekan tombol). Jika itu membuatnya lebih mudah, Anda dapat mengasumsikan percakapan bergantian (yaitu setelah pengguna mengirim pesan, mereka harus menunggu jawaban sebelum mengirim pesan berikutnya)
  • Builtin bahasa yang diizinkan (tidak perlu menulis metode kriptografi atau jaringan Anda sendiri jika mereka sudah didukung).
  • Format komunikasi yang mendasarinya terserah Anda.
  • Langkah-langkah komunikasi yang diberikan di atas adalah contoh, tetapi Anda tidak diharuskan untuk mengikutinya (selama informasi yang diperlukan dibagikan, dan tidak ada perantara yang dapat menghitung kunci atau pesan yang dibagikan)
  • Jika detail yang diperlukan untuk terhubung ke server Anda tidak diketahui sebelumnya (mis. Jika mendengarkan pada port acak), detail ini harus dicetak. Anda dapat mengasumsikan bahwa alamat IP mesin diketahui.
  • Penanganan kesalahan (mis. Alamat tidak valid, kehilangan koneksi, dll.) Tidak diperlukan.
  • Tantangannya adalah kode golf, jadi kode terpendek dalam byte menang.
Dave
sumber
Apakah hardcoding pdan gdiperbolehkan?
ASCII-satunya
@ ASCII-hanya dari apa yang bisa saya katakan, hard-coding nilai p & g berkualitas baik dianggap baik-baik saja (kecuali pengembang jahat menggunakan nilai-nilai yang diketahui rentan terhadap serangan tertentu). Jadi untuk tantangan ini tidak apa-apa (selama rahasia yang dihasilkan setidaknya 1024 bit)
Dave

Jawaban:

3

Node.js ( 372 423 + 94 = 517 513 bytes)

Golf

Linebreak ditambahkan untuk "keterbacaan".

chat.js ( 423 419 bytes)

Tidak ada jeda baris

[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))})})

Jeda baris

[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);

Tidak disatukan

Node memiliki kemampuan jaringan dan kripto bawaan. Ini menggunakan TCP untuk jaringan (karena lebih sederhana dari antarmuka Node untuk HTTP, dan ini berfungsi baik dengan stream).

Saya menggunakan stream cipher (RC4) bukan AES untuk menghindari harus berurusan dengan ukuran blok. Wikipedia tampaknya berpikir itu bisa rentan, jadi jika ada yang punya wawasan ke mana cipher disukai, itu akan bagus.

Jalankan server gema node echo_server.jsyang akan mendengarkan pada port 9. Jalankan dua contoh dari program ini dengan node chat.js <server IP>dan node chat.js <server IP> 1(argumen terakhir hanya menetapkan yang mana yang mengirim prime). Setiap instance terhubung ke server gema. Pesan pertama menangani pembuatan kunci, dan pesan selanjutnya menggunakan stream cipher.

Gema Server hanya mengirimkan semuanya kembali ke semua klien yang terhubung kecuali aslinya.

Klien

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
        }
    });
})

Server gema

var net = require('net');
clients = [];

net.createServer(socket => {
    clients.forEach(c=>{socket.pipe(c); c.pipe(socket)});
    clients.push(socket);
}).listen(9)

Terima kasih Dave untuk semua tips + umpan balik!

user3080953
sumber
1
Jangan menambahkan keterbacaan ke versi golf, untuk itulah versi ungolfed. Atau jika Anda melakukannya, lepaskan titik koma sebelum garis putus, jadi panjangnya sama.
mbomb007
@ mbomb007 "keterbacaan" sebagian besar untuk menghindari keharusan menggulir. Sayangnya, isi kode tidak memiliki titik koma, sehingga tidak berfungsi. Saya pikir mencari cepat dan mengganti tidak akan terlalu berat. pasti akan mengingat tip Anda untuk komentar di masa depan!
user3080953
@Terima kasih atas semua umpan baliknya! Saya telah membuat perubahan untuk menggunakan vanilla DH, yang sebenarnya menambahkan sedikit panjang karena Anda perlu bertukar bilangan prima juga AES benar-benar berfungsi sebagai pengganti drop-in, tetapi masalah dengan AES adalah bahwa tidak ada yang dikirim sampai Anda selesai sebuah balok, dan bantalan akan terasa menyebalkan. juga rc4 lebih pendek dari aes128
user3080953
1
Saya tidak yakin apakah itu akan berhasil melalui jaringan, tetapi mungkin tidak, dan saya menulisnya di bus sehingga saya tidak punya cara untuk memeriksa. versi baru menggunakan server gema sebagai gantinya. Ini juga memecahkan masalah batas waktu. Saya mencoba menghindari server + klien, tetapi ini adalah bentuk yang jauh lebih baik. akhirnya, terima kasih atas tantangan ini, saya belajar satu ton tentang bagaimana cara benar-benar menggunakan simpul alih-alih hanya mengambil perpustakaan dari mana-mana :)
user3080953
@ user3080953 terdengar bagus. Dengan pembaruan tersebut, Anda harus menjalankan karunia ini!
Dave
0

Node.js, 638 607 byte

Sekarang sudah baik dan benar-benar dipukuli (dan dalam bahasa yang sama), inilah jawaban tes saya:

R=require,P=process,s=R('net'),y=R('crypto'),w=0,C='create',W='write',D='data',B='hex',G=_=>a.generateKeys(B),Y=(t,m,g,f)=>g((c=y[C+t+'ipher']('aes192',w,k='')).on('readable',_=>k+=(c.read()||'').toString(m)).on('end',_=>f(k)))+c.end(),F=C+'DiffieHellman',X=s=>s.on(D,x=>(x+'').split(B).map(p=>p&&(w?Y('Dec','utf8',c=>c[W](p,B),console.log):P.stdin.on(D,m=>Y('C',B,c=>c[W](m),r=>s[W](r+B)),([p,q,r]=p.split(D),r&&s[W](G(a=y[F](q,B,r,B))),w=a.computeSecret(p,B))))));(R=P.argv)[3]?X(s.Socket()).connect(R[3],R[2]):s[C+'Server'](s=>X(s,a=y[F](2<<9))[W](G()+D+a.getPrime(B)+D+a.getGenerator(B)+B)).listen(R[2])

Atau dengan pembungkus:

R=require,P=process,s=R('net'),y=R('crypto'),w=0,C='create',W='write',D='data',B
='hex',G=_=>a.generateKeys(B),Y=(t,m,g,f)=>g((c=y[C+t+'ipher']('aes192',w,k=''))
.on('readable',_=>k+=(c.read()||'').toString(m)).on('end',_=>f(k)))+c.end(),F=C+
'DiffieHellman',X=s=>s.on(D,x=>(x+'').split(B).map(p=>p&&(w?Y('Dec','utf8',c=>c[
W](p,B),console.log):P.stdin.on(D,m=>Y('C',B,c=>c[W](m),r=>s[W](r+B)),([p,q,r]=p
.split(D),r&&s[W](G(a=y[F](q,B,r,B))),w=a.computeSecret(p,B))))));(R=P.argv)[3]?
X(s.Socket()).connect(R[3],R[2]):s[C+'Server'](s=>X(s,a=y[F](2<<9))[W](G()+D+a.
getPrime(B)+D+a.getGenerator(B)+B)).listen(R[2])

Pemakaian

Ini adalah implementasi server / klien; satu instantiation akan menjadi server, dan yang lainnya klien. Server diluncurkan dengan port tertentu, kemudian klien diarahkan ke port server. DH dapat membutuhkan waktu beberapa detik untuk pengaturan jika mesin Anda rendah pada entropi, sehingga pesan pertama mungkin sedikit tertunda.

MACHINE 1                       MACHINE 2
$ node e2e.js <port>            :
:                               $ node e2e.js <address> <port>
$ hello                         :
:                               : hello
:                               $ hi
: hi                            :

Kerusakan

s=require('net'),
y=require('crypto'),
w=0,                                      // Shared secret starts unknown
Y=(t,m,g,f)=>g(                           // Helper for encryption & decryption
  (c=y['create'+t+'ipher']('aes192',w,k=''))
  .on('readable',_=>k+=(c.read()||'').toString(m))
  .on('end',_=>f(k)))+c.end();
X=s=>s.on('data',x=>(x+'').split('TOKEN2').map(p=>
  p&&(w                                   // Have we completed handshake?
    ?Y('Dec','utf8',c=>c.write(p,'hex'),console.log) // Decrypt + print messages
    :                                     // Haven't completed handshake:
     process.stdin.on('data',m=>          //  Prepare to encrypt + send input
       Y('C','hex',c=>c.write(m),r=>s.write(r+'TOKEN2')),(
       [p,q,r]=p.split('TOKEN1'),         //  Split up DH data sent to us
       r&&                                //  Given DH details? (client)
          s.write(
            (a=y.createDiffieHellman(     //   Compute key pair...
              q,'hex',r,'hex')            //   ...using the received params
            ).generateKeys('hex')),       //   And send the public key
       w=a.computeSecret(p,'hex')         //  Compute shared secret
       //,console.log(w.toString('hex'))  //  Print if you want to verify no MITM
))))),
(R=process.argv)[3]                       // Are we running as a client?
  ?X(s.Socket()).connect(R[3],R[2])       // Connect & start chat
  :s.createServer(s=>                     // Start server. On connection:
    X(s,                                  //  Start chat,
      a=y.createDiffieHellman(1024))      //  Calc DiffieHellman,
    .write(                               //  Send public key & public DH details
      a.generateKeys('hex')+'TOKEN1'+
      a.getPrime('hex')+'TOKEN1'+
      a.getGenerator('hex')+'TOKEN2')
  ).listen(R[2])                          // Listen on requested port

Satu-satunya persyaratan untuk token adalah bahwa mereka mengandung setidaknya satu karakter non-hex, sehingga dalam kode minified konstanta string lain yang digunakan ( datadan hex).

Dave
sumber