FULL PRODUCT VERSION :
java version "1.7.0_03"
Java(TM) SE Runtime Environment (build 1.7.0_03-b05)
Java HotSpot(TM) Client VM (build 22.1-b02, mixed mode, sharing)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.1.7601]
EXTRA RELEVANT SYSTEM CONFIGURATION :
CPU: Intel Core i7 2600K
A DESCRIPTION OF THE PROBLEM :
As DigestOutputStream does not implement the FilterOutputStream.close() method, it has no awareness of the stream being actually closed. When the stream is closed and digest calculation is left "on", this object continues updating its MessageDigest on subsequent calls on any of its write() methods.
This causes problem in case of nested OutputStreams. In particular, nesting an ObjectOutputStream over a CipherOutputStream (with block ciphers such as AES) which is on top of a DigestOutputStream reveals the problem, that is, when we are finished with the inner most stream (the ObjectOutputSteam in this case), we have to call on its close method. This call, in turn, forces a flush on CipherOutputStream which DOES NOT FLUSH its unprocessed buffers. Then comes the turn for CipherOutputSteam being closed, which first performs a flush and then a final output of its residue buffers to the underlying stream (DigestOutputStream), followed by a flush and close on DigestOutputStream.
Things are pretty clear up to this point, but for some strange reasons, CipherOutputSteam decides to do a flush and re-write its latest buffer values after it has closed its underlying stream. Since DigestOutputStream has not taken last "close()" call seriously, it accepts the "write()" call and updates its digest AND THEN passes the call to the underlying stream which reasonably throws a "Stream Closed" IOExcepion, but since the CipherOutputStram is already closed, it will not re-throw the exception and no one on the outside will eve be told of this. But, the one who is left in the dark is the poor DigestOutputStream which has already updated its digest WITHOUT first taking into account its state and to a lesser degree WITHOUT waiting for an underlying "write()" to succeed.
It might be more of a problem by CipherOutputStream calling "write()" after being closed, but more or less, it is also a flaw in DigestOutputStream.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Create a FileOutputStream
2. Create a DigestOutputStream (SHA-1) over it
3. Initialize a block-cipher (AES-128) and create a CipherOutputStream over the previous one
4. Create an ObjectOutputStream
5. Write some data to the ObjectOutputStream
6. Close the ObjectOutputStream
7. Close the CipherOutputSteam (if necessary)
8. Close the DigestOutputStream (if necessary)
9. Close the FileOutputStream (if necessary)
10. Call "digest()" on the MessageDigest object of the DigestOutputStream and store the result.
EXPECTED VERSUS ACTUAL BEHAVIOR :
The result stored in step 10 must be equal to an independently calculated SHA-1 checksum of the file contents, which is surprisingly not, as mentioned earlier.
A different checksum, due to a late call to "write()" method which INCORRECTLY updated the message digest.
ERROR MESSAGES/STACK TRACES THAT OCCUR :
no error message is produces here
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
public class Test
public static void main(String args) throws Exception
* Producing the problem
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
KeyGenerator aesKeyGen = KeyGenerator.getInstance("AES");
SecretKey aesKey = aesKeyGen.generateKey();
Cipher aes = Cipher.getInstance("AES");
try (FileOutputStream fos = new FileOutputStream("test.bin");
DigestOutputStream dos = new DigestOutputStream(fos, sha1);
CipherOutputStream cos = new CipherOutputStream(dos, aes);
ObjectOutputStream oos = new ObjectOutputStream(cos))
oos.writeObject("This is a sample data to be AES encrypted.");
byte incorrectSha1 = sha1.digest();
* Calculating the correct SHA1 for the file
byte correctSha1 = null;
try (FileInputStream fis = new FileInputStream("test.bin"))
byte buffer = new byte;
count = fis.read(buffer);
sha1.update(buffer, 0, count);
correctSha1 = sha1.digest();
if(Arrays.equals(correctSha1, incorrectSha1) == false)
System.out.println("The flaw still exists!");
System.out.println("The problem is resolved.");
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
The simplest solution to work this around is to subclass DigestOutputStream, override the "close()" method and TURN ITS CALCULATIONS OFF by calling "on(false)" after calling the "super.close()". Following snippet demonstrate it:
public static class CorrectDigestOutputStream extends DigestOutputStream
public CorrectDigestOutputStream(OutputStream stream, MessageDigest digest)
public void close() throws IOException
We might be able to fix it by overriding the "write()" methods, but this one is more concise.