Задать вопрос
Fernus
@Fernus
Техник - Механик :)

E2EE + WEB = поищем безопасность?

Виртуальная задача(упрощённо):

- Обмен сообщениями(данными) со сквозным шифрованием через WEB;
- "Создатель сервера" - недоверенное лицо - но сервер под контролем разработчика.

Решение "с наскока":

- Стандартная авторизация WEB-приложения;
- Генерируем ключи ECDH(если до этого на устройстве их нет - или они невалидные);
- Храним приватный ключ на клиенте(в браузере в данном случае...или пойти дальше - в приложении);
- Публичный - используем как хотим в пределах системы и обмена между участниками;
- При шифровке/дешифровке данных используем derivedKey(общий ключ шифирования для публичного "получателя" и приватный ключ "отправителя" - и наоборот);
- При смене устройства(где хранится приватный ключ пользователя) - предлагать синхронизировать приватные ключи(аля QR) или введя доп. пинкод(который задаётся при регистрации...или который высылается по другой "довереннной" связи);
- Или предлагать "сбросить всё" - в случае - если нет "пин-кода", НО при этом - пройдена авторизация WEB-приложения(тем самым предыдущая переписка будет недоступна третьим лицам);
- При смене(сброса) публичного ключа - "вторая сторона" будет оповещена об этом - и далее "эта сторона" сама решает о "ликвидности" общения со стороной изменившей "публичный ключ".

Минимальный JS(в виде класса) для этой задачи на стороне клиента состряпал такой:

class msgCrypt {

    constructor(publicKeyJwk, privateKeyJwk) {

        this.publicKeyJwk = publicKeyJwk;
        this.privateKeyJwk = privateKeyJwk;

    }

    static async generateKeyPair() {

        const keyPair = await window.crypto.subtle.generateKey(
            {
                name: "ECDH",
                namedCurve: "P-256",
            },
            true,
            ["deriveKey", "deriveBits"]
        );

        const publicKeyJwk = await window.crypto.subtle.exportKey(
            "jwk",
            keyPair.publicKey
        );

        const privateKeyJwk = await window.crypto.subtle.exportKey(
            "jwk",
            keyPair.privateKey
        );

        return {publicKeyJwk, privateKeyJwk};

    }

    async deriveKey() {

        const publicKeyJwk = this.publicKeyJwk;
        const privateKeyJwk = this.privateKeyJwk;

        const publicKey = await window.crypto.subtle.importKey(
            "jwk",
            publicKeyJwk,
            {
                name: "ECDH",
                namedCurve: "P-256",
            },
            true,
            []
        );

        const privateKey = await window.crypto.subtle.importKey(
            "jwk",
            privateKeyJwk,
            {
                name: "ECDH",
                namedCurve: "P-256",
            },
            true,
            ["deriveKey", "deriveBits"]
        );

        return await window.crypto.subtle.deriveKey(
            {name: "ECDH", public: publicKey},
            privateKey,
            {name: "AES-GCM", length: 256},
            true,
            ["encrypt", "decrypt"]
        );
    };

    async encrypt(text) {

        const derivedKey = await this.deriveKey();

        const encodedText = new TextEncoder().encode(text);

        const iv = window.crypto.getRandomValues(new Uint8Array(12));

        const encryptedData = await window.crypto.subtle.encrypt(
            {name: "AES-GCM", iv: iv},
            derivedKey,
            encodedText
        );

        const uintArray = new Uint8Array(encryptedData);

        const string = String.fromCharCode.apply(null, uintArray);
        const iv_string = String.fromCharCode.apply(null, iv);

        const bDATA = btoa(string);
        const bIV = btoa(iv_string);

        return JSON.stringify({bDATA, bIV});
    };

    async decrypt(messageJSON) {
        try {

            const derivedKey = await this.deriveKey();

            const message = JSON.parse(messageJSON);
            const text = message.bDATA;
            const initializationVector = message.bIV;

            const iv_string = atob(initializationVector);
            const iv = new Uint8Array(
                [...iv_string].map((char) => char.charCodeAt(0))
            );

            const string = atob(text);
            const uintArray = new Uint8Array(
                [...string].map((char) => char.charCodeAt(0))
            );
            const algorithm = {
                name: "AES-GCM",
                iv: iv,
            };
            const decryptedData = await window.crypto.subtle.decrypt(
                algorithm,
                derivedKey,
                uintArray
            );

            return new TextDecoder().decode(decryptedData);
        } catch (e) {
            return `error decrypting message: ${e}`;
        }
    };

}


Остальное - "детали" - если взять за "ядро" - JS выше...

Вопрос:
Какие видятся минусы с точки зрения безопасности пересылаемых данных?
Если "отбросить" самый главный минус - "скомпрометированное устройство" самого обладателя.
  • Вопрос задан
  • 324 просмотра
Подписаться 1 Средний 2 комментария
Помогут разобраться в теме Все курсы
  • Академия Eduson
    Fullstack-разработчик на JavaScript
    11 месяцев
    Далее
  • Skillbox
    JavaScript
    3 месяца
    Далее
  • ProductStar
    Javascript: продвинутый уровень
    1 месяц
    Далее
Пригласить эксперта
Ответы на вопрос 2
@rPman
Для общения вообще достаточно клиентского шифрования с хранением ключей у клиентов и использованием публичных серверов stun/turn и signaling для старта webrtc. Т.е. для создания защищенного чата вообще достаточно статичных страниц github pages (там и исходники можно проконтролировать) и пользователи при создании комнаты должны обменяться по какому то каналу информацией об используемых stun/turn/signaling серверов (есть публичные, можно взять в аренду, можно поднять свой), при этом на практике почти ничего плохого владельцы stun/turn серверов сделать не могут, правда есть проблема с signaling сервером, его владельцы могут селать атаку MITM но второй уровень шифрования на стороне клиента (штатно webrtc уже шифруется) нивелирует эту проблему.

как и всегда, главная проблема - обмен контактами между пользователями (ключами шифрования) требования к каналу передачи тут наиважнейшие, и для этого рекомендуется вообще личная встреча.
Ответ написан
vabka
@vabka Куратор тега Веб-разработка
Выглядит как что-то избыточное, так как в обычных ситуациях должно быть достаточно https+обычной аутентификации по паролю, чтобы убедиться, что на том конце клиент именно тот, за кого себя выдаёт.

Если хочется добавить аутентификацию по ключу, то можно использовать mTLS.

Если у тебя нет https и ты хочешь защититься от mitm, то тогда такое решение как сейчас не подойдёт, так как злоумышленник посередине может подменить js, чтобы параллельно отправлять незашифрованный текст.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы