Привет, Хабр!
В комментариях к нашему первому посту народ требовал хлеба и зрелищ кода и «success story». И если с первым у нас все более-менее нормально, то со вторым чего-то, как-то не складывается пока. Мы, знаете ли, люди все молодые, успехи у нас достаточно скромные, в баскетбол вот неплохо играем. Одним словом, сегодня мы пока просто немного покодим и раскажем о реализации end-to-end шифрования с использованием наших сервисов. А «success story»? Какие наши годы, будут еще в нашем блоге эти самые success stories.

Управление ключами


Одна из проблем реализации end-to-end шифрования заключается в безопасной передаче ключей шифрования получателю.
Наиболее очевидный способ решения проблемы — криптография с открытым ключом. Каждый пользователь системы имеет ключ, состоящий из двух частей: закрытая часть (секретный ключ), который хранится в секрете и никогда не передается по сети; открытая часть (публичный ключ), который должен быть доступен для всех участников системы. Секретный ключ принято использовать для подписания и расшифровки, в то время как с помощью открытого ключа производятся операции шифрования и проверки подписи.
Исходя из определения секретного и публичного ключа следует, что абсолютно любой участник системы способен, используя публичный ключ, зашифровать сообщение, но только обладатель секретного ключа способен это сообщение расшифровать.

Первое что следует принять во внимание при реализации схемы с открытым ключом — это возможность подмены публичного ключа третьей стороной. Злоумышленник, располагающий возможностью модифицировать трафик, способен перехватить публичный ключ пользователя Алиса и подменить его на свой собственный публичный ключ. Это приводит к тому, что пользователь Боб, при попытке отправить зашифраванное сообщение Алисе, на самом деле зашифрует его ключом атакующего, который сможет расшифровать и прочитать секретные данные.
Один из возможных вариантов предотвращения такой ситуации — это возможность однозначно идентифицировать обладателя ключа. В таком случае подмена ключа станет очевидна для Боба. В качестве идентификатора, можно указать e-mail адрес, номер телефона и другую уникальную, но легко распознаваемую информацию. При этом следует учесть, что идентификатор указываемый при создании нового ключа, должен быть проверен и подтвержден. В противном случае мы рискуем получить систему, в которой каждый пользователь сможет выдать себя за кого угодно.

Для проверки имени потребуется сервис проверки личности (identity validation service). Как правило, такие сервисы высылают случайно сгенерированный код на указанный в качестве имени e-mail адрес или номер телефона и ждут подтверждения от пользователя.
Однако использование сервиса проверки личности лишь перекладывает проблему доверия с канала связи на сам сервис. На самом деле ничто не мешает владельцу сервиса создать скомпрометированный ключ, якобы принадлежащий третьей стороне.

Для того, чтобы полностью избавиться от возможности появления скомпрометированных ключей, необходима аутентификация ключей.
Аутентификация ключей – способ убедить Боба, что ключ который он считает принадлежащим Алисе, на самом деле принадлежит Алисе. Существует несколько способов решения проблемы аутентификации ключей. Наиболее очевидный среди них – это обмен ключами при личной встрече или с использованием более надежного канала связи. Несмотря на высокую надежность, этот способ не всегда реализуем в реальном мире. Поэтому используется один из следующих методов аутентификации ключей.
Первый способ – использование доверенных центров сертификации. Когда определенный пользователь (доверенный центр) системы располагает неограниченным доверием и способен выступать в качестве заверителя ключей. Ни один ключ в такой системе не считается надежным без подписи доверенного центра.
Второй способ – использование сети доверия. При этом, каждый пользователь системы в праве подписать ключ любого другого пользователя, подтверждая тем самым, что ключ на самом деле принадлежит указанному владельцу. Чем больше заверяющих подписей под ключом, тем большего доверия он заслуживает.

Таким образом, процедура создания, хранения и передачи ключей имеет ряд подводных камней, которые легко обойти с помощью Virgil Security.

Virgil Security


