Why do I get an invalid stream header error when I read an object from a CipherInputStream ? (edit: possibly due to re-use of Cipher object?, new version of sample code included below has resolved the issue.)
My program is trying to read from an ObjectInputStream which is using a CipherInputStream as a source (which is sourced from a file), ie:
File -> DeCipher Stream -> DeSerialize -> Object
The runtime error is:
java.io.StreamCorruptedException: invalid stream header: 73720019
The write method in the example program listed below writes the file using a plain FileOutputStream and as a CipherOutputStream, so we have 2 files to examine. The read method similarly attempts to read both files. The plain file is written and read without issues, however the encrypted file throws the exception. If you look at the first 8 bytes of the plain file, you can see they are:
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
Where "ac ed 00 05" corresponds to the ObjectStream Magic and Version (ie the header): https://docs.oracle.com/javase/8/docs/platform/serialization/spec/protocol.html
BUT the 'corrupt' header reported by the Exception corresponds to the four bytes after the header: 73 72 00 19 !
It looks to me like the CipherInputStream is decrypting the stream correctly but consuming or skipping the header itself and delivering the data starting at byte 5.
This is the bad output:
run: Read Object (plain): Test Exception in readTest: java.io.StreamCorruptedException: invalid stream header: 73720019 BUILD SUCCESSFUL (total time: 0 seconds)
Here is the code:
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(); } }
Edit: I've re-jigged the code somewhat so that I use different Cipher objects for the stream and the Seal operations: x.sealdob and x.iostream
This modified code now runs correctly, both files are written and read back without error:
This is the new (good) output:
run: Read Object (plain): Test Read Object (encrypt): Test BUILD SUCCESSFUL (total time: 0 seconds)
Here is the updated code (that works):
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(); } }