Skip to main content

Serializing to a CipherOutputStream gets corrupted data

6 replies [Last post]
wiebe_ruiter
Offline
Joined: 2006-06-05
Points: 0

Hi,

I implemented two methods that try to store a JAXB object encrypted to an outputstream. One is actually working, named 'store' and the other one works, but has side effects, named 'storeButWithBadPddingExceptionLater'.

Doing side by side comparisons of what they store, the 'store' method stores everything and the faulty one stores a file that is shorter.

When loading the file stored with the faulty method, a BadPaddingException is thrown. I tried with both jce crypto implementations and with bouncycastle jce implementations, because my first guess was that the problem was in the crypto code. But having these methods side by side, both using the CipherOutputStream and seeing different file sizes makes me think that it somewhere between the Marshaller and the CipherOutputStream.

The method that works has a big disadvantage because it needs to dump everything in a byte buffer first, which eats up memory fast. Any ideas what to do ?

<br />
    public static void storeButWithBadPaddingExceptionLater( Changingtimes item, char[] password, OutputStream out ) throws JAXBException{</p>
<p>        Cipher pbeCipher = initialize( Cipher.ENCRYPT_MODE, password );<br />
        CipherOutputStream result = new CipherOutputStream( out, pbeCipher );<br />
        store( item, result );<br />
    }<br />
    public static void store( Changingtimes item, char[] password, OutputStream out ) throws JAXBException{</p>
<p>        ByteArrayOutputStream buffer = new ByteArrayOutputStream();<br />
        store( item, buffer );</p>
<p>        Cipher pbeCipher = initialize( Cipher.ENCRYPT_MODE, password );<br />
        CipherOutputStream result = new CipherOutputStream( out, pbeCipher );<br />
        try{<br />
            result.write( buffer.toByteArray() );<br />
            result.close();<br />
        }<br />
        catch( IOException ioe ){ throw new JAXBException( ioe ); }<br />
    }<br />
    public static Cipher initialize( int ciphermode, char[] password ) throws JAXBException{</p>
<p>        PBEKeySpec pbeKeySpec;<br />
        PBEParameterSpec pbeParamSpec;<br />
        SecretKeyFactory keyFac;</p>
<p>        // Salt<br />
        byte[] salt = {<br />
            (byte)0xa1, (byte)0x05, (byte)0x88, (byte)0x9d,<br />
            (byte)0x7e, (byte)0x1a, (byte)0x32, (byte)0x3f<br />
        };</p>
<p>        // Iteration count<br />
        int count = 20;</p>
<p>        // Create PBE parameter set<br />
        pbeParamSpec = new PBEParameterSpec(salt, count);</p>
<p>        pbeKeySpec = new PBEKeySpec(password);<br />
        try{<br />
            //keyFac = SecretKeyFactory.getInstance("PBEWithMD5AndTripleDES" );<br />
            keyFac = SecretKeyFactory.getInstance( "PBEWithSHAAndTwofish-CBC", "BC" );<br />
            SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);</p>
<p>            // Create PBE Cipher<br />
            Cipher pbeCipher = Cipher.getInstance( pbeKey.getAlgorithm() + "/CBC/PKCS5Padding", "BC" );</p>
<p>            // Initialize PBE Cipher with key and parameters<br />
            pbeCipher.init(ciphermode, pbeKey, pbeParamSpec );</p>
<p>            return pbeCipher;<br />
        }<br />
        catch( GeneralSecurityException gse ){<br />
            throw new JAXBException( gse );<br />
        }<br />
    }<br />

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
wyztix
Offline
Joined: 2012-10-16
Points: 0