Virgil Security предоставляет набор бесплатных криптографических сервисов, использование которых помогает решить множество проблем, связанных с безопасностью. В частности, проблему управления ключами.
На сегодняшний день Virgil Security это:
  1. Virgil crypto library – это оболочка над mbedTLS с открытым исходным кодом, которая позволяет использовать все стандартные алгоритмы шифрования и подписи. А также добавляет гибридные алгоритмы шифрования (ECIES). Virgil crypto library позволяет менять алгоритмы в будущем без першифровки существующих данных — принцип crypto agility.
  2. Virgil identity service – сервис проверки личности с открытым API, позволяющий проверить валидность e-mail адреса. Сервис высылает на указанный e-mail секретный код, который должен быть введен пользователем для подтверждения факта владения адресом.
  3. Virgil keys service – сервис публичных ключей, позволяющий работать с надежным и доступным хранилищем публичных ключей. Каждый ключ в хранилище привязан к специальной структуре данных, называемой VirgilCard. VirgilCard обязательно включает в себя: уникальный идентификатор карты — id, идентификатор владельца ключа — e-mail адрес, номер телефона и т.д., а также дату создания ключа. С помощью Virgil keys service можно быстро находить публичные ключи пользователей системы, а также удалять карты со скомпрометированными ключами.
  4. Virgil private keys service — сервис секретных ключей, отвечающий за загрузку, скачивание и удаление секретных ключей. Для доступа к секретному ключу, необходимо подтвердить владение Virgil картой, к которой он привязан.

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

Virgil services и end-to-end шифрование


Ну вот теперь пришло время немного покодить. Покажем как с помощью Virgil Security осуществить надежное end-to-end шифрование. Для этого реализуем следующую схему:

Обратите внимание, что при шифровании используется ECIES схема. Это означает, что сообщение шифруется симметричным алгоритмом AES с помощью эфемерного симметричного ключа, восстановить который возможно только обладая приватным ключом получателя. Шифрование же производится с использованием публичного ключа получателя.

