Гольф сквозное шифрование

16

Эта задача приносит 200 баллов за первый ответ и остается непобедимым не менее 3 дней. Заявлено пользователем 3080953 .

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

Задача здесь состоит в том, чтобы реализовать обмен ключами Диффи-Хеллмана между двумя сетевыми системами, а затем позволить пользователям обмениваться данными с использованием сгенерированного симметричного ключа. Для выполнения этой задачи никакие другие средства защиты не требуются (например, нет необходимости циклически переключать ключ, проверять идентификационные данные, защищать от DoS и т. Д.), И вы можете использовать открытый интернет (все прослушиваемые порты доступны для всех). Использование встроенных файлов разрешено и приветствуется!

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

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

Подробные правила:

  • Вы можете предоставить одну программу или несколько программ (например, сервер и клиент). Ваша оценка - это общий размер кода во всех программах.
  • Ваша программа должна быть теоретически способна обмениваться данными по сети (но для тестирования подойдет localhost). Если выбранный вами язык не поддерживает работу в сети, вы можете объединить его с чем-то, что делает (например, сценарий оболочки); в этом случае ваш счет - это общий размер кода для всех используемых языков.
  • Генерация ключей Диффи-Хеллмана может использовать жестко закодированные значения «p» и «g».
  • Сгенерированный общий ключ должен быть не менее 1024 бит.
  • Как только ключ используется совместно, выбор шифрования с симметричным ключом остается за вами, но вы не должны выбирать метод, который, как известно в настоящее время, имеет практическую атаку против него (например, смещение Цезаря тривиально, чтобы изменить его без знания ключа ). Пример разрешенных алгоритмов:
    • AES (любой размер ключа)
    • RC4 (теоретически сломан, но нет практических атак, о которых я могу упомянуть, поэтому здесь допустимо)
  • Пользователи A и B должны иметь возможность отправлять друг другу сообщения (двусторонняя связь) в интерактивном режиме (например, чтение строк из стандартного ввода, постоянный запрос или такие события, как нажатие кнопки). Если это облегчает задачу, вы можете начать чередующийся разговор (т. Е. После того, как пользователь отправит сообщение, он должен дождаться ответа, прежде чем отправлять свое следующее сообщение).
  • Язык встроенные команды не допускаются (нет необходимости писать свои собственные криптографические или сетевые методы , если они уже поддерживаются).
  • Основной формат общения зависит от вас.
  • Приведенные выше шаги связи приведены в качестве примера, но вы не обязаны им следовать (при условии, что необходимая информация передается, и посредники не могут рассчитать общий ключ или сообщения)
  • Если детали, необходимые для подключения к вашему серверу, не известны заранее (например, если он прослушивает произвольный порт), эти данные должны быть напечатаны. Вы можете предположить, что IP-адрес машины известен.
  • Обработка ошибок (например, неверные адреса, потерянные соединения и т. Д.) Не требуется.
  • Задача состоит в коде гольфа, поэтому выигрывает самый короткий код в байтах.
Дейв
источник
Это жесткое pи gразрешено?
Только для ASCII
@ Только ASCII, насколько я могу судить, жесткое кодирование значений p & g хорошего качества считается хорошим (если разработчик не использует злонамеренно значения, которые, как известно, уязвимы для определенных атак). Так что с этим испытанием все в порядке (при условии, что итоговый секрет составляет не менее 1024 бит)
Дейв

Ответы:

3

Node.js ( 372 423 + 94 = 517 513 байт)

Golfed

Добавлены разрывы строк для «читабельности».

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, чтобы избежать необходимости иметь дело с размерами блоков. Википедия, похоже, считает, что она может быть уязвимой, поэтому, если у кого-то есть понимание того, какие шифры предпочтительнее, это было бы здорово.

Запустите эхо-сервер 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
        }
    });
})

Эхо сервер

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

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

Спасибо Дейву за все советы + отзывы!

user3080953
источник
1
Не добавляйте удобочитаемости в версию для гольфа, для этого и нужна версия без гольфа. Или, если вы это сделаете, удалите точку с запятой до разрыва строки, чтобы она была одинаковой длины.
mbomb007
@ mbomb007 «удобочитаемость» в основном заключается в том, чтобы избежать необходимости прокрутки. К сожалению, тело кода не имеет точек с запятой, так что это не работает. Я решил, что быстрый поиск и замена не будут слишком обременительными. обязательно буду иметь в виду ваш совет для будущих комментариев, хотя!
user3080953
@ Дейв спасибо за все отзывы! Я внес изменения, чтобы использовать Vanilla DH, который на самом деле добавил довольно много длины, потому что вам нужно также обмениваться простыми числами, AES фактически работает как замена в виде вставки, но проблема с AES заключается в том, что ничего не отправляется, пока вы не завершите блок, и прокладка будет боль. также rc4 короче, чем aes128
user3080953
1
Я не был уверен, будет ли это работать по сети, но, вероятно, не будет, и я написал это на шине, поэтому у меня не было возможности проверить. новая версия использует вместо этого эхо-сервер. Это также решает проблему тайм-аута. Я пытался избежать сервера + клиента, но это гораздо лучше. наконец, спасибо за этот вызов, я узнал много о том, как на самом деле использовать узел, а не просто захватить библиотеки отовсюду :)
user3080953
@ 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).

Дейв
источник