I know this issue is really old and probably forgotten, but this is where I ended up when googling for a similar problem and I though this could help others like me. It appears that the CipherOutputStream is not user friendly at all. I kept having error from JAXB with the org.xml.sax.SAXParseException: Premature end of file error. Well it appears that I should've read the manual. By the CipherOutputStream documentation(http://docs.oracle.com/javase/6/docs/api/javax/crypto/CipherOutputStream...) the class catches ALL exceptions that are thrown by the cipher and do nothing with them (not event replace by an IOException). After lot of work, I determined the issue was the amount of data sent to the write method. I would've expect a solid implementation from Oracle, but looks like I was wrong: the Cipher doesn't allow more that 117 bytes written at the time using my cipher (RSA 1024 bit - reserved). When tracking the behavior of JAXB, I realized that they write requests comes in a very imprevisable way:

Received write for byte array of len 1024 with offset of 0 and len of 1023
Received write for byte array of len 32 with offset of 0 and len of 8
Received write for byte array of len 1024 with offset of 0 and len of 1014
Received write for byte array of len 32 with offset of 0 and len of 12
Received write for byte array of len 1024 with offset of 0 and len of 1022
Received write for byte array of len 139 with offset of 0 and len of 3
Received write for byte array of len 1024 with offset of 0 and len of 154
Received write for byte array of len 1024 with offset of 0 and len of 0
Received flush