Для работы с сервисами Virgil Security нам нужно получить секретный токен и пара публичный/закрытый ключ. Для этого регистрируемся на сайте Virgil Security. После этого приступаем к реализации e2ee.

  1. Скачиваем и устанавливаем VirgilSDK используя NPM:
    npm install virgil-sdk
    

  2. Инициализируем работу с сервисами Virgil Security:
    var virgil = new VirgilSDK("%ACCESS_TOKEN%");
    

  3. Генерируем пару ключей:
    var keyPair = virgil.crypto.generateKeyPair();
    
    console.log(keyPair.publicKey);
    console.log(keyPair.privateKey);
    

    И получаем вот такой результат:
    -----BEGIN PUBLIC KEY-----
    MFswFQYHKoZIzj0CAQYKKwYBBAGXVQEFAQNCAAQO8ohmBRyclmcfQ38Lwmvv4Cau
    jyX6vWn8kJrR0RRfFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    -----END PUBLIC KEY-----

    -----BEGIN EC PRIVATE KEY-----
    MHkCAQEEIFB+lOUvbb4WX+e3zLkAcYpvZR3qpQI8Ru/tcnciCMkIoAwGCisGAQQB
    l1UBBQGhRANCAAQO8ohmBRyclmcfQ38Lwmvv4CaujyX6vWn8kJrR0RRfFQAAAAAA
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    -----END EC PRIVATE KEY-----

  4. Публикуем открытый ключ. Для этого необходим validation token, подтверждающий личность владельца ключа. Получить validation token можно двумя способами: первый воспользоваться сервисом Virgil Identity, который проверит действительно ли владелец ключа имеет доступ к узаканному email адресу и сгенерирует validation token. Либо можно воспользоваться приватным ключом приложения и с его помощью сгенерировать validation token самостоятельно. В таком случае разработчик избегает необходимость доверять стороннему сервису идентификации. Воспользуемся полученным validation token и сохраним публичный ключ на сервере:
    var options = {
         public_key: keyPair.publicKey,
         private_key: keyPair.privateKey,
         identity: {
             type: 'member',
             value: 'Darth Vader',
             validation_token: '%VALIDATION_TOKEN%'
         }
    };
    
    virgil.cards.create(options).then(function (card){
        myCard = card;
        console.log(card);
    });
    

    В результате на сервере публичных ключей будет создана карта следующего вида:
    { 
        "id":"3e5a5d8b-e0b9-4be6-aa6b-66e3374c05b3",
        "authorized_by":"com.virgilsecurity.twilio-ip-messaging-demo",
        "hash":"QiWtZjZyIQhqZK7+3nZmIEWFBU+qI64EzSuqBcY+E7ZtKPwd4ZyU6gdfU/VzbTn6dHtfahCzHasN...",
        "data":null,
        "created_at":"2016-05-03T14:34:08+0000",
        "public_key":{ 
            "id":"359abe31-3344-453a-a292-fd98a83e500a",
            "public_key":"-----BEGIN PUBLIC KEY-----\nMFswFQYHKoZIzj0CAQYKKwYBBAGXVQEFAQNCAAQ...",
            "created_at":"2016-05-03T14:34:08+0000"
        },
        "identity":{ 
            "id":"965ea277-ab78-442c-93fe-6bf1d70aeb4b",
            "type":"member",
            "value":"Darth Vader",
            "created_at":"2016-05-03T14:34:08+0000"
        }
    }
    




  5. Создаем канал, по которому будут передаваться зашифрованные сообщения. Сделать это будет не так сложно, как кажется. С недавних пор мы стали партнерами с компанией Twilio, которая в частности предоставляет API для реализации IP мессенджера. Пример готового IP мессенджера со встроенной функцией end-to-end шифрования можно найти тут.
    // Create a Channel
    twilioClient.createChannel({ friendlyName: 'general' }).then(function(channel) {
        generalChannel = channel;
    });
    

  6. Чтобы отправить секретное сообщение найдем публичные ключи собеседников и воспользуемся им для шифрования
    // Receive the list of Channel's recipients
    Promise.all(generalChannel.getMembers().map(function(member) {
        // Search for the member’s cards on Virgil Keys service
        return virgil.cards.search({ value: member.identity, type: 'member' })
            .then(function(cards){
                return { 
                    recipientId: cards[0].id, 
                    publicKey: cards[0].public_key.public_key
                };
        });
    }).then(function(recipients) {
        var message = $('#chat-input').val();
        var encryptedMessage = virgil.crypto.encryptStringToBase64(message, recipients);
    
        generalChannel.sendMessage(encryptedMessage);    
        console.log(encryptedMessage);
    });
    

    Зашифрованное сообщение выглядит следующим образом:
    MIIDBQIBADCCAv4GCSqGSIb3DQEHA6CCAu8wggLrAgECMYICvDCCAVoCAQKgJgQkMDg3YjgwYmMtMzNjYi00MTI1LWI4YTgtYTE
    3OTEwM2Y3ZjRkMBUGByqGSM49AgEGCisGAQQBl1UBBQEEggEUMIIBEAIBADBbMBUGByqGSM49AgEGCisGAQQBl1UBBQEDQgAEcd
    8fhKqYlZxvcmmodg7Z3PNhE1LXLJqobouEcRfZaRMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAYBgcogYxxAgUCM
    A0GCWCGSAFlAwQCAgUAMEEwDQYJYIZIAWUDBAICBQAEMEaJMAvX7S+52BpI5hYyFOc0noIc+qdFFrQanNAtNGBAX/Pxeg5yJ2iA
    JijyZ8ut9zBRMB0GCWCGSAFlAwQBKgQQ81bklcNOyU/QTatCigSzoAQwHnAcbXk0daExIIS+sr6aIvVuF/o6j+1Rs5bvq2WVN41
    k/Oir5x7KZTSR7v3nx+fTMIIBWgIBAqAmBCRmNzM4YTUwNi1hMDYwLTQ1MDgtYTJkYS04NjY1NjZlYzg0ODMwFQYHKoZIzj0CAQ
    YKKwYBBAGXVQEFAQSCARQwggEQAgEAMFswFQYHKoZIzj0CAQYKKwYBBAGXVQEFAQNCAARJ5C3hsYuI2Sf14k60Dz5Mv5yD/AsVA
    zPfsmlreGTC2gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMBgGByiBjHECBQIwDQYJYIZIAWUDBAICBQAwQTANBglg
    hkgBZQMEAgIFAAQwhu7WM1rff9RYsQ+dmfX9Os3Irwm4cm5bIvUlcGXlCfmEsrjTyTg5MGjYLtxbYtL9MFEwHQYJYIZIAWUDBAE
    qBBCfKdP/gZnkVwJvv4Hdf2eWBDC3czBjV/yPGeGTqBIilHSsrqwK7lVMTBuKR+mR3eNdh+yBIAcOk4rveSUbDuWagDIwJgYJKo
    ZIhvcNAQcBMBkGCWCGSAFlAwQBLgQMfjkCvK3UgXdorcYUmtCHHuSm4yfBacMsniMADAeos7qN7OmNsFU1


  7. Получатель расшифровывает сообщение, используя секретный ключ:
    // Listen for new Messages sent to a Channel
    generalChannel.on('messageAdded', function(message) {
    
        // Decrypt the Message using card id and private key values.
        var decryptedMessage = virgil.crypto.decryptStringFromBase64(
            message.body, 
            myCard.id, 
            keyPair.privateKey
        );
    
        console.log(message.author + ': ' + decryptedMessage);
    });
    

    И увидит следующее:
    Darth Vader: Luke. I am your father!


Достаточно просто. Вы пишите всего несколько строк кода и получаете полноценное end-to-end шифрование с возможностью аутентификации ключей. При этом сервисы Virgil Security не имеют доступа к секретным ключам пользователей, что исключает возможность чтения секретных данных.
Поделиться с друзьями
-->

Комментарии (9)


  1. Ivan_83
    31.05.2016 18:44

    А NIST SP 800-90A (RNG) у вас в либе с дыркой?
    https://en.wikipedia.org/wiki/NIST_SP_800-90A


  1. sseroshtan
    31.05.2016 19:01
    +1

    А NIST SP 800-90A у вас с дыркой?

    Данный алгоритм реализован как часть библиотеки MbedTLS — https://tls.mbed.org/ctr-drbg-source-code
    На дынный момент уязвимостей в данном модуле не найдено — https://tls.mbed.org/security


    1. Ivan_83
      31.05.2016 23:22
      -1

      Те используется там Dual_EC_DRBG с параметрами от NIST или нет вы лично не в курсе?
      Уязвимость и бэкдор это разные вещи, и почему я должен верить написанному на каком то сайте?

      Либу посмотрел.
      Dual_EC_DRBG там нет, используют CTR_DRBG и HMAC_DRBG.
      Либа выглядит весьма уныло на фоне libressl: реализовано мало алгоритмов, реализации далеки от оптимальных в плане оптимизации по скорости. Уверен что там пачка варнингов при сборке вылезает, если врубить -Wall.


      1. dmitrydain
        01.06.2016 01:46
        +1

        Проверил с -Wall под OSX — warnings нет, но тут надо смотреть gcc version.

        LibreSSL ето fork OpenSSL кстати — там размеры в разы больше как раз из-за реализации устаревших алгоритмов. К примеру: des(3), idea, rc2, rc4(3) + все кривости ASN.1 OpenSSL есть, хотя их чинят.

        Скорость сравнить тяжело — в LibreSSL нет сделанного ECIES. Сравнивать AES — он у всех на аппаратном уровне. mbed TLS очень удобен, значительно более чем OpenSSL и LibreSSL как библиотека реализующая «crypto primitives».


        1. Ivan_83
          01.06.2016 02:08

          Я как минимум видел не используемую метку в одной из функций, и это при беглом осмотре-листании.

          Сравнивайте хэши, программный AES.
          Элиптика очень сильно просаженная по скорости в этой либе, тут даже тестов не надо.

          Из того в чём я понимаю:
          — md5, sha1, sha2 — около референсные реализации. Забыл посмотреть работу с буфером в update от sha, в референсе она по одному байту копировала во временный буфер.
          — ECDSA: работа с большими цифрами так себе, многие вещи можно было сделать быстрее в разы, хотя бы подсчёт битов, да и сдвиги. Умножение точки на скаляр вроде тоже самое примитивное из рекомендаций ниста/банкиров, никаких достижений математиков за последние 10+ лет в этой области там не увидел.
          — AES там две реализации, обычная и AES-NI. С первой — подозреваю что тоже тормозной референс. AES-NI у меня дома только в одном проце есть, остальные коредуо, дуалкоры, атом и закат — там нет AES-NI, сомневаюсь что у других пользователей оно повсеместно есть. Поскольку это позиционируется как решение для не только х86 платформ, то там то точно AES-NI нет.

          Зато в LibreSSL есть ещё и ГОСТовские алгоритмы, да и прочих других тоже по больше.
          За удобство API ничего не скажу, но вроде у LibreSSL есть более новое относительно OpenSSL, а API OpenSSL оставлено для совместимости.


      1. sseroshtan
        01.06.2016 11:29

        реализации далеки от оптимальных в плане оптимизации по скорости

        Если скорость не устраивает, рекомендую использовать fork от VirgilSecurity и кривую Curve25519, при этом для вычислений используется оптимизированный вариант на базе Ed25519


        Уверен что там пачка варнингов при сборке вылезает, если врубить -Wall

        Это голословное заявление :) MbedTLS собирается на Travis-CI с флагами -Wall -Werror


        1. Ivan_83
          01.06.2016 13:22

          У меня есть собственная реализация ECDSA+ГОСТ, я уж лучше ей воспользуюсь, или LibreSSL освою.


          1. sseroshtan
            01.06.2016 13:24

            Ну свое то однозначно лучше и роднее :)


      1. sseroshtan
        01.06.2016 17:06

        Код также проверяется статическим анализатором CoverityScan: