Crittografia end-to-end del golf


16

Questa sfida ha una ricompensa di 200 punti per i primi a rispondere e rimanere imbattuti per almeno 3 giorni. Rivendicato dall'utente3080953 .

Ultimamente si parla molto della crittografia end-to-end e della pressione sulle aziende affinché la rimuovano dai loro prodotti. Non sono interessato ai diritti e ai torti di ciò, ma mi chiedevo: quanto può essere breve il codice che indurrebbe un'azienda a non essere utilizzata?

La sfida qui è implementare uno scambio di chiavi Diffie Hellman tra due sistemi in rete, quindi consentire agli utenti di comunicare avanti e indietro utilizzando la chiave simmetrica generata. Ai fini di questa attività, non sono necessarie altre protezioni (ad esempio, non è necessario scorrere la chiave, verificare le identità, proteggere da DoS, ecc.) E si può assumere una rete aperta (tutte le porte su cui si ascolta sono disponibili a tutti). L'uso dei builtin è consentito e incoraggiato!

Puoi scegliere uno dei due modelli:

  • Un server e un client: il client si connette al server, quindi il server o il client può inviare messaggi all'altro. Le terze parti tra i due non devono poter leggere i messaggi. Un flusso di esempio potrebbe essere:
    1. L'utente A avvia il server
    2. L'utente B avvia il client e lo indirizza al server dell'utente A (ad es. Tramite IP / porta), il programma apre una connessione
    3. Il programma dell'utente A riconosce la connessione (facoltativamente chiedendo prima all'utente il consenso)
    4. Il programma dell'utente B inizia la generazione di un segreto DH e invia i dati richiesti (chiave pubblica, prime, generatore, qualsiasi altra esigenza di implementazione) all'utente A
    5. Il programma dell'utente A utilizza i dati inviati per completare la generazione del segreto condiviso e restituisce i dati richiesti (chiave pubblica) all'utente B. Da questo punto, l'utente A può inserire messaggi (ad es. Tramite stdin) che verranno crittografati e inviati all'utente B (ad es. Per stdout).
    6. Il programma dell'utente B completa la generazione del segreto condiviso. Da questo punto, l'utente B può inviare messaggi all'utente A.
  • Oppure: un server con due client collegati ad esso: ogni client parla al server, che inoltra il proprio messaggio all'altro client. Il server stesso (e eventuali terze parti intermedie) non devono essere in grado di leggere i messaggi. Oltre alla connessione iniziale, il processo è lo stesso descritto nella prima opzione.

Regole dettagliate:

  • È possibile fornire un singolo programma o più programmi (ad es. Server e client). Il tuo punteggio è la dimensione totale del codice in tutti i programmi.
  • Il tuo programma deve essere teoricamente in grado di comunicare su una rete (ma per i test, localhost va bene). Se la tua lingua preferita non supporta la rete, puoi combinarla con qualcosa che lo fa (ad esempio uno script di shell); in questo caso il tuo punteggio è la dimensione totale del codice in tutte le lingue utilizzate.
  • La generazione della chiave Diffie Hellman può utilizzare valori "p" e "g" codificati.
  • La chiave condivisa generata deve essere almeno 1024 bit.
  • Una volta che la chiave è condivisa, la scelta della crittografia a chiave simmetrica dipende da te, ma non devi scegliere un metodo che è attualmente noto per avere un attacco pratico contro di essa (ad esempio, uno spostamento di Cesare è banale da annullare senza conoscere la chiave ). Esempi di algoritmi consentiti:
    • AES (qualsiasi dimensione chiave)
    • RC4 (teoricamente rotto, ma nessun attacco pratico di cui posso trovare menzione, quindi è consentito qui)
  • Gli utenti A e B devono entrambi essere in grado di inviare messaggi reciprocamente (comunicazione bidirezionale) in modo interattivo (ad es. Lettura di linee da stdin, richiesta continua o eventi come la pressione di un pulsante). Se è più semplice, puoi assumere una conversazione alternata (ad esempio, dopo che un utente ha inviato un messaggio, deve attendere una risposta prima di inviare il messaggio successivo)
  • I built-in di lingua sono consentiti (non è necessario scrivere i propri metodi crittografici o di rete se sono già supportati).
  • Il formato di comunicazione sottostante dipende da te.
  • I passaggi di comunicazione indicati sopra sono un esempio, ma non è necessario seguirli (purché le informazioni necessarie siano condivise e nessun intermediario è in grado di calcolare la chiave o i messaggi condivisi)
  • Se i dettagli necessari per connettersi al server non sono noti in anticipo (ad es. Se è in ascolto su una porta casuale), questi dettagli devono essere stampati. Si può presumere che l'indirizzo IP della macchina sia noto.
  • La gestione degli errori (ad es. Indirizzi non validi, connessioni perse, ecc.) Non è richiesta.
  • La sfida è il golf del codice, quindi vince il codice più breve in byte.