As for the OP, I would assume the issue comes from another weird decision from Oracle. As of the flush documentation (http://docs.oracle.com/javase/6/docs/api/javax/crypto/CipherOutputStream.html#flush()), if the encapsulated cipher is a block cipher, and the total number of bytes written using one of the write methods is less than the cipher's block size, no bytes will be written out. I am FAR from an expert in the security module, but OP seams to uses CBC which points to a block cipher. Since I really doubt JAXB adds useless data at the end of the marshalling, that would explain the decryption issue: the last bytes were dropped when JAXB called the flush(). This is pretty much my understanding of the error message: org.xml.sax.SAXParseException: XML document structures must start and end within the same entity.

In my opinion, this is a really unsafe implementation coming from Oracle: the deveopper MUST know the cipher and how to use it (i.e send 117 bytes at the time, send padding data at the end before calling flush).

kohsuke
Offline
Joined: 2003-06-09
Points: 0

JAXB treats all OutputStreams equally, so it's bit difficult for me to imagine that it causes a problem like this.

Looking at the code, I notice that you are calling CipherOutputStream.close() on the working code but I don't think JAXB calls CipherOutputStream.close() in storeButWithBadPaddingExceptionLater.

Looking at CipherOutputStream source code, the close() method does a non-trivial computation, so I suspect it's a crucial method to invoke.

wiebe_ruiter
Offline
Joined: 2006-06-05
Points: 0

I agree, it has something to do with closing the stream. But I cannot simply add a close() after the marshaller gives me back control, because the BadPaddingException is thrown before I get back control.

My feeling is that somehow the CypherOutputStream figures it has to end operations and does something like a doFinal too early in the process. But I cannot imagine where it would get this notion, if the marshaller is not telling it to.

kohsuke
Offline
Joined: 2003-06-09
Points: 0

When CipherOutputStream throws BadPaddingException, can you show me that stack trace? If that includes JAXB marshaller, that might be of some help for me to track down the problem.

wiebe_ruiter
Offline
Joined: 2006-06-05
Points: 0

OK, I am sorry for putting you on the wrong track a little. The BadPaddingException is not thrown while writing. It is thrown when reading the written material in again. And then only if you read it in as bytes.

That does not mean there is no problem, however. If you feed the stream directly to the unmarshaller, sax does not like it.

Here is my IdentifiableLoader class that loads and saves jaxb object from and to streams, optionally encrypted. Following your remarks, I hoped closing the stream would solve the issue, but it did not:

[code]
/*
* IdentifiableLoader.java
*
* Created on June 30, 2006, 2:12 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/

package com.docner.util.identifiable;

import com.docner.util.identifiable.model.ChangingtimesValue;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import org.xml.sax.InputSource;

/**
*
* @author wiebe
*/
public class IdentifiableLoader extends ChangingtimesValue {

/**
* Creates a new instance of IdentifiableLoader
*/
public IdentifiableLoader() {
}
public static Changingtimes load( Class target, File in )
throws JAXBException, FileNotFoundException{
InputStream source = new FileInputStream( in );
return load( target, source );
}
public static Changingtimes load( Class target, InputStream in )
throws JAXBException{
InputSource source = new InputSource( in );
return load( target, source );
}
public static Changingtimes load( Class target, InputSource in )
throws JAXBException{
JAXBContext jc = JAXBContext.newInstance( target.getPackage().getName(), target.getClassLoader() );
//JAXBContext jc = JAXBContext.newInstance( new Class[]{ ci, ChangingtimesValue.class } );
Unmarshaller u = jc.createUnmarshaller();

Changingtimes result = (Changingtimes)u.unmarshal( in );
return result;
}
public static void store( Changingtimes item, File out )
throws JAXBException{

File parent = out.getParentFile();
try{
if( parent==null || ( parent.isDirectory() || parent.mkdirs() ) ){
FileOutputStream o = new FileOutputStream( out );
store( item, o );
o.flush();
o.close();
}
else
throw new FileNotFoundException( "Directory not available: " + parent.getAbsolutePath() );
}
catch( IOException ioe ){
throw new JAXBException( ioe );
}
}
public static void store( Changingtimes item, OutputStream out )
throws JAXBException{

Class ic = item.getClass();
JAXBContext jc = JAXBContext.newInstance( new Class[]{ ic, ChangingtimesValue.class } );

Marshaller m = jc.createMarshaller();
m.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
m.setProperty( Marshaller.JAXB_ENCODING, "UTF8" );
m.marshal( item, out );
}

// ------------- CRYPTO ----------------

public static Changingtimes load( Class target, char[] password, File in )
throws JAXBException, FileNotFoundException{
InputStream source = new FileInputStream( in );
return load( target, password, source );
}
public static Changingtimes load( Class target, char[] password, InputStream in )
throws JAXBException{

Cipher cipher = initialize( Cipher.DECRYPT_MODE, password );
InputStream decrypt = new CipherInputStream( in, cipher );
InputSource source = new InputSource( decrypt );
return load( target, source );
}
public static void store( Changingtimes item, char[] password, File out )
throws JAXBException{

File parent = out.getParentFile();
try{
if( parent==null || ( parent.isDirectory() || parent.mkdirs() ) )
storeButWithBadPaddingExceptionLater( item, password, new FileOutputStream( out ) );
else
throw new FileNotFoundException( "Directory not available: " + parent.getAbsolutePath() );
}
catch( IOException ioe ){
throw new JAXBException( ioe );
}
}
public static void storeButWithBadPaddingExceptionLater( Changingtimes item, char[] password, OutputStream out ) throws JAXBException{

Cipher pbeCipher = initialize( Cipher.ENCRYPT_MODE, password );
CipherOutputStream result = new CipherOutputStream( out, pbeCipher );
store( item, result );
try{
result.flush();
result.close();
}
catch( IOException ioe ){
throw new JAXBException( ioe );
}
}
public static void store( Changingtimes item, char[] password, OutputStream out ) throws JAXBException{

ByteArrayOutputStream buffer = new ByteArrayOutputStream();
store( item, buffer );

Cipher pbeCipher = initialize( Cipher.ENCRYPT_MODE, password );
CipherOutputStream result = new CipherOutputStream( out, pbeCipher );
try{
result.write( buffer.toByteArray() );
result.close();
}
catch( IOException ioe ){ throw new JAXBException( ioe ); }
}
public static Cipher initialize( int ciphermode, char[] password ) throws JAXBException{

PBEKeySpec pbeKeySpec;
PBEParameterSpec pbeParamSpec;
SecretKeyFactory keyFac;

// Salt
byte[] salt = {
(byte)0xa1, (byte)0x05, (byte)0x88, (byte)0x9d,
(byte)0x7e, (byte)0x1a, (byte)0x32, (byte)0x3f
};

// Iteration count
int count = 20;

// Create PBE parameter set
pbeParamSpec = new PBEParameterSpec(salt, count);

pbeKeySpec = new PBEKeySpec(password);
try{
keyFac = SecretKeyFactory.getInstance("PBEWithMD5AndTripleDES" );
//keyFac = SecretKeyFactory.getInstance( "PBEWithSHAAndTwofish-CBC", "BC" );
SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);

// Create PBE Cipher
Cipher pbeCipher = Cipher.getInstance( pbeKey.getAlgorithm() + "/CBC/PKCS5Padding" );

// Initialize PBE Cipher with key and parameters
pbeCipher.init(ciphermode, pbeKey, pbeParamSpec );

return pbeCipher;
}
catch( GeneralSecurityException gse ){
throw new JAXBException( gse );
}
}
}
[/code]

