Author: kiwiwings
Date: Fri Aug 19 20:23:16 2016
New Revision: 1756964

URL: http://svn.apache.org/viewvc?rev=1756964&view=rev
Log:
add encryption support

Modified:
    
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/model/InternalWorkbook.java
    
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/BoundSheetRecord.java
    
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/FilePassRecord.java
    
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java
    
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/RecordInputStream.java
    
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/cont/ContinuableRecordInput.java
    
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/crypto/Biff8DecryptingStream.java
    
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
    
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java
    
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java
    
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java
    
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/Encryptor.java
    
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Encryptor.java
    
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java
    
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XORDecryptor.java
    
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionHeader.java
    
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionVerifier.java
    
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptor.java
    
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/filesystem/DocumentInputStream.java
    
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/util/LittleEndianByteArrayInputStream.java
    
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/util/LittleEndianInput.java
    
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/util/LittleEndianInputStream.java
    
poi/branches/hssf_cryptoapi/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowEncrypted.java
    
poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/record/TestRecordFactoryInputStream.java
    
poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/usermodel/TestCryptoAPI.java

Modified: 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/model/InternalWorkbook.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/model/InternalWorkbook.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/model/InternalWorkbook.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/model/InternalWorkbook.java
 Fri Aug 19 20:23:16 2016
@@ -18,6 +18,7 @@
 package org.apache.poi.hssf.model;
 
 import java.security.AccessControlException;
+import java.security.GeneralSecurityException;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
@@ -25,6 +26,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
+import javax.crypto.SecretKey;
+
+import org.apache.poi.EncryptedDocumentException;
 import org.apache.poi.ddf.EscherBSERecord;
 import org.apache.poi.ddf.EscherBoolProperty;
 import org.apache.poi.ddf.EscherContainerRecord;
@@ -52,6 +56,7 @@ import org.apache.poi.hssf.record.Escher
 import org.apache.poi.hssf.record.ExtSSTRecord;
 import org.apache.poi.hssf.record.ExtendedFormatRecord;
 import org.apache.poi.hssf.record.ExternSheetRecord;
+import org.apache.poi.hssf.record.FilePassRecord;
 import org.apache.poi.hssf.record.FileSharingRecord;
 import org.apache.poi.hssf.record.FnGroupCountRecord;
 import org.apache.poi.hssf.record.FontRecord;
@@ -82,8 +87,13 @@ import org.apache.poi.hssf.record.Window
 import org.apache.poi.hssf.record.WriteAccessRecord;
 import org.apache.poi.hssf.record.WriteProtectRecord;
 import org.apache.poi.hssf.record.common.UnicodeString;
+import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
 import org.apache.poi.hssf.util.HSSFColor;
 import org.apache.poi.poifs.crypt.CryptoFunctions;
+import org.apache.poi.poifs.crypt.Decryptor;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.poifs.crypt.EncryptionMode;
+import org.apache.poi.poifs.crypt.Encryptor;
 import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalName;
 import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
 import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheetRange;
