Почему я получаю сообщение об ошибке недопустимого заголовка потока при чтении объекта из 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();
}
}
Приведенный выше код записывает, а затем читает 2 файла, один использует потоки Cipher, один просто потоки File, это как раз для иллюстрации проблемы, что версия CipherStream не работает при чтении. Я думаю, что теперь я удовлетворен тем, что это не проблема обертывания потоков в потоках или запечатанных объектов, скорее это потому, что я использовал тот же объект Cipher для инициализации Cipher Stream, который я использую для распечатки объектов ... каким-то образом он потребляет заголовок .. может быть что-то происходит в предвкушении?




В этом нет смысла. Вам не нужны оба потока Cipher, а также и SealedObject, и если вы рассмотрите фактический порядок выполнения всех из них по отношению к Cipher, вы увидите, что он не симметричен между записью и чтением.
Потеряйте SealedObject или потеряйте потоки Cipher.
Даже если вы удалите файл SealedObject. То, как они читают / пишут, они неправильно шифруют / дешифруют.
@Matt А там CipherInput/OutputStreams? Почему нет?
Я не уверен, когда я пробовал их код, он не работал, расшифрованные байты другие. Думаю, должно работать, но что-то не так.
@matt Невозможно воспроизвести. Получил обратно "Test".
Вы правы, я совершил глупую ошибку.
Я обнаружил, что проблему можно решить, используя различные объекты Cipher для операций CipherInputStream и Sealed (в приведенном выше коде я повторно использовал тот же объект). Еще я заметил, что CipherInputStreams не поддерживает отметку / сброс - это может быть актуально, хотя я бы надеялся, что отметка / сброс не поддерживается исключением, а не молча делает неправильные вещи.
@Mel Вопрос бессмысленный. Двойное шифрование не вдвое безопаснее. mark()/reset() не имеют к этому никакого отношения, и ваши предположения о том, что он «молча делает неправильные вещи», не только безосновательны, но и бессмысленны. Как он может сделать что-нибудь, если его даже нет?
@EJP Двойное шифрование так же бессмысленно, как использование безопасного протокола через безопасное беспроводное соединение. Но я задал не тот вопрос. Бессмысленно или нет, но это не сработало, и я считал, что так должно быть. Оказалось, что ошибка заключалась в том, что я дважды использовал объект Cipher.
Вы пытались просто прочитать расшифрованные байты из зашифрованного файла и сравнить их с байтами, которые вы прочитали из незашифрованного файла? Например, длина? Похоже, вы близки к этому, но вы просто используете вывод исключения.