I wrote a test case to show as clearly as possible what goes wrong:
It outputs also two files, keymanager.xml.encrpted and keymanager.xml.encrpted.compare. These should be exactly the same, but they are not; the 'compare' file that cannot be read in is exactly 8 bytes shorter. Comparing them visually in a text editor it seems that both have exactly the same bytes, but the 'compare' one misses 8 bytes at the end.
So I am still thinking the problem is somewhere in writing these bytes out to the stream.

[code]
public static KeyManager doKeyManagerTest() throws Exception{

User user = new User();
user.city = "Rotterdam";
user.commonname = "Wiebe Ruiter test";
user.country = "NL";
user.email = "****@********";
user.id = 15;
user.idcode = "0f";
user.organization = "Docner BV";
user.password = "********";
user.username = "com.docner.test";

KeyManager test = new KeyManager();

test.setCaPassword( "********" );
test.setCaPrivateKeyAlias( "com.docner.hq" );
test.createSignerKeyAndStore();
test.createPrivateParts( user );

// This printed as a reference
System.out.println( "=============== reference xml ==================" );
IdentifiableLoader.store( test, System.out );

// Now start the crypto stuff writing and reading some variants.
System.out.println( "=============== reference file, this one works fine ==================" );
String path = "/Users/wiebe/Development/java/apps/keys/var/keymanager.xml.encrpted";
char[] pw = "********".toCharArray();
IdentifiableLoader.store( test, pw, new FileOutputStream( new File( path ) ) );
System.out.println( path );
try{
IdentifiableLoader.load( KeyManager.class, pw, new File( path ) );
}
catch( javax.xml.bind.JAXBException jaxe ){
System.out.flush();
jaxe.printStackTrace();
System.out.flush();
}
System.out.flush();
System.out.println( "=============== reference file, this one is broken ==================" );
File outputfile = new File( path + ".compare" );
IdentifiableLoader.storeButWithBadPaddingExceptionLater( test, pw, new FileOutputStream( outputfile ) );
try{
IdentifiableLoader.load( KeyManager.class, pw, outputfile );
}
catch( javax.xml.bind.JAXBException jaxe ){
System.out.flush();
jaxe.printStackTrace();
System.out.flush();
}

System.out.flush();
System.out.println( "=============== delve a little deeper, try decoding this 'manually' ==================" );
Cipher cipher = IdentifiableLoader.initialize( Cipher.DECRYPT_MODE, pw );
//CipherInputStream in = new CipherInputStream( new FileInputStream( path ), cipher );
File inputfile = outputfile;
long size = inputfile.length();
FileInputStream in = new FileInputStream( inputfile );
FileOutputStream out = new FileOutputStream( path + ".decrypted" );
byte[] buf = new byte[65536];
int read = 1;
int sum = 0;
while( read >= 0 ){
read = in.read( buf );
if( read>=0 ){

sum+=read;
byte[] decrypted = null;
if( sum>=size )
decrypted = cipher.doFinal( buf, 0, read );
else
decrypted = cipher.update( buf, 0, read );
if( decrypted.length>0 )
out.write( decrypted );
else
System.out.println( "? encrypted size " + read + " is decrypted size 0?" );
}
}
out.flush(); out.close();

System.out.flush();
System.out.println( "=============== a roundtrip load-and-store with the working version ==================" );
URL url = new URL( "file:" + path );
KeyManager test2 = KeyManager.getInstance( url, pw );
IdentifiableLoader.store( test2, System.out );
return test;
}
[/code]

