เข้ารหัสการเล่นกอล์ฟแบบครบวงจร


16

ความท้าทายนี้มีคะแนน 200 แต้มสำหรับการตอบคำถามแรกและยังคงไม่แพ้ใครเป็นเวลาอย่างน้อย 3 วัน โดยอ้างว่าuser3080953

เมื่อไม่นานมานี้มีการพูดคุยกันมากมายเกี่ยวกับการเข้ารหัสแบบครบวงจรและกดดันให้ บริษัท ต่างๆนำผลิตภัณฑ์ออกจากผลิตภัณฑ์ของตน ฉันไม่ได้สนใจในสิทธิและความผิดของสิ่งนั้น แต่ฉันสงสัยว่ารหัสสั้นแค่ไหนที่จะทำให้ บริษัท กดดันให้ไม่ใช้มัน

ความท้าทายที่นี่คือการใช้การแลกเปลี่ยนคีย์ Diffie Hellmanระหว่างสองระบบเครือข่ายจากนั้นอนุญาตให้ผู้ใช้สื่อสารกลับไปกลับมาโดยใช้คีย์สมมาตรที่สร้างขึ้น สำหรับวัตถุประสงค์ของงานนี้ไม่จำเป็นต้องมีการป้องกันอื่น ๆ (เช่นไม่จำเป็นต้องวนกุญแจตรวจสอบตัวตนป้องกัน DoS ฯลฯ ) และคุณสามารถใช้งานอินเทอร์เน็ตแบบเปิดได้ การใช้บิวอินได้รับอนุญาตและสนับสนุน!

คุณสามารถเลือกหนึ่งในสองรุ่น:

  • เซิร์ฟเวอร์และไคลเอนต์: ไคลเอนต์เชื่อมต่อกับเซิร์ฟเวอร์จากนั้นเซิร์ฟเวอร์หรือไคลเอนต์สามารถส่งข้อความไปยังอีก บุคคลที่สามในระหว่างทั้งสองจะต้องไม่สามารถอ่านข้อความได้ ตัวอย่างการไหลอาจเป็น:
    1. ผู้ใช้ A เปิดตัวเซิร์ฟเวอร์
    2. ผู้ใช้ B เปิดตัวไคลเอนต์และนำไปยังเซิร์ฟเวอร์ของผู้ใช้ A (ผ่านทาง IP / พอร์ต) โปรแกรมจะเปิดการเชื่อมต่อ
    3. โปรแกรมของผู้ใช้ A ยอมรับการเชื่อมต่อ (ขอความยินยอมจากผู้ใช้ก่อน)
    4. โปรแกรมของผู้ใช้ B เริ่มต้นการสร้างความลับ DH และส่งข้อมูลที่ต้องการ (กุญแจสาธารณะนายกตัวสร้างสิ่งอื่นใดที่คุณต้องนำไปใช้) กับผู้ใช้ A
    5. โปรแกรมของผู้ใช้ใช้ข้อมูลที่ส่งเพื่อสร้างความลับที่แชร์เสร็จสมบูรณ์และส่งข้อมูลที่ต้องการ (กุญแจสาธารณะ) กลับไปยังผู้ใช้ B จากจุดนี้ผู้ใช้ A สามารถป้อนข้อความ (เช่นผ่าน stdin) ซึ่งจะถูกเข้ารหัสและส่งไปยังผู้ใช้ B (เช่นเพื่อ stdout)
    6. โปรแกรมของผู้ใช้ B เสร็จสิ้นการสร้างความลับที่แชร์ จากจุดนี้ผู้ใช้ B สามารถส่งข้อความไปยังผู้ใช้ก
  • หรือ: เซิร์ฟเวอร์ที่มีลูกค้าสองรายเชื่อมต่ออยู่: ลูกค้าแต่ละรายพูดคุยกับเซิร์ฟเวอร์ซึ่งส่งต่อข้อความไปยังลูกค้ารายอื่น เซิร์ฟเวอร์เอง (และบุคคลที่สามในระหว่างนั้น) จะต้องไม่สามารถอ่านข้อความได้ นอกเหนือจากการเชื่อมต่อเริ่มต้นกระบวนการจะเหมือนกับที่อธิบายไว้ในตัวเลือกแรก

