Как перестать бояться и полюбить end-to-end шифрование

4401252811174d2aa4e63e28a9408feb.jpg


Привет, Хабр!
В комментариях к нашему первому посту народ требовал хлеба и зрелищ кода и «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 шифрование. Для этого реализуем следующую схему:
0e0dd3932b424037870fc28daeccb751.jpg
Обратите внимание, что при шифровании используется 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 не имеют доступа к секретным ключам пользователей, что исключает возможность чтения секретных данных.

© Habrahabr.ru