Не удалось десериализовать запечатанные объекты из входного потока Cipher

Почему я получаю сообщение об ошибке недопустимого заголовка потока при чтении объекта из CipherInputStream? (изменить: возможно, из-за повторного использования объекта Cipher? новая версия примера кода, включенная ниже, решила проблему.)

Моя программа пытается читать из ObjectInputStream, который использует CipherInputStream в качестве источника (который берется из файла), то есть:

Файл -> DeCipher Stream -> DeSerialize -> Объект

Ошибка выполнения:

java.io.StreamCorruptedException: invalid stream header: 73720019

Метод записи в приведенном ниже примере программы записывает файл, используя простой FileOutputStream а также в качестве CipherOutputStream, поэтому у нас есть 2 файла для проверки. Метод чтения аналогичным образом пытается прочитать оба файла. Простой файл записывается и читается без проблем, однако зашифрованный файл вызывает исключение. Если вы посмотрите на первые 8 байтов простого файла, вы увидите, что они:

 0000000 254 355  \0 005   s   r  \0 031   j   a   v   a   x   .   c   r

          ac  ed  00  05  73  72  00  19  6a  61  76  61  78  2e  63  72

Где «ac ed 00 05» соответствует ObjectStream Magic и Version (то есть заголовку): https://docs.oracle.com/javase/8/docs/platform/serialization/spec/protocol.html

НО «поврежденный» заголовок, о котором сообщает Exception, соответствует четырем байтам после заголовка: 73 72 00 19!

Мне кажется, что CipherInputStream правильно расшифровывает поток, но потребляет или пропускает сам заголовок и доставляет данные, начиная с байта 5.

Это плохой вывод:

    run:
    Read Object (plain): Test
    Exception in readTest: 
    java.io.StreamCorruptedException: invalid stream header: 73720019
    BUILD SUCCESSFUL (total time: 0 seconds)

Вот код:


    package encryptedobjectstreamexample;

    import java.io.*;
    import java.security.*;
    import javax.crypto.*;
    import static javax.crypto.Cipher.*;
    import javax.crypto.spec.*;

    public class EncryptedObjectStreamExample {
        static boolean cipherOn = true;
        static final String fn = "C:/Temp/object.";
        static final byte[] keyBytes = "MySecretPass1234".getBytes();
        static final byte[] iv = "initialialvector".getBytes();
        static String testObject = "Test";
        Cipher c;
        AlgorithmParameters ap;
        IvParameterSpec ivp;
        Key k;

        public EncryptedObjectStreamExample() {
            try {
                c = Cipher.getInstance("AES/CBC/PKCS5Padding");
                ap = AlgorithmParameters.getInstance("AES");
                ivp = new IvParameterSpec(iv);
                ap.init(ivp);
                k = new SecretKeySpec(keyBytes, "AES");
            } catch (Exception ex) {
                System.err.println("Failed Constructor:\n" + ex);
                System.exit(1);
            }
        }

        public void writeTest() {
            // Object -> Serialize -> Cipher Stream -> File
            try {
                c.init(ENCRYPT_MODE, k, ap);
                OutputStream ostrp = new FileOutputStream(fn+"p");
                OutputStream ostrx = new CipherOutputStream(new FileOutputStream(fn+"x"),c);
                try (ObjectOutputStream oos = new ObjectOutputStream(ostrp)) {
                    oos.writeObject(new SealedObject(testObject, c));
                }
                try (ObjectOutputStream oos = new ObjectOutputStream(ostrx)) {
                    oos.writeObject(new SealedObject(testObject, c));
                }
            } catch (Exception e) {
                System.err.println("Exception in writeTest: \n" + e);
            }
        }

        private void readTest() {
            // File -> DeCipher Stream -> DeSerialize -> Object
            try {
                c.init(DECRYPT_MODE, k, ap);
                InputStream istrp = new FileInputStream("C:/Temp/object.p");
                InputStream istrx = new CipherInputStream(new FileInputStream("C:/Temp/object.x"),c);
                try (ObjectInputStream ois = new ObjectInputStream(istrp)) {
                    String result = (String) (((SealedObject) ois.readObject()).getObject(c));
                    System.out.println("Read Object (plain): " + result);
                }
                try (ObjectInputStream ois = new ObjectInputStream(istrx)) {
                    String result = (String) (((SealedObject) ois.readObject()).getObject(c));
                    System.out.println("Read Object (encrypt): " + result);
                }
            } catch (Exception e) {
                System.err.println("Exception in readTest: \n" + e);
            }
        }

        public static void main(String[] args) {
            EncryptedObjectStreamExample eos = new EncryptedObjectStreamExample();
            eos.writeTest();
            eos.readTest();
        }
    }

