Cryptage de bout en bout pour le golf

16

Ce défi comporte une prime de 200 points pour le premier à répondre et reste invaincu pendant au moins 3 jours. Réclamé par user3080953 .

Il y a eu beaucoup de discussions ces derniers temps sur le chiffrement de bout en bout, et des pressions sur les entreprises pour le supprimer de leurs produits. Je ne suis pas intéressé par les droits et les torts de cela, mais je me suis demandé: à quel point le code peut-il être court qui inciterait une entreprise à ne pas l'utiliser?

Le défi ici est de mettre en œuvre un échange de clés Diffie Hellman entre deux systèmes en réseau, puis de permettre aux utilisateurs de communiquer dans les deux sens en utilisant la clé symétrique générée. Aux fins de cette tâche, aucune autre protection n'est requise (par exemple, pas besoin de faire défiler la clé, de vérifier les identités, de se protéger contre le DoS, etc.) et vous pouvez supposer un Internet ouvert (tous les ports que vous écoutez sont disponibles pour tout le monde). L'utilisation de builtins est autorisée et encouragée!

Vous pouvez choisir l'un des deux modèles:

  • Un serveur et un client: le client se connecte au serveur, puis le serveur ou le client peut envoyer des messages à l'autre. Les tiers entre les deux ne doivent pas être en mesure de lire les messages. Un exemple de flux pourrait être:
    1. L'utilisateur A lance le serveur
    2. L'utilisateur B lance le client et le dirige vers le serveur de l'utilisateur A (par exemple via IP / port), le programme ouvre une connexion
    3. Le programme de l'utilisateur A reconnaît la connexion (en demandant éventuellement à l'utilisateur le consentement préalable)
    4. Le programme de l'utilisateur B commence la génération d'un secret DH et envoie les données requises (clé publique, prime, générateur, tout ce dont votre implémentation a besoin) à l'utilisateur A
    5. Le programme de l'utilisateur A utilise les données envoyées pour terminer la génération du secret partagé et renvoie les données requises (clé publique) à l'utilisateur B. À partir de ce moment, l'utilisateur A peut saisir des messages (par exemple via stdin) qui seront cryptés et envoyés à l'utilisateur B (par exemple à stdout).
    6. Le programme de l'utilisateur B termine la génération du secret partagé. À partir de ce moment, l'utilisateur B peut envoyer des messages à l'utilisateur A.
  • Ou: Un serveur avec deux clients connectés: chaque client parle au serveur, qui transmet son message à l'autre client. Le serveur lui-même (et tout tiers intermédiaire) ne doit pas pouvoir lire les messages. Autre que la connexion initiale, le processus est le même que celui décrit dans la première option.

Règles détaillées:

  • Vous pouvez fournir un seul programme ou plusieurs programmes (par exemple, serveur et client). Votre score est la taille totale du code dans tous les programmes.
  • Votre programme doit être théoriquement capable de communiquer sur un réseau (mais pour les tests, localhost est très bien). Si la langue de votre choix ne prend pas en charge la mise en réseau, vous pouvez la combiner avec quelque chose qui le fait (par exemple un script shell); dans ce cas, votre score est la taille totale du code dans toutes les langues utilisées.
  • La génération de clés Diffie Hellman peut utiliser des valeurs "p" et "g" codées en dur.
  • La clé partagée générée doit être d'au moins 1024 bits.
  • Une fois que la clé est partagée, le choix du chiffrement à clé symétrique vous appartient, mais vous ne devez pas choisir une méthode qui est actuellement connue pour avoir une attaque pratique contre elle (par exemple, un changement de césar est trivial à inverser sans connaissance de la clé ). Exemples d'algorithmes autorisés:
    • AES (n'importe quelle taille de clé)
    • RC4 (théoriquement cassé, mais pas d'attaques pratiques que je puisse trouver, donc c'est autorisé ici)
  • Les utilisateurs A et B doivent tous deux être en mesure de s’envoyer des messages (communication bidirectionnelle) de manière interactive (par exemple, lecture de lignes depuis stdin, invite permanente ou événements tels que l’appui sur un bouton). Si cela facilite les choses, vous pouvez supposer une conversation alternée (c'est-à-dire qu'après qu'un utilisateur a envoyé un message, il doit attendre une réponse avant d'envoyer son prochain message)
  • Les extensions de langage sont autorisées (pas besoin d'écrire vos propres méthodes cryptographiques ou réseau si elles sont déjà prises en charge).
  • Le format de communication sous-jacent dépend de vous.
  • Les étapes de communication données ci-dessus sont un exemple, mais vous n'êtes pas obligé de les suivre (tant que les informations nécessaires sont partagées et qu'aucun intermédiaire ne peut calculer la clé ou les messages partagés)
  • Si les détails nécessaires pour se connecter à votre serveur ne sont pas connus à l'avance (par exemple, s'il écoute sur un port aléatoire), ces détails doivent être imprimés. Vous pouvez supposer que l'adresse IP de la machine est connue.
  • La gestion des erreurs (par exemple, adresses invalides, connexions perdues, etc.) n'est pas requise.
  • Le défi est le golf de code, donc le code le plus court en octets gagne.
