Golf End-to-End-Verschlüsselung


16

Diese Herausforderung bringt eine Prämie von 200 Punkten mit sich, wenn der Erste antwortet und mindestens 3 Tage ungeschlagen bleibt. Wird von user3080953 beansprucht .

In letzter Zeit wird viel über End-to-End-Verschlüsselung geredet und Druck auf Unternehmen ausgeübt, sie aus ihren Produkten zu entfernen. Ich bin nicht an den Rechten und Unrechten daran interessiert, aber ich habe mich gefragt: Wie kurz kann Code sein, der ein Unternehmen dazu bringt, ihn nicht zu verwenden?

Hier besteht die Herausforderung darin, einen Diffie Hellman-Schlüsselaustausch zwischen zwei vernetzten Systemen zu implementieren und den Benutzern dann die Möglichkeit zu geben, mithilfe des generierten symmetrischen Schlüssels hin und her zu kommunizieren. Für diese Aufgabe sind keine weiteren Schutzmaßnahmen erforderlich (z. B. kein Durchlaufen des Schlüssels, Überprüfen der Identität, Schutz vor DoS usw.) und Sie können von einem offenen Internet ausgehen (alle Ports, die Sie abhören, sind für alle verfügbar). Die Verwendung von Builtins ist erlaubt und erwünscht !

Sie können eines von zwei Modellen wählen:

  • Ein Server und ein Client: Der Client stellt eine Verbindung zum Server her, und der Server oder Client kann Nachrichten an den anderen senden. Zwischengeschaltete Dritte müssen die Nachrichten nicht lesen können. Ein Beispielablauf könnte sein:
    1. Benutzer A startet den Server
    2. Benutzer B startet den Client und leitet ihn an den Server von Benutzer A weiter (z. B. über IP / Port). Das Programm stellt eine Verbindung her
    3. Das Programm von Benutzer A bestätigt die Verbindung (optional wird der Benutzer zuerst um Zustimmung gebeten)
    4. Das Programm von Benutzer B beginnt mit der Generierung eines DH-Geheimnisses und sendet die erforderlichen Daten (öffentlicher Schlüssel, Prime, Generator, alles andere, was Ihre Implementierung benötigt) an Benutzer A
    5. Das Programm von Benutzer A verwendet die gesendeten Daten, um die Erstellung des gemeinsamen Geheimnisses abzuschließen, und sendet die erforderlichen Daten (öffentlichen Schlüssel) an Benutzer B zurück. Von diesem Punkt an kann Benutzer A Nachrichten eingeben (z. B. über stdin), die verschlüsselt und an Benutzer gesendet werden B (zB zu stdout).
    6. Das Programm von Benutzer B schließt die Erzeugung des gemeinsamen Geheimnisses ab. Von diesem Punkt aus kann Benutzer B Nachrichten an Benutzer A senden.
  • Oder: Ein Server mit zwei verbundenen Clients: Jeder Client spricht mit dem Server, der seine Nachricht an den anderen Client weiterleitet. Der Server selbst (und alle dazwischen liegenden Drittanbieter) müssen die Nachrichten nicht lesen können. Abgesehen von der ursprünglichen Verbindung entspricht der Vorgang dem in der ersten Option beschriebenen.

Detaillierte Regeln:

  • Sie können ein einzelnes Programm oder mehrere Programme (z. B. Server und Client) bereitstellen. Ihre Punktzahl ist die Gesamtcodegröße aller Programme.
  • Ihr Programm muss theoretisch in der Lage sein, über ein Netzwerk zu kommunizieren (zum Testen ist localhost jedoch in Ordnung). Wenn Ihre Sprache die Vernetzung nicht unterstützt, können Sie sie mit einer anderen Sprache kombinieren (z. B. einem Shell-Skript). In diesem Fall ist Ihre Punktzahl die Gesamtgröße des Codes in allen verwendeten Sprachen.
  • Die Diffie Hellman-Schlüsselgenerierung kann fest codierte "p" - und "g" -Werte verwenden.
  • Der generierte gemeinsame Schlüssel muss mindestens 1024 Bit umfassen.
  • Sobald der Schlüssel freigegeben ist, liegt die Wahl der symmetrischen Schlüsselverschlüsselung bei Ihnen. Sie dürfen jedoch keine Methode auswählen, die derzeit als praktisch erwiesen ist (z. B. ist es trivial, eine Cäsar-Verschiebung rückgängig zu machen, ohne den Schlüssel zu kennen ). Beispiel zulässige Algorithmen:
    • AES (beliebige Schlüsselgröße)
    • RC4 (theoretisch defekt, aber keine praktischen Angriffe, die ich erwähnen kann, also ist es hier zulässig)
  • Benutzer A und B müssen in der Lage sein, interaktiv Nachrichten aneinander zu senden (wechselseitige Kommunikation) (z. B. Lesen von Zeilen von stdin, ständige Eingabeaufforderung oder Ereignisse wie Drücken einer Taste). Wenn es einfacher ist, können Sie eine abwechselnde Konversation annehmen (dh nachdem ein Benutzer eine Nachricht gesendet hat, muss er auf eine Antwort warten, bevor er seine nächste Nachricht sendet).
  • Spracheinbauten sind zulässig (Sie müssen keine eigenen kryptografischen oder Netzwerkmethoden schreiben, wenn diese bereits unterstützt werden).
  • Das zugrunde liegende Kommunikationsformat liegt bei Ihnen.
  • Die oben angegebenen Kommunikationsschritte sind ein Beispiel, aber Sie müssen sie nicht befolgen (solange die erforderlichen Informationen geteilt werden und kein Mittelsmann in der Lage ist, den geteilten Schlüssel oder die geteilten Nachrichten zu berechnen).
  • Wenn die für die Verbindung zu Ihrem Server erforderlichen Details nicht im Voraus bekannt sind (z. B. wenn er einen zufälligen Port abhört), müssen diese Details ausgedruckt werden. Sie können davon ausgehen, dass die IP-Adresse des Geräts bekannt ist.
  • Eine Fehlerbehandlung (z. B. ungültige Adressen, verlorene Verbindungen usw.) ist nicht erforderlich.
  • Die Herausforderung ist Codegolf, also gewinnt der kürzeste Code in Bytes.

