Веб

Уязвимости безопасности в сквозном шифровании E2EE

Анализ уязвимостей E2EE с использованием ECDH и AES-GCM. Риски и рекомендации по улучшению безопасности.

Каковы потенциальные уязвимости в безопасности при использовании сквозного шифрования (E2EE) в веб-приложении, где сервер контролируется разработчиком, но считается недоверенным лицом? В частности, какие риски существуют в предложенной реализации, использующей ECDH для обмена ключами и AES-GCM для шифрования, с учетом следующего JS-кода на стороне клиента?

javascript
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}`;
        }
    };

}

Какие угрозы безопасности остаются, если исключить компрометацию устройства пользователя? Какие улучшения можно внести в этот код для повышения безопасности?

Система сквозного шифрования (E2EE) с использованием ECDH и AES-GCM содержит критические уязвимости даже при условии контроля сервера разработчиком. Основные риски связаны с отсутствием проверки открытых ключей, потенциальным повторным использованием IV в AES-GCM и небезопасной производной ключей, что может привести к атакам подстановки данных и компрометации конфиденциальности.

Содержание

Анализ архитектуры E2EE

Представленная система использует ECDH для обмена ключами и AES-GCM для шифрования данных. Хотя архитектура обеспечивает базовый уровень конфиденциальности, её реализация содержит критические недостатки. Согласно OWASP Web Security Testing Guide, системы E2EE с доверенными серверами всё равно подверж атакам, связанным с криптографическими протоколами, а не только с компрометацией устройств.

В текущей реализации сервер контролируется разработчиком, но считается недоверенным. Это означает, что злоумышленник, получивший доступ к серверу, может манипулировать ключами и шифрованными данными. Основные угрозы исходят от слабостей в протоколе ECDH и использовании AES-GCM без должной защиты от повторного использования IV.

Уязвимости в протоколе ECDH

Ключевая проблема кода — отсутствие проверки открытых ключей ECDH. Как указано в RFC 8446, валидация открытых ключей обязательна для предотвращения атак на малые подгруппы и некорректные кривые. В текущей реализации:

javascript
const publicKey = await window.crypto.subtle.importKey(
    "jwk", publicKeyJwk,
    {name: "ECDH", namedCurve: "P-256"},
    true, []
);
  • Нет проверки принадлежности точки кривой P-256
  • Отсутствует проверка порядка открытого ключа
  • Не предотвращается атака через некорректную кривую

По данным NIST SP 800-56A Rev 3, это позволяет злоумышленнику:

  1. Принудить генерацию слабого общего секрета
  2. Выполнить атаку через малую подгруппу
  3. Получить предсказуемые ключи для AES-GCM

Проблемы реализации AES-GCM

В методе encrypt() генерируется случайный IV, но нет защиты от повторного использования:

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

Как подчеркивается в NIST SP 800-38D, повторное использование IV с тем же ключом в AES-GCM катастрофоопасно. В текущей реализации:

  • Нет отслеживания использованных IV
  • Нет механизма предотвращения коллизий IV
  • Отсутствует nonce management

Это приводит к:

  • Полной компрометации ранее зашифрованных сообщений
  • Возможности подделки аутентификационных тегов
  • Раскрытию ключей шифрования

Риски в производной ключей

Метод deriveKey() производит ключ AES-GCM без контекстной информации:

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

Согласно NIST SP 800-56A Rev 3, в производные ключи должны включаться контекстные данные. Отсутствие этого в реализации:

  • Снижает энтропию ключей
  • Упрощает криптоанализ с использованием нескольких сессий
  • Не обеспечивает изоляцию ключей для разных контекстов

Уязвимости в коде клиента

Дополнительные проблемы в предоставленном коде:

  1. Обработка ошибок в decrypt():

    javascript
    catch (e) {
        return `error decrypting message: ${e}`;
    }
    

    Раскрытие информации об ошибках может помочь атакующему в анализе системы.

  2. Временные метки ключей:
    В методе generateKeyPair() нет отслеживания времени жизни ключей, что увеличивает окно для атак.

  3. Уязвимость к replay-атакам:
    Отсутствует защита против повторной отправки сообщений или ключей.

Рекомендации по улучшению безопасности

1. Валидация открытых ключей ECDH

Добавить проверку открытых ключей:

javascript
// Перед импортом ключа
const publicKeyJwk = addPublicKeyValidation(publicKeyJwk);

function addPublicKeyValidation(jwk) {
    // Проверка принадлежности кривой
    if (jwk.crv !== "P-256") throw new Error("Invalid curve");
    
    // Проверка порядка ключа
    const x = BigInt(jwk.x);
    const y = BigInt(jwk.y);
    const p = BigInt("0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF");
    const n = BigInt("0FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC");
    
    if (x >= p || y >= p) throw new Error("Invalid coordinate");
    if (!isValidPointOnCurve(x, y, p)) throw new Error("Point not on curve");
    if (multiplyPoint(x, y, n, p) !== null) throw new Error("Invalid key order");
    
    return jwk;
}

2. Защита от повторного использования IV

Реализовать управление nonce:

javascript
class msgCrypt {
    constructor(publicKeyJwk, privateKeyJwk) {
        // ... существующий код
        this.usedIVs = new Set(); // Для отслеживания используемых IV
    }

    async encrypt(text) {
        // ... существующий код до генерации IV
        
        // Проверка уникальности IV
        while (this.usedIVs.has(ivString)) {
            iv = window.crypto.getRandomValues(new Uint8Array(12));
            ivString = String.fromCharCode.apply(null, iv);
        }
        this.usedIVs.add(ivString);
        
        // ... остальной код
    }
}

3. Контекстная производная ключей

Добавить контекстные данные:

javascript
async deriveKey(context = "") {
    // ... существующий код
    
    const contextBits = new TextEncoder().encode(context);
    const salt = window.crypto.getRandomValues(new Uint8Array(16));
    
    return await window.crypto.subtle.deriveKey(
        {
            name: "ECDH",
            public: publicKey,
            salt: salt,
            info: contextBits
        },
        privateKey,
        {name: "AES-GCM", length: 256},
        true, 
        ["encrypt", "decrypt"]
    );
}

4. Улучшение обработки ошибок

Заменить раскрытие информации на безопасную обработку:

javascript
async decrypt(messageJSON) {
    try {
        // ... существующий код
    } catch (e) {
        // Логирование на сервере (без раскрытия пользователю)
        console.error("Decryption failed", e);
        return "Ошибка обработки сообщения";
    }
}

Заключение

Система сквозного шифрования с текущей реализацией ECDH и AES-GCM содержит критические уязвимости в области валидации ключей, управления nonce и производных ключей. Основные риски связаны с возможностью атак через некорректные открытые ключи, повторным использованием IV и отсутствием контекстной информации в производных ключах. Для повышения безопасности необходимо внедрить проверку открытых ключей ECDH, реализовать систему управления nonce, добавить контекстные данные при производной ключей и улучшить обработку ошибок. Эти изменения значительно снизят риски компрометации систем E2EE при контролируемом, но недоверенном сервере.

Источники

  1. OWASP Web Security Testing Guide
  2. Cryptography Stack Exchange - ECDH
  3. NIST SP 800-38D - AES-GCM Mode
  4. RFC 8446 - TLS 1.3
  5. NIST SP 800-56A Rev 3 - Key Derivation
Авторы
Проверено модерацией
Модерация