高尔夫端到端加密


16

第一次挑战时,悬赏200分,并且至少连续3天保持不败。user3080953主张

最近有很多关于端到端加密的讨论,并且要求公司从其产品中删除它。我对它的是非不感兴趣,但是我想知道:代码能持续多久会给一家公司施加压力,使其不使用它?

这里的挑战是在两个网络系统之间实现Diffie Hellman密钥交换,然后允许用户使用生成的对称密钥来回通信。出于此任务的目的,不需要其他保护(例如,不需要循环密钥,验证身份,防止DoS等),并且您可以假定开放的Internet(您监听的任何端口都对所有人可用)。允许并鼓励使用内建函数

您可以选择以下两种模式之一:

  • 服务器和客户端:客户端连接到服务器,然后服务器或客户端可以向其他服务器发送消息。两者之间的第三方必须无法读取消息。一个示例流程可能是:
    1. 用户A启动服务器
    2. 用户B启动客户端并将其定向到用户A的服​​务器(例如,通过IP /端口),程序将打开连接
    3. 用户A的程序确认连接(可以选择先征求用户的同意)
    4. 用户B的程序开始生成DH机密,并将所需的数据(公钥,素数,生成器,以及您的实现所需的其他任何内容)发送给用户A
    5. 用户A的程序使用发送的数据完成共享密钥的生成,并将所需的数据(公钥)发送回用户B。从这一点来看,用户A可以输入消息(例如,通过stdin),该消息将被加密并发送给用户B(例如到标准输出)。
    6. 用户B的程序完成了共享机密的生成。从这一点来看,用户B可以向用户A发送消息。
  • 或:一台连接有两个客户端的服务器:每个客户端与服务器对话,服务器将其消息转发给另一个客户端。服务器本身(及其之间的任何第三方)必须无法读取消息。除了初始连接之外,该过程与第一个选项中描述的过程相同。

详细规则:

  • 您可以提供一个程序,也可以提供多个程序(例如,服务器和客户端)。您的分数是所有程序的总代码大小。
  • 从理论上讲,您的程序必须能够通过网络进行通信(但对于测试,可以使用localhost)。如果您选择的语言不支持联网,则可以将其与兼容的语言结合使用(例如,shell脚本)。在这种情况下,您的分数就是所有使用的所有语言的总代码大小。
  • Diffie Hellman密钥生成可以使用硬编码的“ p”和“ g”值。
  • 生成的共享密钥必须至少为1024位。
  • 共享密钥后,对称密钥加密的选择就由您决定,但是您一定不能选择目前已知对其有实际攻击能力的方法(例如,在不知道密钥的情况下,进行凯撒转移很容易进行反向操作) )。允许的算法示例:
    • AES(任何密钥大小)
    • RC4(从理论上讲是无效的,但是我没有提到任何实际的攻击,因此这里允许)
  • 用户A和B必须都能够交互地互相发送消息(双向通信)(例如,从stdin中读取行,不断提示或按下按钮之类的事件)。如果这样更容易,您可以假设是交替对话(即,用户发送消息后,他们必须等待响应才能发送下一条消息)
  • 语言内置允许的(没有必要写你自己的密码或网络的方法,如果他们已经支持)。
  • 底层的通信格式取决于您。
  • 上面给出的通信步骤是一个示例,但是您不必遵循这些步骤(只要共享了必要的信息,并且没有中间人能够计算出共享密钥或消息)
  • 如果事先不知道连接到服务器所需的详细信息(例如,如果它在随机端口上侦听),则必须打印这些详细信息。您可以假定机器的IP地址是已知的。
  • 不需要进行错误处理(例如,无效的地址,丢失的连接等)。
  • 挑战是代码高尔夫,因此以字节为单位的最短代码将获胜。

是否可以硬编码pg允许?
仅使用ASCII

我只能说@ASCII,硬编码高质量的p&g值被认为是可以的(除非开发人员恶意使用已知容易受到特定攻击的值)。因此,对于此挑战,可以(只要生成的机密至少为1024位即可)
Dave

Answers:


3

Node.js的(372 423 + 94 = 517 513字节)

打高尔夫球

添加了换行符以提高“可读性”。

chat.js(423 419个字节)

没有换行

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

换行

[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个字节)

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

不打高尔夫球

Node具有内置的联网和加密功能。它使用TCP进行网络连接(因为它比Node的HTTP接口更简单,并且可以很好地与流配合使用)。

我使用流密码(RC4)代替AES,以避免必须处理块大小。Wikipedia似乎认为它很容易受到攻击,因此,如果任何人对首选密码都有任何见识,那将是很好的。

运行node echo_server.js将在端口9上侦听的echo服务器。使用node chat.js <server IP>和运行该程序的两个实例node chat.js <server IP> 1(最后一个参数仅设置发送素数的那个)。每个实例都连接到回显服务器。第一条消息处理密钥生成,而后一条消息使用流密码。

回声服务器只是将所有内容发送回所有连接的客户端(原始客户端除外)。

客户

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

回声服务器

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

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

感谢Dave提供的所有提示和反馈!


1
不要为高尔夫版本增加可读性,这就是非高尔夫版本的用途。或者,如果这样做,请在换行之前删除分号,以使其长度相同。
mbomb007 '17

@ mbomb007的“可读性”主要是为了避免滚动。不幸的是,代码主体没有分号,因此无法正常工作。我认为快速查找和替换不会太麻烦。虽然一定会记住您的提示,以便将来发表评论!
user3080953 '17

@戴夫感谢您的所有反馈!我对使用Vanilla DH进行了更改,该长度实际上增加了很多长度,因为您需要交换素数,并且AES实际上可以作为即插即用的替代品,但是AES的问题在于,直到完成操作,任何东西都无法发送块,并且填充将是痛苦的。也RC4比AES128短
user3080953

1
我不确定它是否可以在网络上运行,但可能不会,我将其写在总线上,因此无法进行检查。新版本使用回显服务器代替。这也解决了超时问题。我试图避免使用服务器+客户端,但这是更好的形式。最后,感谢这个挑战,我学到了很多关于如何实际使用节点而不只是从各处获取库的知识:)
user3080953 '17

@ user3080953听起来不错。有了这些更新,您应该可以争取到赏金!
戴夫

0

Node.js,638字节

既然它已经被击败了(并且用相同的语言),这是我的测试答案:

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

或包装:

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

用法

这是服务器/客户端实现;一个实例将是服务器,另一个是客户端。使用特定的端口启动服务器,然后将客户端指向服务器的端口。如果您的机器熵不足,DH可能需要花费几秒钟来进行设置,因此第一条消息可能会延迟一些时间。

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

分解

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

标记的唯一要求是它们至少包含一个非十六进制字符,因此在缩略代码中使用了其他字符串常量(datahex)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.