Cifrado de golf de extremo a extremo

16

Este desafío conlleva una recompensa de 200 puntos para que el primero responda y permanezca invicto durante al menos 3 días. Reclamado por el usuario 3080953 .

Últimamente se habla mucho sobre el cifrado de extremo a extremo y se presiona a las empresas para que lo eliminen de sus productos. No estoy interesado en los aciertos y errores de eso, pero me preguntaba: ¿qué tan corto puede ser el código que presionaría a una empresa para que no lo use?

El desafío aquí es implementar un intercambio de claves Diffie Hellman entre dos sistemas en red, luego permitir a los usuarios comunicarse de un lado a otro utilizando la clave simétrica generada. Para el propósito de esta tarea, no se requieren otras protecciones (por ejemplo, no es necesario cambiar la clave, verificar identidades, proteger contra DoS, etc.) y puede asumir un Internet abierto (cualquier puerto que escuche está disponible para todos). ¡El uso de builtins está permitido y fomentado!

Puede elegir uno de dos modelos:

  • Un servidor y un cliente: el cliente se conecta al servidor, luego el servidor o el cliente pueden enviar mensajes al otro. Los terceros entre los dos deben ser incapaces de leer los mensajes. Un ejemplo de flujo podría ser:
    1. El usuario A inicia el servidor
    2. El usuario B inicia el cliente y lo dirige al servidor del usuario A (por ejemplo, a través de IP / puerto), el programa abre una conexión
    3. El programa del usuario A reconoce la conexión (opcionalmente, solicita primero el consentimiento del usuario)
    4. El programa del usuario B comienza a generar un secreto DH y envía los datos requeridos (clave pública, primo, generador, cualquier otra cosa que su implementación necesite) al usuario A
    5. El programa del usuario A utiliza los datos enviados para completar la generación del secreto compartido y envía los datos requeridos (clave pública) al usuario B. Desde este punto, el usuario A puede ingresar mensajes (por ejemplo, a través de stdin) que serán encriptados y enviados al usuario B (por ejemplo, stdout).
    6. El programa del usuario B completa la generación del secreto compartido. Desde este punto, el usuario B puede enviar mensajes al usuario A.
  • O bien: un servidor con dos clientes conectados: cada cliente habla con el servidor, que reenvía su mensaje al otro cliente. El servidor en sí (y cualquier tercero intermedio) no debe poder leer los mensajes. Además de la conexión inicial, el proceso es el mismo que el descrito en la primera opción.

Reglas detalladas:

  • Puede proporcionar un solo programa o múltiples programas (por ejemplo, servidor y cliente). Su puntaje es el tamaño total del código en todos los programas.
  • Su programa debe ser teóricamente capaz de comunicarse a través de una red (pero para las pruebas, localhost está bien). Si su idioma de elección no admite redes, puede combinarlo con algo que sí lo haga (por ejemplo, un script de shell); en este caso, su puntaje es el tamaño total del código en todos los idiomas utilizados.
  • La generación de claves Diffie Hellman puede usar valores codificados "p" y "g".
  • La clave compartida generada debe ser de al menos 1024 bits.
  • Una vez que se comparte la clave, la elección del cifrado de clave simétrica depende de usted, pero no debe elegir un método que actualmente se sabe que tiene un ataque práctico contra ella (por ejemplo, un cambio César es trivial para revertir sin conocer la clave ) Ejemplo de algoritmos permitidos:
    • AES (cualquier tamaño de clave)
    • RC4 (teóricamente roto, pero no hay ataques prácticos de los que pueda encontrar mención, por lo que está permitido aquí)
  • Los usuarios A y B deben poder enviarse mensajes entre sí (comunicación bidireccional) de manera interactiva (p. Ej., Leer líneas de stdin, avisos continuos o eventos como presionar un botón). Si lo hace más fácil, puede asumir una conversación alterna (es decir, después de que un usuario envía un mensaje, debe esperar una respuesta antes de enviar su próximo mensaje)
  • Se permiten los idiomas incorporados (no es necesario escribir sus propios métodos criptográficos o de redes si ya son compatibles).
  • El formato de comunicación subyacente depende de usted.
  • Los pasos de comunicación dados anteriormente son un ejemplo, pero no es necesario que los siga (siempre y cuando se comparta la información necesaria y ningún intermediario pueda calcular la clave o los mensajes compartidos)
  • Si los detalles necesarios para conectarse a su servidor no se conocen de antemano (por ejemplo, si escucha en un puerto aleatorio), estos detalles deben imprimirse. Puede suponer que se conoce la dirección IP de la máquina.
  • No se requiere el manejo de errores (por ejemplo, direcciones no válidas, conexiones perdidas, etc.).
  • El desafío es el código de golf, por lo que gana el código más corto en bytes.
