ゴルフのエンドツーエンド暗号化


16

このチャレンジには、最初に答えて少なくとも3日間無敵のままでいるために200ポイントの報奨金が課せられます。user3080953が主張しています

最近、エンドツーエンドの暗号化について多くの話があり、製品からそれを削除するように企業に圧力をかけています。私はその権利と不正には興味がありませんが、私は疑問に思いました。コードをどれだけ短くすれば、会社はそれを使わないように圧力をかけるでしょうか?

ここでの課題は、ネットワーク化された2つのシステム間でDiffie Hellmanキー交換を実装し、生成された対称キーを使用してユーザーがやり取りできるようにすることです。このタスクの目的のために、他の保護は必要ありません(たとえば、キーの循環、IDの確認、DoSからの保護など)。インターネットを開くことができます(リッスンするポートはすべてのユーザーが利用できます)。組み込みの使用が許可され、推奨されています!

次の2つのモデルのいずれかを選択できます。

  • サーバーとクライアント:クライアントはサーバーに接続し、サーバーまたはクライアントは他方にメッセージを送信できます。2つの間のサードパーティは、メッセージを読むことができない必要があります。フローの例は次のとおりです。
    1. ユーザーAがサーバーを起動します
    2. ユーザーBはクライアントを起動し、それをユーザーAのサーバーに転送します(IP /ポート経由など)。プログラムは接続を開きます
    3. ユーザーAのプログラムは接続を確認します(オプションでユーザーに最初に同意を求めます)
    4. ユーザーBのプログラムはDHシークレットの生成を開始し、必要なデータ(公開キー、プライム、ジェネレーター、実装に必要なもの)をユーザーAに送信します。
    5. ユーザーAのプログラムは、送信されたデータを使用して共有シークレットの生成を完了し、必要なデータ(公開キー)をユーザーBに送り返します。この時点から、ユーザーAはメッセージを入力できます。 B(例:標準出力)。
    6. ユーザーBのプログラムは、共有秘密の生成を完了します。この時点から、ユーザーBはユーザーAにメッセージを送信できます。
  • または:2つのクライアントが接続されたサーバー:各クライアントはサーバーと通信し、サーバーはメッセージを他のクライアントに転送します。サーバー自体(およびその間のサードパーティ)は、メッセージを読み取れないようにする必要があります。初期接続以外のプロセスは、最初のオプションで説明したものと同じです。

詳細なルール:

  • 単一のプログラム、または複数のプログラム(サーバーとクライアントなど)を提供できます。スコアは、すべてのプログラムの合計コードサイズです。
  • プログラムは理論的にはネットワークを介して通信できる必要があります(ただし、テストにはlocalhostで十分です)。選択した言語がネットワークをサポートしていない場合は、サポートしているもの(シェルスクリプトなど)と組み合わせることができます。この場合、スコアは使用されるすべての言語にわたるコードサイズの合計です。
  • Diffie Hellmanキー生成では、ハードコードされた「p」および「g」値を使用できます。
  • 生成された共有キーは、少なくとも1024ビットである必要があります。
  • キーが共有されたら、対称キー暗号化の選択はあなた次第ですが、それに対して実際的な攻撃があることが現在知られている方法を選択してはなりません)。許可されたアルゴリズムの例:
    • AES(任意のキーサイズ)
    • RC4(理論的には壊れていますが、言及できる実用的な攻撃はないため、ここでは許容されます)
  • ユーザーAとBはどちらも、相互にメッセージを双方向に送信できる必要があります(双方向通信)(たとえば、stdinからの行の読み取り、継続的なプロンプト、ボタンを押すなどのイベント)。簡単になった場合は、交互の会話を想定できます(つまり、ユーザーがメッセージを送信した後、次のメッセージを送信する前に応答を待つ必要があります)
  • 言語の組み込み許可されています(既にサポートされている場合、独自の暗号化またはネットワークメソッドを記述する必要はありません)。
  • 基礎となる通信形式はユーザー次第です。
  • 上記の通信手順は一例ですが、従う必要はありません(必要な情報が共有され、中間キーが共有キーまたはメッセージを計算できない場合)
  • サーバーへの接続に必要な詳細が事前にわからない場合(ランダムポートでリッスンする場合など)、これらの詳細を印刷する必要があります。マシンのIPアドレスがわかっていると想定できます。
  • エラー処理(無効なアドレス、接続の切断など)は必要ありません。
  • 課題はコードゴルフであるため、バイト単位の最短コードが勝ちます。

ハードコーディングされたpg認め?
ASCIIのみ

私が知ることができるのは@ASCIIのみであり、高品質のp&g値をハードコーディングすることは問題ありません(開発者が特定の攻撃に対して脆弱であることが知られている値を悪意を持って使用しない限り)。したがって、このチャレンジでは問題ありません(結果のシークレットが少なくとも1024ビットである限り)
デイブ

回答:


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を使用します(HTTPのNodeのインターフェースよりも単純であり、ストリームでうまく再生されるため)。

AESの代わりにストリーム暗号(RC4)を使用して、ブロックサイズを処理する必要を回避しています。ウィキペディアは脆弱性があると考えているようですので、誰かが暗号化が好まれる洞察を持っているなら、それは素晴らしいでしょう。

node echo_server.jsポート9でリッスンするエコーサーバーを実行します。このプログラムの2つのインスタンスをnode chat.js <server IP>and で実行します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)

すべてのヒントとフィードバックをお寄せいただきありがとうございます。


1
ゴルフバージョンに読みやすさを追加しないでください。それがゴルフバージョンではありません。または、その場合は、改行する前にセミコロンを削除して、同じ長さにします。
mbomb007

@ mbomb007の「読みやすさ」は、ほとんどスクロールする必要がないことです。残念ながら、コードの本文にはセミコロンがないため、機能しません。すぐに見つけて交換するのは面倒ではないと思いました。ただし、今後のコメントのためにあなたのヒントを必ず覚えておいてください!
-user3080953

@Daveはすべてのフィードバックに感謝します!バニラDHを使用するように変更しました。これは、素数を交換する必要があるため、実際にはかなりの長さを追加しました。AESは実際にはドロップイン置換として機能しますが、ブロック、パディングが苦痛になります。また、rc4はaes128よりも短い
-user3080953

1
ネットワークを介して動作するかどうかはわかりませんでしたが、おそらく動作しないので、バスで書いたので確認する方法がありませんでした。新しいバージョンでは、代わりにエコーサーバーが使用されます。これにより、タイムアウトの問題も解決されます。私はサーバー+クライアントを避けようとしましたが、それははるかに良い形です。最後に、この課題のおかげで、どこからでもライブラリを
取得

@ user3080953いいですね。これらのアップデートを使用すると、賞金を稼ぐことができます!
デイブ

0

Node.js、638 607バイト

今では(そして同じ言語で)うまく機能しているので、テストの答えを次に示します。

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

使用法

これはサーバー/クライアントの実装です。1つのインスタンス化がサーバーになり、もう1つのインスタンス化がクライアントになります。サーバーは特定のポートで起動され、クライアントはサーバーのポートをポイントします。マシンのエントロピーが低い場合、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

トークンの唯一の要件は、少なくとも1つの非16進文字が含まれていることです。そのため、縮小コードでは他の文字列定数が使用されます(dataおよびhex)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.