Шифрування в кінці кінця до гольфу


16

Цей виклик приносить велику суму в 200 балів за першу відповідь і залишається непереможеним принаймні 3 дні. Заявлений user3080953 .

Останнім часом багато говорять про шифрування в кінці та про тиск на компанії, щоб вилучити їх із своїх продуктів. Мене не цікавлять права та несправедливості цього, але я задумався: наскільки короткий код може бути таким, що на компанію тиснуть, щоб не використовувати його?

Завдання полягає в тому, щоб здійснити обмін ключами Diffie Hellman між двома мережевими системами, а потім дозволити користувачам спілкуватися вперед-назад за допомогою створеного симетричного ключа. Для цього завдання не потрібно застосовувати інших захистів (наприклад, не потрібно перемикати ключ, перевіряти особи, захищати від DoS тощо), і ви можете припустити відкритий Інтернет (будь-які порти, які ви слухаєте, доступні для всіх). Використання вбудованих дозволено та заохочується!

Ви можете вибрати одну з двох моделей:

  • Сервер і клієнт: клієнт підключається до сервера, тоді сервер або клієнт можуть надсилати повідомлення іншому. Сторонні сторони між ними не повинні читати повідомлення. Приклад потоку може бути:
    1. Користувач A запускає сервер
    2. Користувач B запускає клієнта і направляє його на сервер користувача A (наприклад, через IP / порт), програма відкриває з'єднання
    3. Програма Користувача A підтверджує з'єднання (необов'язково спочатку просити користувача про згоду)
    4. Програма користувача B починає генерувати секрет DH та надсилає необхідні дані (відкритий ключ, праймер, генератор та все, що потрібно для вашої реалізації) Користувачеві A
    5. Програма Користувача A використовує надіслані дані для завершення генерації загальної таємниці та повертає необхідні дані (відкритий ключ) Користувачу B. З цього моменту Користувач A може вводити повідомлення (наприклад, через stdin), які будуть зашифровані та відправлені Користувачеві B (наприклад, до stdout).
    6. Програма користувача B завершує генерацію загального секрету. З цього моменту Користувач B може надсилати повідомлення Користувачеві А.
  • Або: Сервер з двома підключеними до нього клієнтами: кожен клієнт спілкується з сервером, який пересилає своє повідомлення іншому клієнту. Сам сервер (і будь-які треті сторони між ними) повинні бути нездатні читати повідомлення. Окрім початкового з'єднання, процес такий же, як описаний у першому варіанті.

Детальні правила:

  • Ви можете надати одну програму або кілька програм (наприклад, сервер і клієнт). Ваш бал - це загальний розмір коду для всіх програм.
  • Ваша програма повинна теоретично вміти спілкуватися по мережі (але для тестування, localhost чудово). Якщо ваша обрана мова не підтримує мережу, ви можете комбінувати її з тим, що є (наприклад, сценарій оболонки); у цьому випадку ваш бал - це загальний розмір коду для всіх використовуваних мов.
  • Генерація ключів Diffie Hellman може використовувати жорстко кодовані значення "p" та "g".
  • Створений загальний ключ повинен бути не менше 1024 біт.
  • Після того, як ключ розділений, вибір шифрування симетричного ключа залежить від вас, але ви не повинні вибирати метод, який, як відомо, має практичну атаку на нього (напр., Зсув кесаря ​​є тривіальним, щоб повернути назад без знання ключа ). Приклад дозволених алгоритмів:
    • AES (будь-якого розміру ключа)
    • RC4 (теоретично зламаний, але ніяких практичних атак, про які я можу знайти згадку, тому це допустимо тут)
  • Користувачі A і B повинні бути в змозі надсилати повідомлення один одному (двостороннє спілкування) в інтерактивному режимі (наприклад, читання рядків з stdin, постійне спонукання або такі події, як натискання кнопки). Якщо це полегшиться, ви можете взяти на себе чергування (тобто після того, як користувач надсилає повідомлення, вони повинні чекати відповіді, перш ніж надсилати наступне повідомлення)
  • Мова вбудовані команди НЕ допускаються (немає необхідності писати свої власні криптографічні або мережеві методи , якщо вони вже підтримуються).
  • Базовий формат спілкування залежить від вас.
  • Наведені вище кроки спілкування є прикладом, але вам не потрібно дотримуватися їх (доки потрібна інформація буде надана, і жоден посередник не зможе обчислити спільний ключ або повідомлення)
  • Якщо деталі, необхідні для підключення до вашого сервера, не відомі заздалегідь (наприклад, якщо він прослуховується на випадковому порту), ці дані повинні бути надруковані. Можна припустити, що 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);

Безумовно

Node має вбудовані можливості мереж та криптовалюти. Це використовує TCP для роботи в мережі (оскільки він простіший, ніж інтерфейс Node для HTTP, і він прекрасно грає з потоками).

Я використовую потоковий шифр (RC4) замість AES, щоб уникнути необхідності мати справу з розмірами блоків. Вікіпедія, здається, вважає, що це може бути вразливим, тому, якщо хтось має якусь інформацію про те, які шифри віддають перевагу, це було б чудово.

Запустіть ехо-сервер node echo_server.js який прослухає порт 9. Запустіть два екземпляри цієї програми за допомогою 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
        }
    });
})

Echo-сервер

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 "читабельність" - це здебільшого, щоб уникнути необхідності прокрутки. на жаль, корпус коду не має крапки з комою, і це не працює. Я подумав, що швидка пошук та заміна не будуть надто обтяжливими. Ви обов'язково пам’ятаєте про свої поради щодо майбутніх коментарів!
користувач3080953

@ Дякую за всі відгуки! Я вніс зміни, щоб використовувати ванільний DH, який фактично додав трохи довжини, тому що вам потрібно обміняти прайми, а також AES насправді працює як заміна, але проблема з 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])

Використання

Це реалізація сервера / клієнта; однією інстанцією буде сервер, а іншою клієнт. Сервер запускається з певним портом, потім клієнт вказується на порт сервера. 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) інші рядкові константи .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.