And this is the output when running this method. Note that despite my flushes, the output is a little garbled up: the line that reads 'delve a little deeper' should actually precede the short stack trace with the BadPaddingException which is separate from the UnmarshalException.

[code]
init:
deps-jar:
compile-single:
run-single:
=============== reference xml ==================


UID=com.docner.test
/u3+7QAAAAIAAAABAAAAAQATdWlV2o+5SxEvg
********


com.docner.hq
/u3+7QAAAAIAAAACAA11fuojoQ==

********

=============== reference file, this one works fine ==================
/Users/wiebe/Development/java/apps/keys/var/keymanager.xml.encrpted
=============== reference file, this one is broken ==================
javax.xml.bind.UnmarshalException
- with linked exception:
[org.xml.sax.SAXParseException: XML document structures must start and end within the same entity.]
at javax.xml.bind.helpers.AbstractUnmarshallerImpl.createUnmarshalException(AbstractUnmarshallerImpl.java:315)
at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.createUnmarshalException(UnmarshallerImpl.java:481)
at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:203)
at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:172)
at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:137)
at com.docner.util.identifiable.IdentifiableLoader.load(IdentifiableLoader.java:63)
at com.docner.util.identifiable.IdentifiableLoader.load(IdentifiableLoader.java:109)
at com.docner.util.identifiable.IdentifiableLoader.load(IdentifiableLoader.java:101)
at com.docner.packager.Main.makeKeyManagerTest(Main.java:74)
at com.docner.packager.Main.main(Main.java:151)
Caused by: org.xml.sax.SAXParseException: XML document structures must start and end within the same entity.
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:236)
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.fatalError(ErrorHandlerWrapper.java:215)
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:386)
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:316)
at com.sun.org.apache.xerces.internal.impl.XMLScanner.reportFatalError(XMLScanner.java:1438)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.endEntity(XMLDocumentFragmentScannerImpl.java:663)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.endEntity(XMLDocumentScannerImpl.java:556)
=============== delve a little deeper, try decoding this 'manually' ==================
at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.endEntity(XMLEntityManager.java:1779)
at com.sun.org.apache.xerces.internal.impl.XMLEntityScanner.load(XMLEntityScanner.java:1758)
at com.sun.org.apache.xerces.internal.impl.XMLEntityScanner.scanContent(XMLEntityScanner.java:694)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanContent(XMLDocumentFragmentScannerImpl.java:1051)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDispatcher.dispatch(XMLDocumentFragmentScannerImpl.java:1649)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:368)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:834)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:764)
at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:148)
at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1242)
at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:199)
... 7 more
Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.SunJCE_h.b(DashoA12275)
at com.sun.crypto.provider.SunJCE_h.b(DashoA12275)
at com.sun.crypto.provider.SunJCE_ae.b(DashoA12275)
at com.sun.crypto.provider.PBEWithMD5AndDESCipher.engineDoFinal(DashoA12275)
at javax.crypto.Cipher.doFinal(DashoA12275)
at com.docner.packager.Main.makeKeyManagerTest(Main.java:100)
at com.docner.packager.Main.main(Main.java:151)
Java Result: 1
BUILD SUCCESSFUL (total time: 16 seconds)
[/code]

What do you think ?

kohsuke
Offline
Joined: 2003-06-09
Points: 0

Can you file a complete test case to http://jaxb.dev.java.net/issues/ so that we can look into this?