Несоответствие тега Java AES GCM

У меня проблема с шифрованием Java AES GCM. Я следовал инструкциям на этой странице для генерации следующего кода:

package aes;

import java.nio.ByteBuffer;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AES {
    private Cipher encryptCipher;
    private Cipher decryptCipher;
    private byte[] key;
    private int keyLength;
    private SecretKeySpec keySpec;
    private SecureRandom random;

    public AES(String key) {
        try {
            this.key = key.getBytes();
            this.keyLength = this.key.length*8; // key length in bits
            this.keySpec = new SecretKeySpec(this.key, "AES");
            this.encryptCipher = Cipher.getInstance("AES/GCM/NoPadding");
            this.decryptCipher = Cipher.getInstance("AES/GCM/NoPadding");
            this.random = SecureRandom.getInstance("SHA1PRNG");
        }
        catch (Exception e) {
            System.err.println(e.getMessage());
        }
    }
    public byte[] encrypt(byte[] plaintext) {
        try {
            byte[] iv = new byte[12]; // create new IV
            random.nextBytes(iv); 
            encryptCipher.init(Cipher.ENCRYPT_MODE, keySpec, new GCMParameterSpec(keyLength, iv));
            byte[] encrypted = encryptCipher.doFinal(plaintext);
            ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + encrypted.length);
            byteBuffer.put(iv);
            byteBuffer.put(encrypted);
            return byteBuffer.array(); // IV(0)...IV(11) + ENCRYPTED(0)...ENCRYPTED(N)
        }
        catch (Exception e) {
            System.err.println(e.getMessage());
            e.printStackTrace();
            return null;
        }
    }
    public byte[] decrypt(byte[] ciphertext) {
        try {
            ByteBuffer byteBuffer = ByteBuffer.wrap(ciphertext);
            byte[] iv = new byte[12];
            byteBuffer.get(iv);
            byte[] encrypted = new byte[byteBuffer.remaining()];
            byteBuffer.get(encrypted);
            decryptCipher.init(Cipher.DECRYPT_MODE, keySpec, new GCMParameterSpec(keyLength, iv));
            return decryptCipher.doFinal(ciphertext);
        }
        catch (Exception e) {
            System.err.println(e.getMessage());
            e.printStackTrace();
            return null;
        }
    } 
}

В основном я называю все это следующим образом:

package aes;

public class Main {
    static byte[] plaintext = new byte[] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53};
    public static void main(String[] args) {
        AES aes = new AES("Random09Random09");

        byte[] encrypted = aes.encrypt(plaintext);
        byte[] decrypted = aes.decrypt(encrypted);  
    }
}

Теперь я всегда получаю ошибку несоответствия тегов, например:

Tag mismatch!
javax.crypto.AEADBadTagException: Tag mismatch!
    at com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:578)
    at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1049)
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:985)
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:847)
    at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
    at javax.crypto.Cipher.doFinal(Cipher.java:2164)
    at aes.AES.decrypt(AES.java:55)
    at aes.Main.main(Main.java:9)

причину которого я не могу определить. StackTrace мне не помогает. Я был бы признателен, если бы кто-то мог помочь мне с этим. Как вы можете видеть в StackTrace, ошибка заключается в расшифровке на шаге

doFinal (...)

person Lukas Nothhelfer    schedule 17.12.2018    source источник
comment
@kelalaka Это решение? Насколько мне известно, Base64 — это просто кодировка для получения печатных символов. Как это должно быть связано с проблемой тегов?   -  person Lukas Nothhelfer    schedule 17.12.2018
comment
Как упоминалось в вопросе, у меня есть основная часть кода с веб-сайта. Там вроде все работает. Я не понимаю, почему это не так со мной   -  person Lukas Nothhelfer    schedule 17.12.2018
comment
ааа, для режима GCM требуется тег. cipher.updateAAD (аад); это отсутствует в шифровании. см. эту строку 48. Совпадают ли результаты?   -  person kelalaka    schedule 17.12.2018
comment
@kelalaka Спасибо за ваши усилия и ссылку, которую вы разместили. Это было не так. Тем не менее, я нашел ошибку. Я скоро создам ответ.   -  person Lukas Nothhelfer    schedule 17.12.2018
comment
Несвязанный : вы можете убедиться, что this.key = key.getBytes(); соответствует вашим потребностям. String#getBytes — это системно-зависимый метод (зависит от схемы кодирования по умолчанию вашей хост-системы, обычно UTF8 в Linux, но YMMV в Windows, других * nix, MacOS или если вы запускаете JVM со специальными переменными среды или -Dfile.encoding...) Возможно, вы захотите проверить, откуда берутся ваши пароли (русский, китайский или латинский алфавиты?) и как строки могут быть преобразованы в байты, принимая во внимание вашу систему в целом (внешние программы).   -  person GPI    schedule 17.12.2018
comment
@GPI Большое спасибо за ответ. Это не общедоступная система, но она инкапсулирована, а ОС и все остальное фиксировано и предопределено.   -  person Lukas Nothhelfer    schedule 17.12.2018


Ответы (1)


Наконец-то я нашел ошибку сам. Ошибка заключалась в том, что в методе расшифровки я пытался сделать doFinal (...) для всего полученного сообщения, а не только для извлеченного зашифрованного текста. Я оставил ошибку в своем вопросе и размещаю здесь соответствующую (теперь правильную) часть программы. @kelalaka Спасибо за ваши усилия.

    public byte[] decrypt(byte[] ciphertext) {
        try {
            ByteBuffer byteBuffer = ByteBuffer.wrap(ciphertext);
            byte[] iv = new byte[12];
            byteBuffer.get(iv);
            byte[] encrypted = new byte[byteBuffer.remaining()];
            byteBuffer.get(encrypted);
            decryptCipher.init(Cipher.DECRYPT_MODE, keySpec, new GCMParameterSpec(keyLength, iv));
            return decryptCipher.doFinal(encrypted); // here was the mistake
        }
        catch (Exception e) {
            System.err.println(e.getMessage());
            e.printStackTrace();
            return null;
        }
    } 

И главное:

package aes;

public class Main {
    static byte[] plaintext = new byte[] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53};
    public static void main(String[] args) {
        AES aes = new AES("Random09Random09");

        byte[] encrypted = aes.encrypt(plaintext);
        byte[] decrypted = aes.decrypt(encrypted);  
        System.out.println("Original:\n" + new String(plaintext) + "\nEncrypted:\n" + new String(encrypted) + "\nDecrypted:\n" + new String(decrypted));
    }
}

(Теперь правильный) вывод программы:

Original:
ABCDEFGHIJKLMNOPQRS
Encrypted:
�~q꽕kl�9���&�ZB=�WPU�"�'�H���]:?Bo
Decrypted:
ABCDEFGHIJKLMNOPQRS
person Lukas Nothhelfer    schedule 17.12.2018
comment
Выглядит хорошо, кроме SHA1PRNG и генерации ключей. Плюс SecretKey и SecretKeySpec нужно очищать после использования... - person Saptarshi Basu; 17.12.2018