Dave
la source
Le codage en dur est- pil gautorisé?
ASCII uniquement le
@ ASCII uniquement d'après ce que je peux dire, les valeurs p & g de bonne qualité codées en dur sont considérées comme correctes (à moins que le développeur utilise malicieusement des valeurs connues pour être vulnérables à des attaques particulières). Donc, pour ce défi, c'est OK (tant que le secret résultant est d'au moins 1024 bits)
Dave

Réponses:

3

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

Golfé

Ajout de sauts de ligne pour la "lisibilité".

chat.js ( 423 419 octets)

Aucun saut de ligne

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

Sauts de ligne

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

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

Non golfé

Le nœud possède des capacités de mise en réseau et de chiffrement intégrées. Cela utilise TCP pour la mise en réseau (car il est plus simple que l'interface de Node pour HTTP et il fonctionne bien avec les flux).

J'utilise un chiffrement de flux (RC4) au lieu d'AES pour éviter d'avoir à gérer des tailles de bloc. Wikipédia semble penser qu'il peut être vulnérable, donc si quelqu'un a une idée des chiffres à privilégier, ce serait formidable.

Exécutez le serveur d'écho node echo_server.jsqui écoutera sur le port 9. Exécutez deux instances de ce programme avec node chat.js <server IP>et node chat.js <server IP> 1(le dernier argument définit simplement lequel envoie un premier). Chaque instance se connecte au serveur d'écho. Le premier message gère la génération de clés et les messages suivants utilisent le chiffrement de flux.

Le serveur d'écho renvoie tout simplement à tous les clients connectés, à l'exception de l'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
        }
    });
})

Serveur d'écho

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

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

Merci Dave pour tous les conseils et commentaires!

user3080953
la source
1
N'ajoutez pas de lisibilité à la version golfée, c'est à cela que sert la version non golfée. Ou si vous faites cela, supprimez les points-virgules avant les sauts de ligne, de sorte qu'il soit de la même longueur.
mbomb007
@ mbomb007 la "lisibilité" est surtout pour éviter d'avoir à faire défiler. malheureusement, le corps du code n'a pas de point-virgule, donc cela ne fonctionne pas. J'ai pensé qu'une recherche et un remplacement rapides ne seraient pas trop onéreux. gardera certainement votre astuce à l'esprit pour de futurs commentaires!
user3080953
@Dave merci pour tous les commentaires! J'ai apporté des modifications à l'utilisation de vanilla DH, qui a en fait ajouté un peu de longueur car vous devez également échanger des nombres premiers AES fonctionne en fait comme un remplacement, mais le problème avec AES est que rien n'envoie tant que vous n'avez pas terminé un bloc, et le rembourrage serait une douleur. aussi rc4 est plus court que aes128
user3080953
1
Je ne savais pas si cela fonctionnerait sur un réseau, mais ce ne sera probablement pas le cas, et je l'ai écrit sur un bus, donc je n'avais aucun moyen de vérifier. la nouvelle version utilise un serveur d'écho à la place. Cela résout également le problème de délai d'attente. J'essayais d'éviter un serveur + client, mais c'est une bien meilleure forme. enfin, merci pour ce défi, j'ai appris une tonne sur la façon d'utiliser réellement le nœud au lieu de simplement récupérer les bibliothèques de partout :)
user3080953
@ user3080953 sonne bien. Avec ces mises à jour, vous devriez être en lice pour la prime!
Dave
0

Node.js, 638 607 octets

Maintenant qu'il est bel et bien battu (et dans la même langue), voici ma réponse de test:

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

Ou avec emballage:

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

Usage

Il s'agit d'une implémentation serveur / client; une instanciation sera le serveur, et l'autre le client. Le serveur est lancé avec un port spécifique, puis le client pointe vers le port du serveur. DH peut prendre quelques secondes à configurer si votre machine est faible en entropie, donc les premiers messages peuvent être un peu retardés.

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

Panne

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

La seule exigence pour les jetons est qu'ils contiennent au moins un caractère non hexadécimal, donc dans le code minifié, d'autres constantes de chaîne sont utilisées ( dataet hex).

Dave
la source