Сервер Java TLS 1.2: расшифровка AES-GCM

В настоящее время я работаю над сервером Java TLS. (Я опубликовал здесь несколько дней назад о подписи KeyExchange)

Сейчас я пытаюсь расшифровать сообщение TLS, закодированное с помощью AES-GCM. Сервер уже обрабатывает CBC, но, поскольку он уязвим для POODLE, мы хотели бы вместо этого использовать GCM. Постараюсь объяснить как смогу :)

Для этого кода мы используем Java 8u91, Netty 3.9.0. Мы не используем BouncyCastle и не собираемся, мы хотели бы придерживаться JDK.

Код !

/**
 * Deciphers the fragment and returns the deciphered version of it
 * 
 * @param fragment
 *            to decrypt
 * @return the decrypted fragment
 * @throws InvalidKeyException
 * @throws InvalidAlgorithmParameterException
 * @throws IllegalBlockSizeException
 * @throws BadPaddingException
 */
    private ChannelBuffer decodeCiphered(ChannelBuffer fragment, short contentType, byte[] version) throws InvalidKeyException,
    InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
    if (readCipher != null) {
        byte[] ciphered = fragment.array();
        if (session.cipherSuite.cipher.type == CipherType.stream) {
            /* ... */
        } else if (session.cipherSuite.cipher.type == CipherType.block) {
            /* ... */
        } else if (session.cipherSuite.cipher.type == CipherType.aead) {
            byte[] nonce = concat(clientWriteIV.getIV(), fragment.copy(0, 8).array());
            // ClientWriteIV + explicit nonce (first 8 bytes of encrypted data)

            readCipher.init(Cipher.DECRYPT_MODE, clientWriteKey,
                new GCMParameterSpec(session.cipherSuite.cipher.block * 8, nonce));

            ChannelBuffer aad = ChannelBuffers.buffer(13);
            //seq_num(8B) + TLSCompressed.type(1B) + TLSCompressed.version(2B) + TLSCompressed.length(2B)

            aad.writeLong(writeSequence.longValue());
            aad.writeByte(contentType);
            aad.writeBytes(version);
            aad.writeShort(fragment.readableBytes() - 8 - 16);
            readCipher.updateAAD(aad.array());
            ciphered = readCipher.doFinal(ciphered, 8, ciphered.length - 8);
            fragment = ChannelBuffers.wrappedBuffer(ciphered);
        }
    return fragment;
}

/**
 * Generates the different needed keys.
 */
private void generateKeys() {
    byte[] keyBlock = null;
    if (session.cipherSuite.cipher.type == CipherType.aead) {
        keyBlock = new byte[2 * session.cipherSuite.cipher.key
                    + 8];

        try {
            prf(keyBlock, session.masterSecret, KEY_EXPANSION_LABEL, concat(serverRandom, clientRandom));
        } catch (GeneralSecurityException e) {
        }
        clientWriteKey = new SecretKeySpec(keyBlock,0 , session.cipherSuite.cipher.key, session.cipherSuite.cipher.kalgo);
        serverWriteKey = new SecretKeySpec(keyBlock, session.cipherSuite.cipher.key, session.cipherSuite.cipher.key, session.cipherSuite.cipher.kalgo);
        clientWriteIV = new IvParameterSpec(keyBlock, 2 * session.cipherSuite.cipher.key, 4);
        serverWriteIV = new IvParameterSpec(keyBlock, 2 * session.cipherSuite.cipher.key + 4, 4);

    } else {
        /* ... 
            For CBC
        */
    }
}

Я постоянно получаю следующую ошибку:

javax.crypto.AEADBadTagException: Tag mismatch!
at com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:524)
at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1023)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:960)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:824)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)
at javax.crypto.Cipher.doFinal(Cipher.java:2223)
at com.seemy.codec.tls.TlsCodec.decodeCiphered(TlsCodec.java:525)

Кажется, обычно это означает, что AAD не прав. Но насколько я могу судить из RFC 5246, раздел 6.2.3.3 AAD должен быть следующим:

The additional authenticated data, which we denote as additional_data, is defined as follows:

  additional_data = seq_num + TLSCompressed.type +
                    TLSCompressed.version + TLSCompressed.length;
where "+" denotes concatenation.

Я в растерянности, чтобы найти, что я делаю неправильно, чтобы расшифровать это сообщение. Ошибка «Несоответствие тегов» не очень помогает найти, что не так, поэтому я надеюсь, что кто-то здесь может мне помочь :)

Спасибо и хорошего дня !

Редактировать 1: я поместил короткий вместо байта в contentVersion, однако результат все тот же. Я получаю только ошибки несоответствия тегов...

Редактировать 2: Как предложил @dave_thompson_085, doFinal теперь вызывается для (ciphered, 8, ciphered.length-8) вместо (ciphered, 0, ciphered.length), чтобы исключить явное_nonce.

Я также более тщательно проверил значения aad и nonce.

Часть «explicit_nonce» одноразового номера соответствует пакету, который я вижу в Wireshark. Теперь я беспокоюсь, что client_write_IV не сгенерирован должным образом :|

Что касается aad, я обнаружил кое-что немного странное: длина равна 0.

Сообщение, которое я получаю от клиента, имеет длину 40 байтов, минус 8 для части manifest_nonce, минус длина MAC, которая составляет 32 для SHA-256 (я пытаюсь заставить работать tls_ecdhe_rsa_with_aes_128_gcm_sha256).

Следовательно, если я попробую tls_ecdhe_rsa_with_aes_256_gcm_sha384, длина окажется равной -16. Это кажется мне неправильным.