È hardcoding pe gpermesso?
ASCII,

@ ASCII-solo da quello che posso dire, codificare con cura i valori p & g di buona qualità è considerato corretto (a meno che lo sviluppatore non usi maliziosamente valori noti per essere vulnerabili a particolari attacchi). Quindi per questa sfida va bene (purché il segreto risultante sia almeno 1024 bit)
Dave,

Risposte:


3

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

golfed

Interruzioni di riga aggiunte per "leggibilità".

chat.js ( 423 419 byte)

Nessuna interruzione di riga

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

Interruzioni di riga

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

c=[],require("net").createServer(a=>{c.forEach(b=>{a.pipe(b),b.pipe(a)});c.push(a)}).listen(9);

Ungolfed

Il nodo ha funzionalità di rete e crittografia integrate. Questo utilizza TCP per il networking (perché è più semplice dell'interfaccia di Node per HTTP, e funziona bene con gli stream).

Uso un codice di flusso (RC4) anziché AES per evitare di dover gestire le dimensioni dei blocchi. Wikipedia sembra pensare che possa essere vulnerabile, quindi se qualcuno ha qualche idea su quali siano le cifre preferite, sarebbe fantastico.

Esegui il server echo node echo_server.jsche ascolterà sulla porta 9. Esegui due istanze di questo programma con node chat.js <server IP>e node chat.js <server IP> 1(l'ultimo argomento imposta solo quello che invia un numero primo). Ogni istanza si connette al server echo. Il primo messaggio gestisce la generazione della chiave, mentre i messaggi successivi utilizzano il codice di flusso.

Il server echo restituisce tutto a tutti i client connessi tranne l'originale.

Cliente

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 Echo

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

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

Grazie Dave per tutti i suggerimenti + feedback!


1
Non aggiungere leggibilità alla versione golfata, ecco a cosa serve la versione non golfata. O se lo fai, rimuovi i punti e virgola prima che la riga si interrompa, quindi ha la stessa lunghezza.
mbomb007,

@ mbomb007 la "leggibilità" è principalmente per evitare di dover scorrere. sfortunatamente, il corpo del codice non ha punti e virgola, quindi non funziona. Ho pensato che una rapida ricerca e sostituzione non sarebbe stata troppo onerosa. terrò sicuramente a mente il tuo consiglio per commenti futuri!
user3080953

@Dave grazie per tutto il feedback! Ho apportato modifiche all'utilizzo di DH vaniglia, che in realtà ha aggiunto un po 'di lunghezza perché è necessario scambiare anche numeri primi AES in realtà funziona come un rimpiazzo drop-in, ma il problema con AES è che nulla viene inviato fino al completamento un blocco e l'imbottitura sarebbe un dolore. anche rc4 è più corto di aes128
user3080953

1
Non ero sicuro che avrebbe funzionato su una rete, ma probabilmente non funzionerà, e l'ho scritto su un bus, quindi non avevo modo di controllarlo. la nuova versione utilizza invece un server echo. Questo risolve anche il problema del timeout. Stavo cercando di evitare un server + client, ma è molto meglio. infine, grazie per questa sfida, ho imparato una tonnellata su come usare realmente il nodo invece di afferrare le librerie da ogni parte :)
user3080953

@ user3080953 suona bene. Con quegli aggiornamenti dovresti essere in corsa per la taglia!
Dave,

0

Node.js, 638 607 byte

Ora che è stato ben battuto (e nella stessa lingua), ecco la mia risposta di prova:

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])

O con avvolgimento:

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])

uso

Questa è un'implementazione server / client; un'istanza sarà il server e l'altra il client. Il server viene avviato con una porta specifica, quindi il client viene puntato sulla porta del server. L'installazione di DH può richiedere alcuni secondi se la macchina ha poca entropia, quindi i primi messaggi potrebbero essere leggermente ritardati.

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

Abbattersi

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

L'unico requisito per i token è che contengano almeno un carattere non esadecimale, quindi nel codice minimizzato vengono utilizzate altre costanti di stringa ( datae hex).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.