Ist Hardcoding pund gerlaubt?
Nur ASCII

@ ASCII-only Nach meinem Kenntnisstand wird es als in Ordnung angesehen, P & G-Werte von guter Qualität hart zu codieren (es sei denn, der Entwickler verwendet in böswilliger Absicht Werte, die bekanntermaßen für bestimmte Angriffe anfällig sind). Für diese Herausforderung ist es also in Ordnung (solange das resultierende Geheimnis mindestens 1024 Bit beträgt)
Dave,

Antworten:


3

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

Golf gespielt

Zeilenumbrüche für "Lesbarkeit" hinzugefügt.

chat.js ( 423 419 bytes)

Keine Zeilenumbrüche

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

Zeilenumbrüche

[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

Der Knoten verfügt über integrierte Netzwerk- und Kryptofunktionen. Dies verwendet TCP für die Vernetzung (da es einfacher als die Schnittstelle von Node für HTTP ist und sich gut mit Streams abspielen lässt).

Ich verwende eine Stream-Chiffre (RC4) anstelle von AES, um nicht mit Blockgrößen umgehen zu müssen. Wikipedia scheint der Meinung zu sein, dass es verwundbar sein kann. Wenn also jemand weiß, welche Chiffren bevorzugt werden, wäre das großartig.

Führen Sie den Echoserver aus, node echo_server.jsder auf Port 9 lauscht. Führen Sie zwei Instanzen dieses Programms mit node chat.js <server IP>und aus node chat.js <server IP> 1(das letzte Argument legt nur fest, welche eine Primzahl sendet). Jede Instanz stellt eine Verbindung zum Echoserver her. Die erste Nachricht behandelt die Schlüsselerzeugung, und nachfolgende Nachrichten verwenden die Stream-Verschlüsselung.

Der Echoserver sendet einfach alles bis auf das Original an alle verbundenen Clients zurück.

Klient

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

Echoserver

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

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

Danke Dave für all die Tipps und das Feedback!


1
Fügen Sie der Golf-Version keine Lesbarkeit hinzu, dafür ist die Ungolf-Version gedacht. Oder entfernen Sie in diesem Fall die Semikolons, bevor die Zeile umbrochen wird, damit sie dieselbe Länge haben.
mbomb007

@ mbomb007 die "lesbarkeit" ist meistens zu vermeiden scrollen zu müssen. Leider enthält der Hauptteil des Codes keine Semikolons, sodass dies nicht funktioniert. Ich dachte, ein schnelles Finden und Ersetzen wäre nicht zu lästig. Ich werde jedoch auf jeden Fall Ihren Tipp für zukünftige Kommentare berücksichtigen!
user3080953

@ Dave danke für alle Rückmeldungen! Ich habe Änderungen an der Verwendung von Vanille-DH vorgenommen, die tatsächlich einiges an Länge hinzugefügt haben, da Sie auch Primzahlen austauschen müssen. AES funktioniert tatsächlich als Ersatz für Drop-Ins, aber das Problem bei AES ist, dass nichts gesendet wird, bis Sie fertig sind ein Block, und Polsterung wäre ein Schmerz. auch rc4 ist kürzer als aes128
user3080953

1
Ich war mir nicht sicher, ob es über ein Netzwerk funktionieren würde, aber wahrscheinlich auch nicht. Ich schrieb es in einem Bus, sodass ich keine Möglichkeit hatte, es zu überprüfen. Die neue Version verwendet stattdessen einen Echoserver. Dies löst auch das Timeout-Problem. Ich habe versucht, einen Server + Client zu vermeiden, aber es ist eine viel bessere Form. Schließlich, danke für diese Herausforderung, habe ich eine Tonne gelernt, wie man Knoten tatsächlich benutzt, anstatt nur Bibliotheken von überall zu
greifen

@ user3080953 hört sich gut an. Mit diesen Updates solltest du im Rennen um die Bounty sein!
Dave

0

Node.js, 638 607 Bytes

Jetzt, da es gut und wirklich geschlagen wurde (und in derselben Sprache), ist hier meine Testantwort:

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

Oder mit Umhüllung:

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

Verwendung

Dies ist eine Server / Client-Implementierung. Eine Instanziierung ist der Server und die andere der Client. Der Server wird mit einem bestimmten Port gestartet, und der Client wird auf den Port des Servers verwiesen. Die Einrichtung des DH kann einige Sekunden dauern, wenn die Entropie Ihres Computers niedrig ist, sodass sich die ersten Meldungen möglicherweise etwas verzögern.

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

Nervenzusammenbruch

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

Die einzige Voraussetzung für die Token ist, dass sie mindestens ein Nicht-Hex-Zeichen enthalten. Daher werden im minimierten Code andere Zeichenfolgenkonstanten verwendet ( dataundhex ) verwendet.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.