กฎรายละเอียด:

  • คุณสามารถจัดหาโปรแกรมเดี่ยวหรือหลายโปรแกรม (เช่นเซิร์ฟเวอร์และไคลเอนต์) คะแนนของคุณคือขนาดรหัสทั้งหมดของทุกโปรแกรม
  • โปรแกรมของคุณจะต้องสามารถสื่อสารผ่านเครือข่ายในทางทฤษฎี (แต่สำหรับการทดสอบ localhost นั้นใช้ได้) หากภาษาที่คุณเลือกไม่รองรับเครือข่ายคุณสามารถรวมกับสิ่งที่ทำ (เช่นเชลล์สคริปต์) ในกรณีนี้คะแนนของคุณคือขนาดรหัสทั้งหมดในทุกภาษาที่ใช้
  • การสร้างคีย์ Diffie Hellman สามารถใช้ค่า "p" และ "g" แบบตายตัวได้
  • รหัสแชร์ที่สร้างขึ้นต้องมีอย่างน้อย 1024 บิต
  • เมื่อใช้คีย์ร่วมกันตัวเลือกการเข้ารหัสคีย์สมมาตรนั้นขึ้นอยู่กับคุณ แต่คุณต้องไม่เลือกวิธีที่ทราบกันดีว่าในปัจจุบันมีการโจมตีในทางปฏิบัติ (เช่นการเปลี่ยนซีซาร์เป็นเรื่องเล็กน้อยที่จะย้อนกลับโดยปราศจากความรู้เกี่ยวกับกุญแจ ) ตัวอย่างอัลกอริทึมที่อนุญาต:
    • AES (ขนาดใดก็ได้ที่สำคัญ)
    • RC4 (แตกตามหลักทฤษฏี แต่ไม่มีการโจมตีเชิงปฏิบัติที่ฉันสามารถหากล่าวถึงได้ดังนั้นจึงอนุญาตได้ที่นี่)
  • ผู้ใช้ A และ B ทั้งสองจะต้องสามารถส่งข้อความถึงกันและกัน (การสื่อสารสองทาง) แบบโต้ตอบ (เช่นการอ่านบรรทัดจาก stdin การแจ้งเตือนอย่างต่อเนื่องหรือเหตุการณ์เช่นการกดปุ่ม) หากทำได้ง่ายขึ้นคุณอาจสมมติการสนทนาสำรอง (เช่นหลังจากผู้ใช้ส่งข้อความพวกเขาต้องรอการตอบกลับก่อนส่งข้อความถัดไป)
  • builtins ภาษาจะได้รับอนุญาต (ไม่จำเป็นต้องเขียนวิธีการเข้ารหัสลับหรือเครือข่ายของคุณเองหากพวกเขากำลังได้รับการสนับสนุนแล้ว)
  • รูปแบบการสื่อสารพื้นฐานนั้นขึ้นอยู่กับคุณ
  • ขั้นตอนการสื่อสารที่ระบุด้านบนเป็นตัวอย่าง แต่คุณไม่จำเป็นต้องปฏิบัติตาม (ตราบใดที่มีการแบ่งปันข้อมูลที่จำเป็นและไม่มีคนกลางสามารถคำนวณคีย์หรือข้อความที่แชร์ได้)
  • หากไม่ทราบรายละเอียดที่จำเป็นในการเชื่อมต่อกับเซิร์ฟเวอร์ของคุณล่วงหน้า (เช่นหากฟังบนพอร์ตสุ่ม) รายละเอียดเหล่านี้จะต้องพิมพ์ออกมา คุณสามารถสันนิษฐานได้ว่าที่อยู่ IP ของเครื่องเป็นที่รู้จัก
  • การจัดการข้อผิดพลาด (เช่นที่อยู่ไม่ถูกต้องการเชื่อมต่อที่หายไป ฯลฯ ) ไม่จำเป็น
  • ความท้าทายคือการเขียนโค้ดดังนั้นรหัสที่สั้นที่สุดในหน่วยไบต์ชนะ

การเข้ารหัสpและgอนุญาตเป็นอย่างไร
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);

Ungolfed

โหนดมีความสามารถด้านเครือข่ายและเข้ารหัสลับในตัว สิ่งนี้ใช้ TCP สำหรับเครือข่าย (เพราะง่ายกว่า Node ของอินเตอร์เฟสสำหรับ HTTP และเล่นได้ดีกับสตรีม)