Редактировать: Я немного переделал код, чтобы использовать разные объекты Cipher для потока и операций Seal: x.sealdob и x.iostream.

Этот измененный код теперь работает правильно, оба файла записываются и читаются без ошибок:

Это новый (хороший) результат:

    run:
    Read Object (plain): Test
    Read Object (encrypt): Test
    BUILD SUCCESSFUL (total time: 0 seconds)

Вот обновленный код (который работает):


    package encryptedobjectstreamexample;

    import java.io.*;
    import java.security.*;
    import java.security.spec.InvalidParameterSpecException;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import javax.crypto.*;
    import static javax.crypto.Cipher.*;
    import javax.crypto.spec.*;

    public class EncryptedObjectStreamCipherX {

    static boolean cipherOn = true;
    static final String FN = "C:/Temp/object.";
    static final byte[] keyBytes = "MySecretPass1234".getBytes();
    static final byte[] IV = "initialialvector".getBytes();
    static String testObject = "Test";
    Xipher x;
    Key k;

    private class Xipher {

        AlgorithmParameters ap;
        Cipher iostream, sealedob;
        static final String ALG = "AES";

        Xipher() {
            try {
                iostream = Cipher.getInstance(ALG + "/CBC/PKCS5Padding");
                sealedob = Cipher.getInstance(ALG + "/CBC/PKCS5Padding");
                ap = AlgorithmParameters.getInstance(ALG);
                ap.init(new IvParameterSpec(IV));
                k = new SecretKeySpec(keyBytes, "AES");
            } catch (NoSuchPaddingException | InvalidParameterSpecException | NoSuchAlgorithmException ex) {
                Logger.getLogger(EncryptedObjectStreamCipherX.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        void encryptMode() {
            try {
                iostream.init(ENCRYPT_MODE, new SecretKeySpec(keyBytes, ALG), ap);
                sealedob.init(ENCRYPT_MODE, new SecretKeySpec(keyBytes, ALG), ap);
            } catch (InvalidKeyException | InvalidAlgorithmParameterException ex) {
                Logger.getLogger(EncryptedObjectStreamCipherX.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        void decryptMode() {
            try {
                iostream.init(DECRYPT_MODE, new SecretKeySpec(keyBytes, ALG), ap);
                sealedob.init(DECRYPT_MODE, new SecretKeySpec(keyBytes, ALG), ap);
            } catch (InvalidKeyException | InvalidAlgorithmParameterException ex) {
                Logger.getLogger(EncryptedObjectStreamCipherX.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    public EncryptedObjectStreamCipherX() {
        try {
            x = new Xipher();

        } catch (Exception ex) {
            System.err.println("Failed Constructor:\n" + ex);
            System.exit(1);
        }
    }

    public void writeTest() {
        // Object -> Serialize -> Cipher Stream -> File
        try {
            x.encryptMode();
            OutputStream ostrp = new FileOutputStream(FN + "p");
            OutputStream ostrx = new CipherOutputStream(new FileOutputStream(FN + "x"), x.iostream);
            try (ObjectOutputStream oos = new ObjectOutputStream(ostrp)) {
                oos.writeObject(new SealedObject(testObject, x.sealedob));
            }
            try (ObjectOutputStream oos = new ObjectOutputStream(ostrx)) {
                oos.writeObject(new SealedObject(testObject, x.sealedob));
            }
        } catch (Exception e) {
            System.err.println("Exception in writeTest: \n" + e);
        }
    }

    private void readTest() {
        // File -> DeCipher Stream -> DeSerialize -> Object
        try {
            x.decryptMode();
            InputStream istrp = new FileInputStream("C:/Temp/object.p");
            InputStream istrx = new CipherInputStream(new FileInputStream("C:/Temp/object.x"), x.iostream);
            try (ObjectInputStream ois = new ObjectInputStream(istrp)) {
                String result = (String) (((SealedObject) ois.readObject()).getObject(x.sealedob));
                System.out.println("Read Object (plain): " + result);
            }
            try (ObjectInputStream ois = new ObjectInputStream(istrx)) {
                String result = (String) (((SealedObject) ois.readObject()).getObject(x.sealedob));
                System.out.println("Read Object (encrypt): " + result);
            }
        } catch (Exception e) {
            System.err.println("Exception in readTest: \n" + e);
        }
    }

    public static void main(String[] args) {
        EncryptedObjectStreamCipherX eos = new EncryptedObjectStreamCipherX();
        eos.writeTest();
        eos.readTest();
    }
    }

Вы пытались просто прочитать расшифрованные байты из зашифрованного файла и сравнить их с байтами, которые вы прочитали из незашифрованного файла? Например, длина? Похоже, вы близки к этому, но вы просто используете вывод исключения.

matt 20.03.2018 11:43

Приведенный выше код записывает, а затем читает 2 файла, один использует потоки Cipher, один просто потоки File, это как раз для иллюстрации проблемы, что версия CipherStream не работает при чтении. Я думаю, что теперь я удовлетворен тем, что это не проблема обертывания потоков в потоках или запечатанных объектов, скорее это потому, что я использовал тот же объект Cipher для инициализации Cipher Stream, который я использую для распечатки объектов ... каким-то образом он потребляет заголовок .. может быть что-то происходит в предвкушении?

Mel 21.03.2018 12:55
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
2
487
1

Ответы 1

В этом нет смысла. Вам не нужны оба потока Cipher, а также и SealedObject, и если вы рассмотрите фактический порядок выполнения всех из них по отношению к Cipher, вы увидите, что он не симметричен между записью и чтением.

Потеряйте SealedObject или потеряйте потоки Cipher.

Даже если вы удалите файл SealedObject. То, как они читают / пишут, они неправильно шифруют / дешифруют.

matt 20.03.2018 13:00

@Matt А там CipherInput/OutputStreams? Почему нет?

user207421 21.03.2018 04:23

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

matt 21.03.2018 07:18

@matt Невозможно воспроизвести. Получил обратно "Test".

user207421 21.03.2018 08:06

Вы правы, я совершил глупую ошибку.

matt 21.03.2018 08:26

Я обнаружил, что проблему можно решить, используя различные объекты Cipher для операций CipherInputStream и Sealed (в приведенном выше коде я повторно использовал тот же объект). Еще я заметил, что CipherInputStreams не поддерживает отметку / сброс - это может быть актуально, хотя я бы надеялся, что отметка / сброс не поддерживается исключением, а не молча делает неправильные вещи.

Mel 21.03.2018 12:39

@Mel Вопрос бессмысленный. Двойное шифрование не вдвое безопаснее. mark()/reset() не имеют к этому никакого отношения, и ваши предположения о том, что он «молча делает неправильные вещи», не только безосновательны, но и бессмысленны. Как он может сделать что-нибудь, если его даже нет?

user207421 22.03.2018 00:24

@EJP Двойное шифрование так же бессмысленно, как использование безопасного протокола через безопасное беспроводное соединение. Но я задал не тот вопрос. Бессмысленно или нет, но это не сработало, и я считал, что так должно быть. Оказалось, что ошибка заключалась в том, что я дважды использовал объект Cipher.

Mel 22.03.2018 06:59

Другие вопросы по теме