Редактировать 3: Я снова сделал, как предложил @dave_thompson_085, и изменил свой код, чтобы он имел следующую длину: aad.writeShort(fragment.readableBytes() - 8 - 16);, тогда как раньше я делал aad.writeShort(fragment.readableBytes()-8-session.cipherSuite.mac.len);

Кажется, он хорошо работает при разговоре с хромом (даже если он терпит неудачу после того, как я отправляю собственное зашифрованное сообщение, но для этого потребуется еще один пост позже, если я не могу исправить это самостоятельно), но при попытке с клиентом open_ssl я получаю несоответствие тегов очередной раз. Firefox и Safari просто присылают мне оповещения о версии_протокола…


person Alpha4    schedule 18.07.2016    source источник
comment
Java 8 уже поддерживает TLS с AES и GCM - нет необходимости его реализовывать.   -  person Robert    schedule 18.07.2016
comment
Да, но существующий код, над которым я работаю, не использует JSSE :/ И переделывать его целиком не вариант :)   -  person Alpha4    schedule 18.07.2016
comment
Реализация TLS в Java будет довольно медленной. Поэтому каждая известная мне реализация Java использует нативный код. ИМХО, я бы сделал как Google - использовал OpenSSL/BoringSSL и Java-оболочку.   -  person Robert    schedule 18.07.2016
comment
TLS уже реализован, только часть расшифровки/шифрования GCM не реализована. Сервер отлично работает в CBC :)   -  person Alpha4    schedule 19.07.2016
comment
(1) CBC уязвим для POODLE только в SSLv3 или при неправильном заполнении в TLS. Вам больше не следует использовать SSLv3 или делать неправильное заполнение ни в одной из версий. CBC уязвим для BEAST через TLSv1.0; Duong&Rizzo достигла выбранного начального открытого текста только с нулевыми днями, которые были быстро исправлены, но могли быть и другие. Во всех версиях CBC уязвим для Lucky13, по крайней мере, теоретически; это требует очень повторяемой синхронизации, и я еще не слышал, чтобы это работало вне лаборатории.   -  person dave_thompson_085    schedule 20.07.2016
comment
(2) несоответствие тегов означает не только AAD; тег вычисляется по AAD и зашифрованному тексту с использованием ключа и одноразового номера, и если какой-либо из них неверен, тег терпит неудачу. (В этом весь смысл аутентифицированного шифрования с дополнительными данными.) Предполагая, что не показанные переменные и методы делают то, на что указывают их имена, мне кажется, что это подходит для GCM (не CCM или Chacha), ЗА ИСКЛЮЧЕНИЕМ того, что вы должны расшифровать тело зашифрованной записи, ИСКЛЮЧАЯ явный IV (но включая тег, потому что это делает JCE), т.е. фрагмент от 8 до длины.   -  person dave_thompson_085    schedule 20.07.2016
comment
Большое спасибо @dave_thompson_085! Я подозревал, что мог получить несоответствие тегов по другим причинам, а не только из-за неправильного AAD, но не был уверен. Я просмотрю все это и вернусь сюда со своими выводами :)   -  person Alpha4    schedule 20.07.2016
comment
Пробовал, видимо еще что-то не так делаю. Я объяснил это в самом посте в Edit 2. Если в моем посте чего-то не хватает (методы, переменные, которые могут быть источником проблемы), я с удовольствием добавлю что-нибудь актуальное :)   -  person Alpha4    schedule 20.07.2016
comment
(3) Я не знаю насчет ChannelBuffer, но ваш код явно помещает 13 байтов, которые выглядят правильно, в экземпляр aad, поэтому, если вы не получаете эти 13 байтов обратно, посмотрите документ (или источник) для этого. Или, может быть, просто используйте ByteArrayOutputStream. (4) длина тега для GCM всегда составляет 16 байт (128 бит) и уже удалена Cipher.doFinal; хэш, указанный в наборе шифров (SHA256 или SHA384), не имеет отношения к обработке записей AEAD и не должен ни из чего вычитаться. (Его следует использовать в PRF для вывода ключей и Finished, и только там.)   -  person dave_thompson_085    schedule 21.07.2016
comment
(3) Я получаю из него 13 байтов, дело в том, что Short для длины оказывается равным 0. (4) тег удаляется Cipher.doFinal, но мне все еще нужно указать длину моего буфера - 8 - 16 как длина аада, не так ли?   -  person Alpha4    schedule 21.07.2016
comment
Ах! Я думал, вы имели в виду длину AAD, но вы имели в виду длину В AAD. Да, это вычисляется как зашифрованное_len минус явное IV_len (8 для GCM) минус MAC_len (16 для GCM, потому что тег — это MAC). И теперь я думаю об этом, если это первая запись, которую вы расшифровываете, она сразу после CCS и, следовательно, является сообщением Finished, которое действительно составляет 16 байт (40 - 8 - 16).   -  person dave_thompson_085    schedule 21.07.2016
comment
Да, я понял ;) Я думаю, что моя расшифровка работает сейчас, я работаю над шифрованием, и как только я увижу, что все в порядке, я опубликую ответ здесь!   -  person Alpha4    schedule 22.07.2016
comment
Итак, это было ложное срабатывание. Safari, Safari Technology Preview, Firefox Developer Edition и openssl заканчивают тем, что мой сервер выдает несоответствие тегов при расшифровке. Я не понимаю, почему я могу расшифровать его с помощью Chrome, но, черт возьми, я бы предпочел поддерживать все остальные, особенно когда openssl s_client кажется предпочтительным клиентом для тестирования серверов :)   -  person Alpha4    schedule 11.08.2016