@@ -1082,10 +1092,8 @@ public final class InternalWorkbook {
         SSTRecord sst = null;
         int sstPos = 0;
         boolean wroteBoundSheets = false;
-        for ( int k = 0; k < records.size(); k++ )
-        {
+        for ( Record record : records ) {
 
-            Record record = records.get( k );
             int len = 0;
             if (record instanceof SSTRecord)
             {
@@ -1124,6 +1132,8 @@ public final class InternalWorkbook {
      * Include in it ant code that modifies the workbook record stream and 
affects its size.
      */
     public void preSerialize(){
+        updateEncryptionRecord();
+        
         // Ensure we have enough tab IDs
         // Can be a few short if new sheets were added
         if(records.getTabpos() > 0) {
@@ -1134,6 +1144,49 @@ public final class InternalWorkbook {
         }
     }
 
+    private void updateEncryptionRecord() {
+        FilePassRecord fpr = null;
+        int fprPos = -1;
+        for (Record r : records.getRecords()) {
+            fprPos++;
+            if (r instanceof FilePassRecord) {
+                fpr = (FilePassRecord)r;
+                break;
+            }
+        }
+        
+        String password = Biff8EncryptionKey.getCurrentUserPassword();
+        if (password == null) {
+            if (fpr != null) {
+                // need to remove password data
+                records.remove(fprPos);
+            }
+            return;
+        } else {
+            // create password record
+            if (fpr == null) {
+                fpr = new FilePassRecord(EncryptionMode.binaryRC4);
+                records.add(1, fpr);
+            }
+            
+            // check if the password has been changed
+            EncryptionInfo ei = fpr.getEncryptionInfo();
+            byte encVer[] = ei.getVerifier().getEncryptedVerifier();
+            try {
+                Decryptor dec = ei.getDecryptor();
+                Encryptor enc = ei.getEncryptor();
+                if (encVer == null || !dec.verifyPassword(password)) {
+                    enc.confirmPassword(password);
+                } else {
+                    SecretKey sk = dec.getSecretKey();
+                    ei.getEncryptor().setSecretKey(sk);
+                }
+            } catch (GeneralSecurityException e) {
+                throw new EncryptedDocumentException("can't validate/update 
encryption setting", e);
+            }
+        }
+    }
+    
     public int getSize()
     {
         int retval = 0;

Modified: 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/BoundSheetRecord.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/BoundSheetRecord.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/BoundSheetRecord.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/BoundSheetRecord.java
 Fri Aug 19 20:23:16 2016
@@ -24,6 +24,8 @@ import java.util.List;
 import org.apache.poi.util.BitField;
 import org.apache.poi.util.BitFieldFactory;
 import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianConsts;
 import org.apache.poi.util.LittleEndianOutput;
 import org.apache.poi.util.StringUtil;
 import org.apache.poi.ss.util.WorkbookUtil;
@@ -60,7 +62,9 @@ public final class BoundSheetRecord exte
         * @param in the record stream to read from
         */
        public BoundSheetRecord(RecordInputStream in) {
-               field_1_position_of_BOF = in.readInt();
+           byte buf[] = new byte[LittleEndianConsts.INT_SIZE];
+           in.readPlain(buf, 0, buf.length);
+               field_1_position_of_BOF = LittleEndian.getInt(buf);
                field_2_option_flags = in.readUShort();
                int field_3_sheetname_length = in.readUByte();
                field_4_isMultibyteUnicode = in.readByte();

Modified: 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/FilePassRecord.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/FilePassRecord.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/FilePassRecord.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/FilePassRecord.java
 Fri Aug 19 20:23:16 2016
@@ -56,6 +56,11 @@ public final class FilePassRecord extend
         }
        }
        
+       public FilePassRecord(EncryptionMode encryptionMode) {
+           encryptionType = (encryptionMode == EncryptionMode.xor) ? 
ENCRYPTION_XOR : ENCRYPTION_OTHER;
+           encryptionInfo = new EncryptionInfo(encryptionMode);
+       }
+       
        public FilePassRecord(RecordInputStream in) {
                encryptionType = in.readUShort();
                

Modified: 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java
 Fri Aug 19 20:23:16 2016
@@ -78,20 +78,16 @@ public final class RecordFactoryInputStr
                       outputRecs.add(rec);
                                        }
                                        
-                                       // If it's a FILEPASS, track it 
specifically but
-                                       //  don't include it in the main stream
+                                       // If it's a FILEPASS, track it 
specifically
                                        if (rec instanceof FilePassRecord) {
                                                fpr = (FilePassRecord) rec;
-                                               
outputRecs.remove(outputRecs.size()-1);
-                                               // TODO - add fpr not added to 
outputRecs
-                                               rec = outputRecs.get(0);
-                                       } else {
-                                               // workbook not encrypted 
(typical case)
-                                               if (rec instanceof EOFRecord) {
-                                                       // A workbook stream is 
never empty, so crash instead
-                                                       // of trying to keep 
track of nesting level
-                                                       throw new 
IllegalStateException("Nothing between BOF and EOF");
-                                               }
+                                       }
+
+                                       // workbook not encrypted (typical case)
+                                       if (rec instanceof EOFRecord) {
+                                               // A workbook stream is never 
empty, so crash instead
+                                               // of trying to keep track of 
nesting level
+                                               throw new 
IllegalStateException("Nothing between BOF and EOF");
                                        }
                                }
                        } else {

Modified: 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/RecordInputStream.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/RecordInputStream.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/RecordInputStream.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/RecordInputStream.java
 Fri Aug 19 20:23:16 2016
@@ -309,13 +309,22 @@ public final class RecordInputStream imp
                }
                return result;
        }
+       
+       public void readPlain(byte[] buf, int off, int len) {
+           readFully(buf, 0, buf.length, true);
+       }
+       
        @Override
     public void readFully(byte[] buf) {
-               readFully(buf, 0, buf.length);
+               readFully(buf, 0, buf.length, false);
        }
 
-       @Override
+    @Override
     public void readFully(byte[] buf, int off, int len) {
+        readFully(buf, off, len, false);
+    }
+       
+    protected void readFully(byte[] buf, int off, int len, boolean isPlain) {
            int origLen = len;
            if (buf == null) {
                throw new NullPointerException();
@@ -335,7 +344,11 @@ public final class RecordInputStream imp
                    }
                }
                checkRecordPosition(nextChunk);
-               _dataInput.readFully(buf, off, nextChunk);
+               if (isPlain) {
+                _dataInput.readPlain(buf, off, nextChunk);
+               } else {
+                _dataInput.readFully(buf, off, nextChunk);
+               }
                _currentDataOffset+=nextChunk;
                off += nextChunk;
                len -= nextChunk;

Modified: 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/cont/ContinuableRecordInput.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/cont/ContinuableRecordInput.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/cont/ContinuableRecordInput.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/cont/ContinuableRecordInput.java
 Fri Aug 19 20:23:16 2016
@@ -54,28 +54,34 @@ public class ContinuableRecordInput impl
     public ContinuableRecordInput(RecordInputStream in){
         _in = in;
     }
+    @Override
     public int available(){
         return _in.available();
     }
 
+    @Override
     public byte readByte(){
         return _in.readByte();
     }
 
+    @Override
     public int readUByte(){
         return _in.readUByte();
     }
 
+    @Override
     public short readShort(){
         return _in.readShort();
     }
 
+    @Override
     public int readUShort(){
         int ch1 = readUByte();
         int ch2 = readUByte();
         return (ch2 << 8) + (ch1 << 0);
     }
 
+    @Override
     public int readInt(){
         int ch1 = _in.readUByte();
         int ch2 = _in.readUByte();
@@ -84,6 +90,7 @@ public class ContinuableRecordInput impl
         return (ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0);
     }
 
+    @Override
     public long readLong(){
         int b0 = _in.readUByte();
         int b1 = _in.readUByte();
@@ -103,14 +110,23 @@ public class ContinuableRecordInput impl
                 (b0 <<  0));
     }
 
+    @Override
     public double readDouble(){
         return _in.readDouble();
     }
+
+    @Override
     public void readFully(byte[] buf){
         _in.readFully(buf);
     }
+
+    @Override
     public void readFully(byte[] buf, int off, int len){
         _in.readFully(buf, off, len);
     }
-
+    
+    @Override
+    public void readPlain(byte[] buf, int off, int len) {
+        readFully(buf, off, len);
+    }
 }

Modified: 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/crypto/Biff8DecryptingStream.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/crypto/Biff8DecryptingStream.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/crypto/Biff8DecryptingStream.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/crypto/Biff8DecryptingStream.java
 Fri Aug 19 20:23:16 2016
@@ -17,7 +17,6 @@
 
 package org.apache.poi.hssf.record.crypto;
 
-import java.io.IOException;
 import java.io.InputStream;
 import java.io.PushbackInputStream;
 
@@ -35,7 +34,7 @@ import org.apache.poi.util.LittleEndianI
 
 public final class Biff8DecryptingStream implements BiffHeaderInput, 
LittleEndianInput {
 
-    private static final int RC4_REKEYING_INTERVAL = 1024;
+    public static final int RC4_REKEYING_INTERVAL = 1024;
 
     private final EncryptionInfo info;
     private ChunkedCipherInputStream ccis;
@@ -180,7 +179,7 @@ public final class Biff8DecryptingStream
      *
      * @return <code>true</code> if record type specified by <tt>sid</tt> is 
never encrypted
      */
-    private static boolean isNeverEncryptedRecord(int sid) {
+    public static boolean isNeverEncryptedRecord(int sid) {
         switch (sid) {
             case BOFRecord.sid:
                 // sheet BOFs for sure
@@ -204,15 +203,9 @@ public final class Biff8DecryptingStream
         }
     }
 
-    private void readPlain(byte b[], int off, int len) {
-        try {
-            int readBytes = ccis.readPlain(b, off, len);
-            if (readBytes < len) {
-                throw new RecordFormatException("buffer underrun");
-            }
-        } catch (IOException e) {
-            throw new RecordFormatException(e);
-        }
+    @Override
+    public void readPlain(byte b[], int off, int len) {
+        ccis.readPlain(b, off, len);
     }
 
 }

Modified: 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
 Fri Aug 19 20:23:16 2016
@@ -23,6 +23,7 @@ import static org.apache.poi.hssf.model.
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -61,8 +62,10 @@ import org.apache.poi.hssf.model.Interna
 import org.apache.poi.hssf.model.RecordStream;
 import org.apache.poi.hssf.record.AbstractEscherHolderRecord;
 import org.apache.poi.hssf.record.BackupRecord;
+import org.apache.poi.hssf.record.BoundSheetRecord;
 import org.apache.poi.hssf.record.DrawingGroupRecord;
 import org.apache.poi.hssf.record.ExtendedFormatRecord;
+import org.apache.poi.hssf.record.FilePassRecord;
 import org.apache.poi.hssf.record.FontRecord;
 import org.apache.poi.hssf.record.LabelRecord;
 import org.apache.poi.hssf.record.LabelSSTRecord;
@@ -74,8 +77,11 @@ import org.apache.poi.hssf.record.SSTRec
 import org.apache.poi.hssf.record.UnknownRecord;
 import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor;
 import org.apache.poi.hssf.record.common.UnicodeString;
+import org.apache.poi.hssf.record.crypto.Biff8DecryptingStream;
 import org.apache.poi.hssf.util.CellReference;
+import org.apache.poi.poifs.crypt.ChunkedCipherOutputStream;
 import org.apache.poi.poifs.crypt.Decryptor;
+import org.apache.poi.poifs.crypt.Encryptor;
 import org.apache.poi.poifs.filesystem.DirectoryEntry;
 import org.apache.poi.poifs.filesystem.DirectoryNode;
 import org.apache.poi.poifs.filesystem.DocumentNode;
@@ -99,8 +105,11 @@ import org.apache.poi.ss.usermodel.Workb
 import org.apache.poi.ss.util.WorkbookUtil;
 import org.apache.poi.util.Configurator;
 import org.apache.poi.util.HexDump;
+import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.Internal;
 import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianByteArrayInputStream;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
 import org.apache.poi.util.POILogFactory;
 import org.apache.poi.util.POILogger;
 
@@ -1443,7 +1452,7 @@ public final class HSSFWorkbook extends
         if (log.check( POILogger.DEBUG )) {
             log.log(DEBUG, "HSSFWorkbook.getBytes()");
         }
-
+        
         HSSFSheet[] sheets = getSheets();
         int nSheets = sheets.length;
 
@@ -1485,9 +1494,70 @@ public final class HSSFWorkbook extends
             }
             pos += serializedSize;
         }
+
+        encryptBytes(retval);
+        
         return retval;
     }
 
+    @SuppressWarnings("resource")
+    protected void encryptBytes(byte buf[]) {
+        int initialOffset = 0;
+        FilePassRecord fpr = null;
+        for (Record r : workbook.getRecords()) {
+            initialOffset += r.getRecordSize();
+            if (r instanceof FilePassRecord) {
+                fpr = (FilePassRecord)r;
+                break;
+            }
+        }
+        if (fpr == null) {
+            return;
+        }
+        
+        LittleEndianByteArrayInputStream plain = new 
LittleEndianByteArrayInputStream(buf, 0);
+        LittleEndianByteArrayOutputStream leos = new 
LittleEndianByteArrayOutputStream(buf, 0);
+        Encryptor enc = fpr.getEncryptionInfo().getEncryptor();
+        enc.setChunkSize(Biff8DecryptingStream.RC4_REKEYING_INTERVAL);
+        byte tmp[] = new byte[1024];
+        try {
+            ChunkedCipherOutputStream os = enc.getDataStream(leos, 
initialOffset);
+            int totalBytes = 0;
+            while (totalBytes < buf.length) {
+                plain.read(tmp, 0, 4);
+                final int sid = LittleEndian.getUShort(tmp, 0);
+                final int len = LittleEndian.getUShort(tmp, 2);
+                boolean isPlain = 
Biff8DecryptingStream.isNeverEncryptedRecord(sid);
+                os.setNextRecordSize(len, isPlain);
+                os.writePlain(tmp, 0, 4);
+                if (sid == BoundSheetRecord.sid) {
+                    // special case for the field_1_position_of_BOF 
(=lbPlyPos) field of
+                    // the BoundSheet8 record which must be unencrypted
+                    byte bsrBuf[] = new byte[len];
+                    plain.readFully(bsrBuf);
+                    os.writePlain(bsrBuf, 0, 4);
+                    os.write(bsrBuf, 4, len-4);
+                } else {
+                    int todo = len;
+                    while (todo > 0) {
+                        int nextLen = Math.min(todo, tmp.length);
+                        plain.readFully(tmp, 0, nextLen);
+                        if (isPlain) {
+                            os.writePlain(tmp, 0, nextLen);
+                        } else {
+                            os.write(tmp, 0, nextLen);
+                        }
+                        todo -= nextLen;
+                    }
+                }
+                totalBytes += 4 + len;
+            }
+            os.close();
+        } catch (Exception e) {
+            throw new EncryptedDocumentException(e);
+        }
+    }
+    
     /*package*/ InternalWorkbook getWorkbook() {
         return workbook;
     }

Modified: 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java
 Fri Aug 19 20:23:16 2016
@@ -222,24 +222,33 @@ public abstract class ChunkedCipherInput
     /**
      * Used when BIFF header fields (sid, size) are being read. The internal
      * {@link Cipher} instance must step even when unencrypted bytes are read
+     * 
      */
-    public int readPlain(byte b[], int off, int len) throws IOException {
+    @Override
+    public void readPlain(byte b[], int off, int len) {
         if (len <= 0) {
-            return len;
+            return;
         }
 
-        int readBytes, total = 0;
-        do {
-            readBytes = read(b, off, len, true);
-            total += Math.max(0, readBytes);
-        } while (readBytes > -1 && total < len);
-
-        return total;
+        try {
+            int readBytes, total = 0;
+            do {
+                readBytes = read(b, off, len, true);
+                total += Math.max(0, readBytes);
+            } while (readBytes > -1 && total < len);
+    
+            if (total < len) {
+                throw new EOFException("buffer underrun");
+            }
+        } catch (IOException e) {
+            // need to wrap checked exception, because of LittleEndianInput 
interface :(
+            throw new RuntimeException(e);
+        }
     }
 
     /**
      * Some ciphers (actually just XOR) are based on the record size,
-     * which needs to be set before encryption
+     * which needs to be set before decryption
      *
      * @param recordSize the size of the next record
      */

Modified: 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java
 Fri Aug 19 20:23:16 2016
@@ -25,6 +25,7 @@ import java.io.FilterOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.security.GeneralSecurityException;
+import java.util.BitSet;
 
 import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
@@ -48,14 +49,17 @@ public abstract class ChunkedCipherOutpu
     private static final POILogger LOG = 
POILogFactory.getLogger(ChunkedCipherOutputStream.class);
     private static final int STREAMING = -1;
 
-    protected final int _chunkSize;
-    protected final int _chunkBits;
+    private final int _chunkSize;
+    private final int _chunkBits;
 
     private final byte[] _chunk;
+    private final BitSet _plainByteFlags;
     private final File _fileOut;
     private final DirectoryNode _dir;
 
     private long _pos = 0;
+    private long _totalPos = 0;
+    private long _written = 0;
     private Cipher _cipher;
 
     public ChunkedCipherOutputStream(DirectoryNode dir, int chunkSize) throws 
IOException, GeneralSecurityException {
@@ -63,6 +67,7 @@ public abstract class ChunkedCipherOutpu
         this._chunkSize = chunkSize;
         int cs = chunkSize == STREAMING ? 4096 : chunkSize;
         _chunk = new byte[cs];
+        _plainByteFlags = new BitSet(cs);
         _chunkBits = Integer.bitCount(cs-1);
         _fileOut = TempFile.createTempFile("encrypted_package", "crypt");
         _fileOut.deleteOnExit();
@@ -76,6 +81,7 @@ public abstract class ChunkedCipherOutpu
         this._chunkSize = chunkSize;
         int cs = chunkSize == STREAMING ? 4096 : chunkSize;
         _chunk = new byte[cs];
+        _plainByteFlags = new BitSet(cs);
         _chunkBits = Integer.bitCount(cs-1);
         _fileOut = null;
         _dir = null;
@@ -106,8 +112,15 @@ public abstract class ChunkedCipherOutpu
     }
 
     @Override
-    public void write(byte[] b, int off, int len)
-    throws IOException {
+    public void write(byte[] b, int off, int len) throws IOException {
+        write(b, off, len, false);
+    }
+
+    public void writePlain(byte[] b, int off, int len) throws IOException {
+        write(b, off, len, true);
+    }
+    
+    protected void write(byte[] b, int off, int len, boolean writePlain) 
throws IOException {
         if (len == 0) {
             return;
         }
@@ -121,7 +134,11 @@ public abstract class ChunkedCipherOutpu
             int posInChunk = (int)(_pos & chunkMask);
             int nextLen = Math.min(_chunk.length-posInChunk, len);
             System.arraycopy(b, off, _chunk, posInChunk, nextLen);
+            if (writePlain) {
+                _plainByteFlags.set(posInChunk, posInChunk+nextLen);
+            }
             _pos += nextLen;
+            _totalPos += nextLen;
             off += nextLen;
             len -= nextLen;
             if ((_pos & chunkMask) == 0) {
@@ -130,12 +147,12 @@ public abstract class ChunkedCipherOutpu
         }
     }
 
-    private int getChunkMask() {
+    protected int getChunkMask() {
         return _chunk.length-1;
     }
 
     protected void writeChunk(boolean continued) throws IOException {
-        if (_pos == 0) {
+        if (_pos == 0 || _totalPos == _written) {
             return;
         }
 
@@ -157,14 +174,18 @@ public abstract class ChunkedCipherOutpu
         int ciLen;
         try {
             boolean doFinal = true;
+            long oldPos = _pos;
+            // reset stream (not only) in case we were interrupted by plain 
stream parts
+            // this also needs to be set to prevent an endless loop
+            _pos = 0;
             if (_chunkSize == STREAMING) {
                 if (continued) {
                     doFinal = false;
                 }
-                // reset stream (not only) in case we were interrupted by 
plain stream parts
-                _pos = 0;
             } else {
                 _cipher = initCipherForBlock(_cipher, index, lastChunk);
+                // restore pos - only streaming chunks will be reset
+                _pos = oldPos;
             }
             ciLen = invokeCipher(posInChunk, doFinal);
         } catch (GeneralSecurityException e) {
@@ -172,6 +193,8 @@ public abstract class ChunkedCipherOutpu
         }
 
         out.write(_chunk, 0, ciLen);
+        _plainByteFlags.clear();
+        _written += ciLen;
     }
 
     /**
@@ -184,11 +207,17 @@ public abstract class ChunkedCipherOutpu
      * @throws ShortBufferException 
      */
     protected int invokeCipher(int posInChunk, boolean doFinal) throws 
GeneralSecurityException {
-        if (doFinal) {
-            return _cipher.doFinal(_chunk, 0, posInChunk, _chunk);
-        } else {
-            return _cipher.update(_chunk, 0, posInChunk, _chunk);
+        byte plain[] = (_plainByteFlags.isEmpty()) ? null : _chunk.clone();
+
+        int ciLen = (doFinal)
+            ? _cipher.doFinal(_chunk, 0, posInChunk, _chunk)
+            : _cipher.update(_chunk, 0, posInChunk, _chunk);
+        
+        for (int i = _plainByteFlags.nextSetBit(0); i >= 0 && i < posInChunk; 
i = _plainByteFlags.nextSetBit(i+1)) {
+            _chunk[i] = plain[i];
         }
+        
+        return ciLen;
     }
     
     @Override
@@ -208,7 +237,33 @@ public abstract class ChunkedCipherOutpu
             throw new IOException(e);
         }
     }
+    
+    protected byte[] getChunk() {
+        return _chunk;
+    }
+
+    protected BitSet getPlainByteFlags() {
+        return _plainByteFlags;
+    }
+
+    protected long getPos() {
+        return _pos;
+    }
+
+    protected long getTotalPos() {
+        return _totalPos;
+    }
 
+    /**
+     * Some ciphers (actually just XOR) are based on the record size,
+     * which needs to be set before encryption
+     *
+     * @param recordSize the size of the next record
+     * @param isPlain {@code true} if the record is unencrypted
+     */
+    public void setNextRecordSize(int recordSize, boolean isPlain) {
+    }
+    
     private class EncryptedPackageWriter implements POIFSWriterListener {
         @Override
         public void processPOIFSWriterEvent(POIFSWriterEvent event) {

Modified: 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java
 Fri Aug 19 20:23:16 2016
@@ -498,7 +498,9 @@ public class CryptoFunctions {
      * @return the byte array for xor obfuscation
      */
     public static byte[] createXorArray1(String password) {
-        if (password.length() > 15) password = password.substring(0, 15);
+        if (password.length() > 15) {
+            password = password.substring(0, 15);
+        }
         byte passBytes[] = password.getBytes(Charset.forName("ASCII"));
         
         // this code is based on the libre office implementation.

Modified: 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/Encryptor.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/Encryptor.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/Encryptor.java 
(original)
+++ 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/Encryptor.java 
Fri Aug 19 20:23:16 2016
@@ -61,11 +61,16 @@ public abstract class Encryptor implemen
         return getDataStream(fs.getRoot());
     }
 
+    public ChunkedCipherOutputStream getDataStream(OutputStream stream, int 
initialOffset)
+    throws IOException, GeneralSecurityException {
+        throw new RuntimeException("this decryptor doesn't support writing 
directly to a stream");
+    }
+    
     public SecretKey getSecretKey() {
         return secretKey;
     }
 
-    protected void setSecretKey(SecretKey secretKey) {
+    public void setSecretKey(SecretKey secretKey) {
         this.secretKey = secretKey;
     }
 
@@ -77,6 +82,17 @@ public abstract class Encryptor implemen
         this.encryptionInfo = encryptionInfo;
     }
 
+    /**
+     * Sets the chunk size of the data stream.
+     * Needs to be set before the data stream is requested.
+     * When not set, the implementation uses method specific default values
+     *
+     * @param chunkSize the chunk size, i.e. the block size with the same 
encryption key
+     */
+    public void setChunkSize(int chunkSize) {
+        throw new RuntimeException("this decryptor doesn't support changing 
the chunk size");
+    }
+    
     @Override
     public Encryptor clone() throws CloneNotSupportedException {
         Encryptor other = (Encryptor)super.clone();

Modified: 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Encryptor.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Encryptor.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Encryptor.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Encryptor.java
 Fri Aug 19 20:23:16 2016
@@ -41,6 +41,8 @@ import org.apache.poi.util.LittleEndianB
 
 public class BinaryRC4Encryptor extends Encryptor implements Cloneable {
 
+    private int _chunkSize = 512;
+    
     protected BinaryRC4Encryptor() {
     }
 
@@ -84,6 +86,12 @@ public class BinaryRC4Encryptor extends
         return countStream;
     }
 
+    @Override
+    public BinaryRC4CipherOutputStream getDataStream(OutputStream stream, int 
initialOffset)
+    throws IOException, GeneralSecurityException {
+        return new BinaryRC4CipherOutputStream(stream);
+    }
+    
     protected int getKeySizeInBytes() {
         return getEncryptionInfo().getHeader().getKeySize() / 8;
     }
@@ -106,18 +114,33 @@ public class BinaryRC4Encryptor extends
     }
 
     @Override
+    public void setChunkSize(int chunkSize) {
+        _chunkSize = chunkSize;
+    }
+    
+    @Override
     public BinaryRC4Encryptor clone() throws CloneNotSupportedException {
         return (BinaryRC4Encryptor)super.clone();
     }
 
     protected class BinaryRC4CipherOutputStream extends 
ChunkedCipherOutputStream {
 
+        public BinaryRC4CipherOutputStream(OutputStream stream)
+        throws IOException, GeneralSecurityException {
+            super(stream, BinaryRC4Encryptor.this._chunkSize);
+        }
+
+        public BinaryRC4CipherOutputStream(DirectoryNode dir)
+        throws IOException, GeneralSecurityException {
+            super(dir, BinaryRC4Encryptor.this._chunkSize);
+        }
+
         @Override
         protected Cipher initCipherForBlock(Cipher cipher, int block, boolean 
lastChunk)
         throws GeneralSecurityException {
             return BinaryRC4Decryptor.initCipherForBlock(cipher, block, 
getEncryptionInfo(), getSecretKey(), Cipher.ENCRYPT_MODE);
         }
-
+        
         @Override
         protected void calculateChecksum(File file, int i) {
         }
@@ -128,9 +151,10 @@ public class BinaryRC4Encryptor extends
             BinaryRC4Encryptor.this.createEncryptionInfoEntry(dir);
         }
 
-        public BinaryRC4CipherOutputStream(DirectoryNode dir)
-        throws IOException, GeneralSecurityException {
-            super(dir, 512);
+        @Override
+        public void flush() throws IOException {
+            writeChunk(false);
+            super.flush();
         }
     }
 }

Modified: 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java
 Fri Aug 19 20:23:16 2016
@@ -111,7 +111,8 @@ public class CryptoAPIEncryptor extends
         throw new IOException("not supported");
     }
     
-    public CryptoAPICipherOutputStream getDataStream(OutputStream stream)
+    @Override
+    public CryptoAPICipherOutputStream getDataStream(OutputStream stream, int 
initialOffset)
     throws IOException, GeneralSecurityException {
         return new CryptoAPICipherOutputStream(stream);
     }
@@ -212,6 +213,7 @@ public class CryptoAPIEncryptor extends
         return getEncryptionInfo().getHeader().getKeySize() / 8;
     }
 
+    @Override
     public void setChunkSize(int chunkSize) {
         _chunkSize = chunkSize;
     }
@@ -268,6 +270,7 @@ public class CryptoAPIEncryptor extends
         @Override
         public void flush() throws IOException {
             writeChunk(false);
+            super.flush();
         }
     }
 

Modified: 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XORDecryptor.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XORDecryptor.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XORDecryptor.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XORDecryptor.java
 Fri Aug 19 20:23:16 2016
@@ -36,17 +36,73 @@ public class XORDecryptor extends Decryp
     private long _length = -1L;
     private int _chunkSize = 512;
 
+    protected XORDecryptor() {
+    }
+
+    @Override
+    public boolean verifyPassword(String password) {
+        XOREncryptionVerifier ver = 
(XOREncryptionVerifier)getEncryptionInfo().getVerifier();
+        int keyVer = LittleEndian.getUShort(ver.getEncryptedKey());
+        int verifierVer = LittleEndian.getUShort(ver.getEncryptedVerifier());
+        int keyComp      = CryptoFunctions.createXorKey1(password);
+        int verifierComp = CryptoFunctions.createXorVerifier1(password);
+        if (keyVer == keyComp && verifierVer == verifierComp) {
+            byte xorArray[] = CryptoFunctions.createXorArray1(password);
+            setSecretKey(new SecretKeySpec(xorArray, "XOR"));
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public Cipher initCipherForBlock(Cipher cipher, int block)
+    throws GeneralSecurityException {
+        return null;
+    }
+
+    protected static Cipher initCipherForBlock(Cipher cipher, int block,
+        EncryptionInfo encryptionInfo, SecretKey skey, int encryptMode)
+    throws GeneralSecurityException {
+        return null;
+    }
+
+    @Override
+    public ChunkedCipherInputStream getDataStream(DirectoryNode dir) throws 
IOException, GeneralSecurityException {
+        throw new RuntimeException("not supported");
+    }
+
+    @Override
+    public InputStream getDataStream(InputStream stream, int size, int 
initialPos)
+            throws IOException, GeneralSecurityException {
+        return new XORCipherInputStream(stream, initialPos);
+    }
+
+
+    @Override
+    public long getLength() {
+        if (_length == -1L) {
+            throw new IllegalStateException("Decryptor.getDataStream() was not 
called");
+        }
+
+        return _length;
+    }
+
+    @Override
+    public void setChunkSize(int chunkSize) {
+        _chunkSize = chunkSize;
+    }
+    
+    @Override
+    public XORDecryptor clone() throws CloneNotSupportedException {
+        return (XORDecryptor)super.clone();
+    }
+
     private class XORCipherInputStream extends ChunkedCipherInputStream {
         private final int _initialOffset;
         private int _recordStart = 0;
         private int _recordEnd = 0;
         
-        @Override
-        protected Cipher initCipherForBlock(Cipher existing, int block)
-                throws GeneralSecurityException {
-            return XORDecryptor.this.initCipherForBlock(existing, block);
-        }
-
         public XORCipherInputStream(InputStream stream, int initialPos)
                 throws GeneralSecurityException {
             super(stream, Integer.MAX_VALUE, _chunkSize);
@@ -54,6 +110,12 @@ public class XORDecryptor extends Decryp
         }
         
         @Override
+        protected Cipher initCipherForBlock(Cipher existing, int block)
+                throws GeneralSecurityException {
+            return XORDecryptor.this.initCipherForBlock(existing, block);
+        }
+
+        @Override
         protected int invokeCipher(int totalBytes, boolean doFinal) {
             final int pos = (int)getPos();
             final byte xorArray[] = 
getEncryptionInfo().getDecryptor().getSecretKey().getEncoded();
@@ -100,75 +162,13 @@ public class XORDecryptor extends Decryp
          */
         @Override
         public void setNextRecordSize(int recordSize) {
-            _recordStart = (int)getPos();
+            final int pos = (int)getPos();
+            final byte chunk[] = getChunk();
+            final int chunkMask = getChunkMask();
+            _recordStart = pos;
             _recordEnd = _recordStart+recordSize;
-            int pos = (int)getPos();
-            byte chunk[] = getChunk();
-            int chunkMask = getChunkMask();
             int nextBytes = Math.min(recordSize, chunk.length-(pos & 
chunkMask));
             invokeCipher(nextBytes, true);
         }
     }
-
-    protected XORDecryptor() {
-    }
-
-    @Override
-    public boolean verifyPassword(String password) {
-        XOREncryptionVerifier ver = 
(XOREncryptionVerifier)getEncryptionInfo().getVerifier();
-        int keyVer = LittleEndian.getUShort(ver.getEncryptedKey());
-        int verifierVer = LittleEndian.getUShort(ver.getEncryptedVerifier());
-        int keyComp      = CryptoFunctions.createXorKey1(password);
-        int verifierComp = CryptoFunctions.createXorVerifier1(password);
-        if (keyVer == keyComp && verifierVer == verifierComp) {
-            byte xorArray[] = CryptoFunctions.createXorArray1(password);
-            setSecretKey(new SecretKeySpec(xorArray, "XOR"));
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    @Override
-    public Cipher initCipherForBlock(Cipher cipher, int block)
-    throws GeneralSecurityException {
-        return null;
-    }
-
-    protected static Cipher initCipherForBlock(Cipher cipher, int block,
-        EncryptionInfo encryptionInfo, SecretKey skey, int encryptMode)
-    throws GeneralSecurityException {
-        return null;
-    }
-
-    @Override
-    public ChunkedCipherInputStream getDataStream(DirectoryNode dir) throws 
IOException, GeneralSecurityException {
-        throw new RuntimeException("not supported");
-    }
-
-    @Override
-    public InputStream getDataStream(InputStream stream, int size, int 
initialPos)
-            throws IOException, GeneralSecurityException {
-        return new XORCipherInputStream(stream, initialPos);
-    }
-
-
-    @Override
-    public long getLength() {
-        if (_length == -1L) {
-            throw new IllegalStateException("Decryptor.getDataStream() was not 
called");
-        }
-
-        return _length;
-    }
-
-    @Override
-    public void setChunkSize(int chunkSize) {
-        _chunkSize = chunkSize;
-    }
-
-    @Override
-    public XORDecryptor clone() throws CloneNotSupportedException {
-        return (XORDecryptor)super.clone();
-    }
 }

Modified: 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionHeader.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionHeader.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionHeader.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionHeader.java
 Fri Aug 19 20:23:16 2016
@@ -27,7 +27,7 @@ public class XOREncryptionHeader extends
     }
 
     @Override
-    public void write(LittleEndianByteArrayOutputStream 
littleendianbytearrayoutputstream) {
+    public void write(LittleEndianByteArrayOutputStream leos) {
     }
 
     @Override

Modified: 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionVerifier.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionVerifier.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionVerifier.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionVerifier.java
 Fri Aug 19 20:23:16 2016
@@ -58,4 +58,14 @@ public class XOREncryptionVerifier exten
     public XOREncryptionVerifier clone() throws CloneNotSupportedException {
         return (XOREncryptionVerifier)super.clone();
     }
+
+    @Override
+    protected void setEncryptedVerifier(byte[] encryptedVerifier) {
+        super.setEncryptedVerifier(encryptedVerifier);
+    }
+
+    @Override
+    protected void setEncryptedKey(byte[] encryptedKey) {
+        super.setEncryptedKey(encryptedKey);
+    }
 }

Modified: 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptor.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptor.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptor.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptor.java
 Fri Aug 19 20:23:16 2016
@@ -21,37 +21,41 @@ import java.io.File;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.security.GeneralSecurityException;
-import java.security.MessageDigest;
-import java.security.SecureRandom;
-import java.util.Random;
+import java.util.BitSet;
 
 import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
 
-import org.apache.poi.EncryptedDocumentException;
 import org.apache.poi.poifs.crypt.ChunkedCipherOutputStream;
 import org.apache.poi.poifs.crypt.CryptoFunctions;
-import org.apache.poi.poifs.crypt.DataSpaceMapUtils;
-import org.apache.poi.poifs.crypt.EncryptionInfo;
 import org.apache.poi.poifs.crypt.Encryptor;
-import org.apache.poi.poifs.crypt.HashAlgorithm;
-import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
 import org.apache.poi.poifs.filesystem.DirectoryNode;
-import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+import org.apache.poi.util.LittleEndian;
 
 public class XOREncryptor extends Encryptor implements Cloneable {
-
     protected XOREncryptor() {
     }
 
     @Override
     public void confirmPassword(String password) {
+        int keyComp      = CryptoFunctions.createXorKey1(password);
+        int verifierComp = CryptoFunctions.createXorVerifier1(password);
+        byte xorArray[]  = CryptoFunctions.createXorArray1(password);
+        
+        byte shortBuf[] = new byte[2];
+        XOREncryptionVerifier ver = 
(XOREncryptionVerifier)getEncryptionInfo().getVerifier();
+        LittleEndian.putUShort(shortBuf, 0, keyComp);
+        ver.setEncryptedKey(shortBuf);
+        LittleEndian.putUShort(shortBuf, 0, verifierComp);
+        ver.setEncryptedVerifier(shortBuf);
+        setSecretKey(new SecretKeySpec(xorArray, "XOR"));
     }
 
     @Override
     public void confirmPassword(String password, byte keySpec[],
             byte keySalt[], byte verifier[], byte verifierSalt[],
             byte integritySalt[]) {
+        confirmPassword(password);
     }
 
     @Override
@@ -61,10 +65,21 @@ public class XOREncryptor extends Encryp
         return countStream;
     }
 
+    @Override
+    public XORCipherOutputStream getDataStream(OutputStream stream, int 
initialOffset)
+    throws IOException, GeneralSecurityException {
+        return new XORCipherOutputStream(stream, initialOffset);
+    }
+
     protected int getKeySizeInBytes() {
         return -1;
     }
 
+    @Override
+    public void setChunkSize(int chunkSize) {
+        // chunkSize is irrelevant
+    }
+
     protected void createEncryptionInfoEntry(DirectoryNode dir) throws 
IOException {
     }
 
@@ -73,7 +88,21 @@ public class XOREncryptor extends Encryp
         return (XOREncryptor)super.clone();
     }
 
-    protected class XORCipherOutputStream extends ChunkedCipherOutputStream {
+    private class XORCipherOutputStream extends ChunkedCipherOutputStream {
+        private final int _initialOffset;
+        private int _recordStart = 0;
+        private int _recordEnd = 0;
+        private boolean _isPlain = false;
+
+        public XORCipherOutputStream(OutputStream stream, int initialPos) 
throws IOException, GeneralSecurityException {
+            super(stream, -1);
+            _initialOffset = initialPos;
+        }
+
+        public XORCipherOutputStream(DirectoryNode dir) throws IOException, 
GeneralSecurityException {
+            super(dir, -1);
+            _initialOffset = 0;
+        }
 
         @Override
         protected Cipher initCipherForBlock(Cipher cipher, int block, boolean 
lastChunk)
@@ -91,9 +120,67 @@ public class XOREncryptor extends Encryp
             XOREncryptor.this.createEncryptionInfoEntry(dir);
         }
 
-        public XORCipherOutputStream(DirectoryNode dir)
-        throws IOException, GeneralSecurityException {
-            super(dir, 512);
+        @Override
+        public void setNextRecordSize(int recordSize, boolean isPlain) {
+            if (_recordEnd > 0 && !_isPlain) {
+                // encrypt last record
+                invokeCipher((int)getPos(), true);
+            }
+            _recordStart = (int)getTotalPos()+4;
+            _recordEnd = _recordStart+recordSize;
+            _isPlain = isPlain;
+        }
+
+        @Override
+        public void flush() throws IOException {
+            setNextRecordSize(0, true);
+            super.flush();
+        }
+
+        @Override
+        protected int invokeCipher(int posInChunk, boolean doFinal) {
+            if (posInChunk == 0) {
+                return 0;
+            }
+
+            final int start = Math.max(posInChunk-(_recordEnd-_recordStart), 
0);
+
+            final BitSet plainBytes = getPlainByteFlags();
+            final byte xorArray[] = 
getEncryptionInfo().getEncryptor().getSecretKey().getEncoded();
+            final byte chunk[] = getChunk();
+            final byte plain[] = (plainBytes.isEmpty()) ? null : chunk.clone();
+
+            /*
+             * From: 
http://social.msdn.microsoft.com/Forums/en-US/3dadbed3-0e68-4f11-8b43-3a2328d9ebd5
+             *
+             * The initial value for XorArrayIndex is as follows:
+             * XorArrayIndex = (FileOffset + Data.Length) % 16
+             *
+             * The FileOffset variable in this context is the stream offset 
into the Workbook stream at
+             * the time we are about to write each of the bytes of the record 
data.
+             * This (the value) is then incremented after each byte is written.
+             */
+            // ... also need to handle invocation in case of a filled chunk
+            int xorArrayIndex = _recordEnd+(start-_recordStart);
+
+            for (int i=start; i < posInChunk; i++) {
+                byte value = chunk[i];
+                value ^= xorArray[(xorArrayIndex++) & 0x0F];
+                value = rotateLeft(value, 8-3);
+                chunk[i] = value;
+            }
+
+            for (int i = plainBytes.nextSetBit(start); i >= 0 && i < 
posInChunk; i = plainBytes.nextSetBit(i+1)) {
+                chunk[i] = plain[i];
+            }
+
+            return posInChunk;
+        }
+
+        private byte rotateLeft(byte bits, int shift) {
+            return (byte)(((bits & 0xff) << shift) | ((bits & 0xff) >>> (8 - 
shift)));
         }
+
+
     }
 }

Modified: 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/filesystem/DocumentInputStream.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/filesystem/DocumentInputStream.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/filesystem/DocumentInputStream.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/filesystem/DocumentInputStream.java
 Fri Aug 19 20:23:16 2016
@@ -189,4 +189,9 @@ public class DocumentInputStream extends
         int i = readInt();
         return i & 0xFFFFFFFFL;
     }
+
+    @Override
+    public void readPlain(byte[] buf, int off, int len) {
+        readFully(buf, off, len);
+    }
 }

Modified: 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/util/LittleEndianByteArrayInputStream.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/util/LittleEndianByteArrayInputStream.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/util/LittleEndianByteArrayInputStream.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/util/LittleEndianByteArrayInputStream.java
 Fri Aug 19 20:23:16 2016
@@ -104,4 +104,9 @@ public final class LittleEndianByteArray
         checkPosition(buffer.length);
         read(buffer, 0, buffer.length);
        }
+
+    @Override
+    public void readPlain(byte[] buf, int off, int len) {
+        readFully(buf, off, len);
+    }
 }

Modified: 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/util/LittleEndianInput.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/util/LittleEndianInput.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/util/LittleEndianInput.java 
(original)
+++ 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/util/LittleEndianInput.java 
Fri Aug 19 20:23:16 2016
@@ -16,10 +16,7 @@
 ==================================================================== */
 
 package org.apache.poi.util;
-/**
- *
- * @author Josh Micich
- */
+
 public interface LittleEndianInput {
        int available();
        byte readByte();
@@ -31,4 +28,14 @@ public interface LittleEndianInput {
        double readDouble();
        void readFully(byte[] buf);
        void readFully(byte[] buf, int off, int len);
+
+       /**
+        * Usually acts the same as {@link #readFully(byte[], int, int)}, but
+        * for an encrypted stream the raw (unencrypted) data is filled 
+        *
+        * @param buf the byte array to receive the bytes
+        * @param off the start offset into the byte array 
+        * @param len the amount of bytes to fill
+        */
+       void readPlain(byte[] buf, int off, int len);
 }

Modified: 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/util/LittleEndianInputStream.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/util/LittleEndianInputStream.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/util/LittleEndianInputStream.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/java/org/apache/poi/util/LittleEndianInputStream.java
 Fri Aug 19 20:23:16 2016
@@ -34,7 +34,8 @@ public class LittleEndianInputStream ext
                super(is);
        }
        
-       public int available() {
+       @Override
+    public int available() {
                try {
                        return super.available();
                } catch (IOException e) {
@@ -42,11 +43,13 @@ public class LittleEndianInputStream ext
                }
        }
        
-       public byte readByte() {
+       @Override
+    public byte readByte() {
                return (byte)readUByte();
        }
        
-       public int readUByte() {
+       @Override
+    public int readUByte() {
                byte buf[] = new byte[1];
                try {
                        checkEOF(read(buf), 1);
@@ -56,11 +59,13 @@ public class LittleEndianInputStream ext
                return LittleEndian.getUByte(buf);
        }
        
-       public double readDouble() {
+       @Override
+    public double readDouble() {
                return Double.longBitsToDouble(readLong());
        }
        
-       public int readInt() {
+       @Override
+    public int readInt() {
            byte buf[] = new byte[LittleEndianConsts.INT_SIZE];
                try {
                    checkEOF(read(buf), buf.length);
@@ -82,7 +87,8 @@ public class LittleEndianInputStream ext
        return retNum & 0x00FFFFFFFFL;
     }
        
-       public long readLong() {
+       @Override
+    public long readLong() {
                byte buf[] = new byte[LittleEndianConsts.LONG_SIZE];
                try {
                    checkEOF(read(buf), LittleEndianConsts.LONG_SIZE);
@@ -92,11 +98,13 @@ public class LittleEndianInputStream ext
                return LittleEndian.getLong(buf);
        }
        
-       public short readShort() {
+       @Override
+    public short readShort() {
                return (short)readUShort();
        }
        
-       public int readUShort() {
+       @Override
+    public int readUShort() {
                byte buf[] = new byte[LittleEndianConsts.SHORT_SIZE];
                try {
                    checkEOF(read(buf), LittleEndianConsts.SHORT_SIZE);
@@ -112,15 +120,22 @@ public class LittleEndianInputStream ext
                }
        }
 
-       public void readFully(byte[] buf) {
+       @Override
+    public void readFully(byte[] buf) {
                readFully(buf, 0, buf.length);
        }
 
-       public void readFully(byte[] buf, int off, int len) {
+       @Override
+    public void readFully(byte[] buf, int off, int len) {
            try {
                checkEOF(read(buf, off, len), len);
            } catch (IOException e) {
             throw new RuntimeException(e);
         }
        }
+
+    @Override
+    public void readPlain(byte[] buf, int off, int len) {
+        readFully(buf, off, len);
+    }
 }

Modified: 
poi/branches/hssf_cryptoapi/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowEncrypted.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowEncrypted.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowEncrypted.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowEncrypted.java
 Fri Aug 19 20:23:16 2016
@@ -169,7 +169,7 @@ public class HSLFSlideShowEncrypted impl
 
             if (cyos == null) {
                 enc.setChunkSize(-1);
-                cyos = enc.getDataStream(plainStream);
+                cyos = enc.getDataStream(plainStream, 0);
             }
             cyos.initCipherForBlock(persistId, false);
         } catch (Exception e) {
@@ -314,7 +314,7 @@ public class HSLFSlideShowEncrypted impl
 
         try {
             enc.setChunkSize(-1);
-            ccos = enc.getDataStream(los);
+            ccos = enc.getDataStream(los, 0);
             int recInst = 
fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset));
             int recType = LittleEndian.getUShort(pictstream, offset+2);
             final int rlen = (int)LittleEndian.getUInt(pictstream, offset+4);

Modified: 
poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/record/TestRecordFactoryInputStream.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/record/TestRecordFactoryInputStream.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/record/TestRecordFactoryInputStream.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/record/TestRecordFactoryInputStream.java
 Fri Aug 19 20:23:16 2016
@@ -151,11 +151,12 @@ public final class TestRecordFactoryInpu
        
        
        /**
-        * makes sure the record stream starts with {@link BOFRecord} and then 
{@link WindowOneRecord}
-        * The second record is gets decrypted so this method also checks its 
content.
+        * makes sure the record stream starts with {@link BOFRecord}, {@link 
FilePassRecord} and then {@link WindowOneRecord}
+        * The third record is decrypted so this method also checks its content.
         */
        private void confirmReadInitialRecords(RecordFactoryInputStream rfis) {
                assertEquals(BOFRecord.class, rfis.nextRecord().getClass());
+               FilePassRecord recFP = (FilePassRecord) rfis.nextRecord();
                WindowOneRecord rec1 = (WindowOneRecord) rfis.nextRecord();
                
assertArrayEquals(HexRead.readFromString(SAMPLE_WINDOW1),rec1.serialize());
        }

Modified: 
poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/usermodel/TestCryptoAPI.java
URL: 
http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/usermodel/TestCryptoAPI.java?rev=1756964&r1=1756963&r2=1756964&view=diff
==============================================================================
--- 
poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/usermodel/TestCryptoAPI.java
 (original)
+++ 
poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/usermodel/TestCryptoAPI.java
 Fri Aug 19 20:23:16 2016
@@ -17,8 +17,7 @@
 
 package org.apache.poi.hssf.usermodel;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.apache.poi.POITestCase.assertContains;
 
 import java.io.IOException;
 
@@ -38,25 +37,34 @@ public class TestCryptoAPI {
 
     @Test
     public void bug59857() throws IOException {
-        Biff8EncryptionKey.setCurrentUserPassword("abc");
-        HSSFWorkbook wb1 = 
ssTests.openSampleWorkbook("xor-encryption-abc.xls");
-        String textExpected = "Sheet1\n1\n2\n3\n";
-        String textActual = new ExcelExtractor(wb1).getText();
-        assertEquals(textExpected, textActual);
-        wb1.close();
-
-        Biff8EncryptionKey.setCurrentUserPassword("password");
-        HSSFWorkbook wb2 = ssTests.openSampleWorkbook("password.xls");
-        textExpected = "A ZIP bomb is a variant of mail-bombing. After most 
commercial mail servers began checking mail with anti-virus software and 
filtering certain malicious file types, trojan horse viruses tried to send 
themselves compressed into archives, such as ZIP, RAR or 7-Zip. Mail server 
software was then configured to unpack archives and check their contents as 
well. That gave black hats the idea to compose a \"bomb\" consisting of an 
enormous text file, containing, for example, only the letter z repeated 
millions of times. Such a file compresses into a relatively small archive, but 
its unpacking (especially by early versions of mail servers) would use a high 
amount of processing power, RAM and swap space, which could result in denial of 
service. Modern mail server computers usually have sufficient intelligence to 
recognize such attacks as well as sufficient processing power and memory space 
to process malicious attachments without interruption of service, though some ar
 e still susceptible to this technique if the ZIP bomb is mass-mailed.";
-        textActual = new ExcelExtractor(wb2).getText();
-        assertTrue(textActual.contains(textExpected));
-        wb2.close();
-
-        Biff8EncryptionKey.setCurrentUserPassword("freedom");
-        HSSFWorkbook wb3 = ssTests.openSampleWorkbook("35897-type4.xls");
-        textExpected = "Sheet1\nhello there!\n";
-        textActual = new ExcelExtractor(wb3).getText();
-        assertEquals(textExpected, textActual);
-        wb3.close();
+        // XOR-Obfuscation
+        // TODO: XOR-Obfuscation is currently flawed - although the 
de-/obfuscation initially works,
+        // it suddenly differs from the result of encrypted files via Office 
...
+        // and only very small files can be opened without file validation 
errors
+        validateContent("xor-encryption-abc.xls", "abc", "Sheet1\n1\n2\n3\n");
+
+        // BinaryRC4
+        validateContent("password.xls", "password", "A ZIP bomb is a variant 
of mail-bombing. After most commercial mail servers began checking mail with 
anti-virus software and filtering certain malicious file types, trojan horse 
viruses tried to send themselves compressed into archives, such as ZIP, RAR or 
7-Zip. Mail server software was then configured to unpack archives and check 
their contents as well. That gave black hats the idea to compose a \"bomb\" 
consisting of an enormous text file, containing, for example, only the letter z 
repeated millions of times. Such a file compresses into a relatively small 
archive, but its unpacking (especially by early versions of mail servers) would 
use a high amount of processing power, RAM and swap space, which could result 
in denial of service. Modern mail server computers usually have sufficient 
intelligence to recognize such attacks as well as sufficient processing power 
and memory space to process malicious attachments without interrupti
 on of service, though some are still susceptible to this technique if the ZIP 
bomb is mass-mailed.");
+
+        // CryptoAPI
+        validateContent("35897-type4.xls", "freedom", "Sheet1\nhello 
there!\n");
+    }
+    
+    private void validateContent(String wbFile, String password, String 
textExpected) throws IOException {
+        Biff8EncryptionKey.setCurrentUserPassword(password);
+        HSSFWorkbook wb = ssTests.openSampleWorkbook(wbFile);
+        ExcelExtractor ee1 = new ExcelExtractor(wb);
+        String textActual = ee1.getText();
+        assertContains(textActual, textExpected);
+
+        Biff8EncryptionKey.setCurrentUserPassword("bla");
+        HSSFWorkbook wbBla = ssTests.writeOutAndReadBack(wb);
+        ExcelExtractor ee2 = new ExcelExtractor(wbBla);
+        textActual = ee2.getText();
+        assertContains(textActual, textExpected);
+        ee2.close();
+        ee1.close();
+        wbBla.close();
+        wb.close();
     }
 }



---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to