ฉันใช้รหัสกระแส (RC4) แทน AES เพื่อหลีกเลี่ยงการจัดการกับขนาดบล็อก Wikipedia ดูเหมือนจะคิดว่ามันอาจมีช่องโหว่ดังนั้นหากใครมีข้อมูลเชิงลึกว่าต้องการระบบเลขศูนย์ใดมันจะยอดเยี่ยมมาก

เรียกใช้เซิร์ฟเวอร์ echo node echo_server.jsซึ่งจะรับฟังพอร์ต 9 เรียกใช้สองอินสแตนซ์ของโปรแกรมนี้ด้วยnode chat.js <server IP>และnode chat.js <server IP> 1(อาร์กิวเมนต์สุดท้ายเพียงแค่ตั้งค่าที่หนึ่งส่งนายก) แต่ละอินสแตนซ์เชื่อมต่อกับเซิร์ฟเวอร์ echo ข้อความแรกจัดการการสร้างคีย์และข้อความที่ตามมาจะใช้การเข้ารหัสสตรีม

เซิร์ฟเวอร์ echo เพียงส่งทุกอย่างกลับไปยังไคลเอนต์ที่เชื่อมต่อทั้งหมดยกเว้นต้นฉบับ

ไคลเอนต์

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

เซิร์ฟเวอร์ Echo

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

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

ขอบคุณเดฟสำหรับเคล็ดลับ + ข้อเสนอแนะทั้งหมด!


1
อย่าเพิ่มความสามารถในการอ่านให้กับเวอร์ชัน golfed นั่นคือสิ่งที่เวอร์ชัน ungolfed ใช้ หรือถ้าคุณทำอย่างนั้นให้ลบเครื่องหมายอัฒภาคก่อนที่จะถึงเส้นแบ่งดังนั้นมันมีความยาวเท่ากัน
mbomb007

@ mbomb007 "การอ่าน" เป็นส่วนใหญ่เพื่อหลีกเลี่ยงการเลื่อน โชคไม่ดีที่เนื้อความของโค้ดไม่มีอัฒภาคดังนั้นจึงใช้ไม่ได้ ฉันคิดว่าการค้นหาอย่างรวดเร็วและแทนที่จะไม่เป็นภาระมากเกินไป จะเก็บเคล็ดลับไว้ในใจสำหรับความคิดเห็นในอนาคตแม้ว่า!
user3080953

@ ขอขอบคุณสำหรับข้อเสนอแนะทั้งหมด! ฉันได้ทำการเปลี่ยนแปลงเพื่อใช้ vanilla DH ซึ่งจริง ๆ แล้วเพิ่มความยาวไม่น้อยเพราะคุณจำเป็นต้องแลกเปลี่ยนเฉพาะด้วย AES ใช้แทนการดรอปอินได้ แต่ปัญหาของ AES คือไม่มีอะไรส่งจนกว่าคุณจะเสร็จสิ้น บล็อกและ padding จะเจ็บปวด rc4 นั้นสั้นกว่า aes128
3080953

1
ฉันไม่แน่ใจว่ามันจะทำงานผ่านเครือข่าย แต่อาจจะไม่ได้และฉันเขียนไว้บนรถบัสดังนั้นฉันจึงไม่มีวิธีตรวจสอบ เวอร์ชันใหม่ใช้เซิร์ฟเวอร์ echo แทน นอกจากนี้ยังช่วยแก้ปัญหาการหมดเวลา ฉันพยายามหลีกเลี่ยงเซิร์ฟเวอร์ + ลูกค้า แต่มันเป็นรูปแบบที่ดีกว่ามาก ในที่สุดขอบคุณสำหรับความท้าทายนี้ฉันได้เรียนรู้วิธีการใช้โหนดจริง ๆ แทนการคว้า
ไลบรารี่

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

การใช้

นี่เป็นการใช้งานเซิร์ฟเวอร์ / ไคลเอนต์ อินสแตนซ์หนึ่งจะเป็นเซิร์ฟเวอร์และอีกไคลเอนต์ เซิร์ฟเวอร์เปิดใช้งานด้วยพอร์ตเฉพาะจากนั้นไคลเอ็นต์จะชี้ไปที่พอร์ตของเซิร์ฟเวอร์ 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

ข้อกำหนดเพียงอย่างเดียวสำหรับโทเค็นคือมีอักขระที่ไม่ใช่ฐานสิบหกตัวอย่างน้อยหนึ่งตัวดังนั้นในรหัสที่ลดขนาดจะใช้ค่าคงที่สตริงอื่น ( dataและhex)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.