Dave
fuente
¿Está codificado py gpermitido?
Solo ASCII
@ Solo ASCII, por lo que puedo decir, codificar valores de p & g de buena calidad se considera correcto (a menos que el desarrollador use maliciosamente valores que se sabe que son vulnerables a ataques particulares). Entonces, para este desafío está bien (siempre que el secreto resultante sea de al menos 1024 bits)
Dave

Respuestas:

3

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

Golfed

Saltos de línea agregados para "legibilidad".

chat.js ( 423 419 bytes)

Sin saltos de línea

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

Saltos de línea

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

Sin golf

Node tiene capacidades integradas de red y criptografía. Esto utiliza TCP para la creación de redes (porque es más simple que la interfaz de Node para HTTP, y funciona muy bien con las transmisiones).

Utilizo un cifrado de flujo (RC4) en lugar de AES para evitar tener que lidiar con los tamaños de bloque. Wikipedia parece pensar que puede ser vulnerable, por lo que si alguien tiene alguna idea de cuáles son las cifras preferidas, sería genial.

Ejecute el servidor echo node echo_server.jsque escuchará en el puerto 9. Ejecute dos instancias de este programa con node chat.js <server IP>y node chat.js <server IP> 1(el último argumento solo establece cuál envía un primo). Cada instancia se conecta al servidor echo. El primer mensaje maneja la generación de claves, y los mensajes posteriores usan el cifrado de flujo.

El servidor de eco simplemente envía todo de vuelta a todos los clientes conectados, excepto el original.

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

Servidor de eco

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

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

Gracias Dave por todos los consejos + comentarios!

usuario3080953
fuente
1
No agregue legibilidad a la versión de golf, para eso está la versión sin golf. O si hace eso, elimine los puntos y comas antes de que se rompa la línea, de modo que tenga la misma longitud.
mbomb007
@ mbomb007 la "legibilidad" es principalmente para evitar tener que desplazarse. desafortunadamente, el cuerpo del código no tiene punto y coma, por lo que no funciona. Supuse que encontrar y reemplazar rápidamente no sería demasiado oneroso. ¡sin duda tendrá en cuenta su consejo para futuros comentarios!
user3080953
@Dave gracias por todos los comentarios! He realizado cambios para usar Vanilla DH, que en realidad agregó bastante longitud porque también necesitas intercambiar primos. AES realmente funciona como un reemplazo directo, pero el problema con AES es que nada se envía hasta que hayas completado un bloque, y el relleno sería un dolor. también rc4 es más corto que aes128
user3080953
1
No estaba seguro de si funcionaría en una red, pero probablemente no, y lo escribí en un autobús, así que no tenía forma de verificarlo. la nueva versión usa un servidor echo en su lugar. Esto también resuelve el problema del tiempo de espera. Intenté evitar un servidor + cliente, pero es mucho mejor. Finalmente, gracias por este reto, he aprendido una tonelada sobre cómo utilizar realmente nodo en lugar de sólo sacar bibliotecas de todas partes :)
user3080953
@ user3080953 suena bien. ¡Con esas actualizaciones deberías estar corriendo por la recompensa!
Dave
0

Nodo.js, 638 607 bytes

Ahora que ha sido bien derrotado (y en el mismo idioma), aquí está mi respuesta de prueba:

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 envoltura:

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

Esta es una implementación de servidor / cliente; una instancia será el servidor y la otra el cliente. El servidor se inicia con un puerto específico, luego el cliente apunta al puerto del servidor. DH puede tardar unos segundos en configurarse si su máquina tiene poca entropía, por lo que los primeros mensajes pueden retrasarse un poco.

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

Descompostura

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

El único requisito para los tokens es que contengan al menos un carácter no hexadecimal, por lo que en el código minificado se usan otras constantes de cadena ( datay hex).

Dave
fuente