You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@poi.apache.org by ki...@apache.org on 2016/09/28 23:36:09 UTC

svn commit: r1762726 [1/3] - in /poi: site/src/documentation/content/xdocs/ trunk/ trunk/src/java/org/apache/poi/ trunk/src/java/org/apache/poi/hssf/model/ trunk/src/java/org/apache/poi/hssf/record/ trunk/src/java/org/apache/poi/hssf/record/cont/ trunk...

Author: kiwiwings
Date: Wed Sep 28 23:36:09 2016
New Revision: 1762726

URL: http://svn.apache.org/viewvc?rev=1762726&view=rev
Log:
Bug 59857 - Password protected files with "Microsoft Enhanced Cryptographic Provider v1.0"

Added:
    poi/trunk/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentInputStream.java
      - copied unchanged from r1762715, poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentInputStream.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentOutputStream.java
      - copied unchanged from r1762715, poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentOutputStream.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/xor/
      - copied from r1762715, poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/
    poi/trunk/src/testcases/org/apache/poi/hssf/usermodel/TestCryptoAPI.java
      - copied unchanged from r1762715, poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/usermodel/TestCryptoAPI.java
    poi/trunk/src/testcases/org/apache/poi/poifs/crypt/AllEncryptionTests.java
      - copied unchanged from r1762715, poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/AllEncryptionTests.java
    poi/trunk/src/testcases/org/apache/poi/poifs/crypt/TestBiff8DecryptingStream.java
      - copied unchanged from r1762715, poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/TestBiff8DecryptingStream.java
    poi/trunk/src/testcases/org/apache/poi/poifs/crypt/TestXorEncryption.java
      - copied unchanged from r1762715, poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/TestXorEncryption.java
    poi/trunk/src/testcases/org/apache/poi/poifs/crypt/binaryrc4/
      - copied from r1762715, poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/binaryrc4/
    poi/trunk/test-data/slideshow/60003.ppt
      - copied unchanged from r1762715, poi/branches/hssf_cryptoapi/test-data/slideshow/60003.ppt
Removed:
    poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8Cipher.java
    poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8RC4.java
    poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8RC4Key.java
    poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8XOR.java
    poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8XORKey.java
    poi/trunk/src/testcases/org/apache/poi/hssf/record/crypto/
Modified:
    poi/site/src/documentation/content/xdocs/encryption.xml
    poi/site/src/documentation/content/xdocs/status.xml
    poi/trunk/   (props changed)
    poi/trunk/src/java/org/apache/poi/POIDocument.java
    poi/trunk/src/java/org/apache/poi/hssf/model/InternalWorkbook.java
    poi/trunk/src/java/org/apache/poi/hssf/record/BoundSheetRecord.java
    poi/trunk/src/java/org/apache/poi/hssf/record/FilePassRecord.java
    poi/trunk/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java
    poi/trunk/src/java/org/apache/poi/hssf/record/RecordInputStream.java
    poi/trunk/src/java/org/apache/poi/hssf/record/cont/ContinuableRecordInput.java
    poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8DecryptingStream.java
    poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java
    poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/Decryptor.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionHeader.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionInfoBuilder.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionMode.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/Encryptor.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Decryptor.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionHeader.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionInfoBuilder.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4EncryptionVerifier.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Encryptor.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDecryptor.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionHeader.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionInfoBuilder.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptionVerifier.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardDecryptor.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionHeader.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionInfoBuilder.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptionVerifier.java
    poi/trunk/src/java/org/apache/poi/poifs/crypt/standard/StandardEncryptor.java
    poi/trunk/src/java/org/apache/poi/poifs/filesystem/DocumentInputStream.java
    poi/trunk/src/java/org/apache/poi/util/LittleEndianByteArrayInputStream.java
    poi/trunk/src/java/org/apache/poi/util/LittleEndianByteArrayOutputStream.java
    poi/trunk/src/java/org/apache/poi/util/LittleEndianInput.java
    poi/trunk/src/java/org/apache/poi/util/LittleEndianInputStream.java
    poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileDecryptor.java
    poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionHeader.java
    poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionInfoBuilder.java
    poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptionVerifier.java
    poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/agile/AgileEncryptor.java
    poi/trunk/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestAgileEncryptionParameters.java
    poi/trunk/src/scratchpad/src/org/apache/poi/hslf/record/DocumentEncryptionAtom.java
    poi/trunk/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowEncrypted.java
    poi/trunk/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowImpl.java
    poi/trunk/src/scratchpad/testcases/org/apache/poi/hslf/record/TestDocumentEncryption.java
    poi/trunk/src/testcases/org/apache/poi/hssf/record/AllRecordTests.java
    poi/trunk/src/testcases/org/apache/poi/hssf/record/TestRecordFactoryInputStream.java
    poi/trunk/src/testcases/org/apache/poi/poifs/crypt/TestCipherAlgorithm.java

Modified: poi/site/src/documentation/content/xdocs/encryption.xml
URL: http://svn.apache.org/viewvc/poi/site/src/documentation/content/xdocs/encryption.xml?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/site/src/documentation/content/xdocs/encryption.xml (original)
+++ poi/site/src/documentation/content/xdocs/encryption.xml Wed Sep 28 23:36:09 2016
@@ -74,8 +74,8 @@
             <td>XWPF</td>
         </tr>
         <tr>
-            <td><link href="https://msdn.microsoft.com/en-us/library/dd949802(v=office.12).aspx">XOR obfuscation</link></td>
-            <td class="feature-partly">Read</td>
+            <td><link href="https://msdn.microsoft.com/en-us/library/dd949802(v=office.12).aspx">XOR obfuscation *)</link></td>
+            <td class="feature-yes">Yes</td>
             <td class="feature-na">N/A</td>
             <td class="feature-no">No</td>
             <td class="feature-na">N/A</td>
@@ -84,7 +84,7 @@
         </tr>
         <tr>
             <td><link href="https://msdn.microsoft.com/en-us/library/dd909583(v=office.12).aspx">40-bit RC4 encryption</link></td>
-            <td class="feature-partly">Read</td>
+            <td class="feature-yes">Yes</td>
             <td class="feature-na">N/A</td>
             <td class="feature-no">No</td>
             <td class="feature-na">N/A</td>
@@ -93,7 +93,7 @@
         </tr>
         <tr>
             <td><link href="https://msdn.microsoft.com/en-us/library/dd910113(v=office.12).aspx">Office Binary Document RC4 CryptoAPI Encryption</link></td>
-            <td class="feature-no">No</td>
+            <td class="feature-yes">Yes</td>
             <td class="feature-yes">Yes</td>
             <td class="feature-no">No</td>
             <td class="feature-na">N/A</td>
@@ -101,7 +101,7 @@
             <td class="feature-na">N/A</td>
         </tr>
         <tr>
-            <td><link href="https://msdn.microsoft.com/en-us/library/dd907466(v=office.12).aspx">Office Binary Document RC4 Encryption *)</link></td>
+            <td><link href="https://msdn.microsoft.com/en-us/library/dd907466(v=office.12).aspx">Office Binary Document RC4 Encryption **)</link></td>
             <td class="feature-na">N/A</td>
             <td class="feature-na">N/A</td>
             <td class="feature-na">N/A</td>
@@ -138,7 +138,10 @@
         </tr>
     </table>
     
-    <p>*) the <link href="https://msdn.microsoft.com/en-us/library/cc313071(v=office.12).aspx">MS-OFFCRYPTO</link>
+    <p>*) the xor encryption is flawed and works only for very small files - see <link href="https://bz.apache.org/bugzilla/show_bug.cgi?id=59857">#59857</link>.
+    </p>
+
+    <p>**) the <link href="https://msdn.microsoft.com/en-us/library/cc313071(v=office.12).aspx">MS-OFFCRYPTO</link>
     documentation only mentions the RC4 (without CryptoAPI) encryption as a "in place" encryption, but
     apparently there's also a container based method with that key generation logic. 
     </p>

Modified: poi/site/src/documentation/content/xdocs/status.xml
URL: http://svn.apache.org/viewvc/poi/site/src/documentation/content/xdocs/status.xml?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/site/src/documentation/content/xdocs/status.xml (original)
+++ poi/site/src/documentation/content/xdocs/status.xml Wed Sep 28 23:36:09 2016
@@ -40,6 +40,7 @@
     </devs>
 
     <release version="3.16-beta1" date="2016-11-??">
+        <action dev="PD" type="fix" fixes-bug="59857">Password protected files with "Microsoft Enhanced Cryptographic Provider v1.0"</action>
         <action dev="PD" type="fix" fixes-bug="59687">XSSF: Comments removed from wrong row when removing a row from a sheet with empty rows</action>
         <action dev="PD" type="fix" fixes-bug="59933">POI: Fix IllegalAccess exception caused by logger</action>
         <action dev="PD" type="add" fixes-bug="59853">XSSF: Support Table (structured reference) sources in PivotTables</action>

Propchange: poi/trunk/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Wed Sep 28 23:36:09 2016
@@ -1,6 +1,7 @@
 /poi/branches/common_sl:1661320-1691849
 /poi/branches/excelant:1069732-1073692
 /poi/branches/gsoc2012:1341450-1371650
+/poi/branches/hssf_cryptoapi:1753906-1762715
 /poi/branches/ss_border_property_template:1747847-1748074
 /poi/branches/xml_signature:1617139-1628347
 /poi/branches/xssf_structured_references:1747607-1747656

Modified: poi/trunk/src/java/org/apache/poi/POIDocument.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/POIDocument.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/POIDocument.java (original)
+++ poi/trunk/src/java/org/apache/poi/POIDocument.java Wed Sep 28 23:36:09 2016
@@ -32,6 +32,7 @@ import org.apache.poi.hpsf.PropertySet;
 import org.apache.poi.hpsf.PropertySetFactory;
 import org.apache.poi.hpsf.SummaryInformation;
 import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIDecryptor;
 import org.apache.poi.poifs.filesystem.DirectoryNode;
 import org.apache.poi.poifs.filesystem.DocumentInputStream;
 import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
@@ -59,6 +60,8 @@ public abstract class POIDocument implem
 
     /* Have the property streams been read yet? (Only done on-demand) */
     private boolean initialized = false;
+
+    private static final String[] encryptedStreamNames = { "EncryptedSummary" };
     
     /**
      * Constructs a POIDocument with the given directory node.
@@ -195,13 +198,18 @@ public abstract class POIDocument implem
         try {
             if (encryptionInfo != null) {
                 step = "getting encrypted";
-                InputStream is = encryptionInfo.getDecryptor().getDataStream(directory);
-                try {
-                    encPoifs = new NPOIFSFileSystem(is);
-                    dirNode = encPoifs.getRoot();
-                } finally {
-                    is.close();
+                String encryptedStream = null;
+                for (String s : encryptedStreamNames) {
+                    if (dirNode.hasEntry(s)) {
+                        encryptedStream = s;
+                    }
+                }
+                if (encryptedStream == null) {
+                    throw new EncryptedDocumentException("can't find matching encrypted property stream");
                 }
+                CryptoAPIDecryptor dec = (CryptoAPIDecryptor)encryptionInfo.getDecryptor();
+                encPoifs = dec.getSummaryEntries(dirNode, encryptedStream);
+                dirNode = encPoifs.getRoot();
             }
             
             //directory can be null when creating new documents

Modified: poi/trunk/src/java/org/apache/poi/hssf/model/InternalWorkbook.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/model/InternalWorkbook.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/model/InternalWorkbook.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/model/InternalWorkbook.java Wed Sep 28 23:36:09 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/trunk/src/java/org/apache/poi/hssf/record/BoundSheetRecord.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/BoundSheetRecord.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/BoundSheetRecord.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/BoundSheetRecord.java Wed Sep 28 23:36:09 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/trunk/src/java/org/apache/poi/hssf/record/FilePassRecord.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/FilePassRecord.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/FilePassRecord.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/FilePassRecord.java Wed Sep 28 23:36:09 2016
@@ -17,9 +17,22 @@
 
 package org.apache.poi.hssf.record;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
 import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.poifs.crypt.EncryptionMode;
+import org.apache.poi.poifs.crypt.binaryrc4.BinaryRC4EncryptionHeader;
+import org.apache.poi.poifs.crypt.binaryrc4.BinaryRC4EncryptionVerifier;
+import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptionHeader;
+import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptionVerifier;
+import org.apache.poi.poifs.crypt.xor.XOREncryptionHeader;
+import org.apache.poi.poifs.crypt.xor.XOREncryptionVerifier;
 import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
 import org.apache.poi.util.LittleEndianOutput;
+import org.apache.poi.util.LittleEndianOutputStream;
 
 /**
  * Title: File Pass Record (0x002F) <p>
@@ -31,228 +44,92 @@ public final class FilePassRecord extend
     private static final int ENCRYPTION_XOR = 0;
     private static final int ENCRYPTION_OTHER = 1;
 	
-	private int _encryptionType;
-	private KeyData _keyData;
-
-	private static interface KeyData extends Cloneable {
-	    void read(RecordInputStream in);
-	    void serialize(LittleEndianOutput out);
-	    int getDataSize();
-	    void appendToString(StringBuffer buffer);
-	    KeyData clone(); // NOSONAR
-	} 
+	private final int encryptionType;
+    private EncryptionInfo encryptionInfo;
 	
-	public static final class Rc4KeyData implements KeyData, Cloneable {
-	    private static final int ENCRYPTION_OTHER_RC4 = 1;
-	    private static final int ENCRYPTION_OTHER_CAPI_2 = 2;
-	    private static final int ENCRYPTION_OTHER_CAPI_3 = 3;
-        private static final int ENCRYPTION_OTHER_CAPI_4 = 4;
-	    
-	    private byte[] _salt;
-	    private byte[] _encryptedVerifier;
-	    private byte[] _encryptedVerifierHash;
-	    private int _encryptionInfo;
-	    private int _minorVersionNo;
-	    
-	    public void read(RecordInputStream in) {
-	        _encryptionInfo = in.readUShort();
-	        switch (_encryptionInfo) {
-	            case ENCRYPTION_OTHER_RC4:
-	                // handled below
-	                break;
-	            case ENCRYPTION_OTHER_CAPI_2:
-	            case ENCRYPTION_OTHER_CAPI_3:
-                case ENCRYPTION_OTHER_CAPI_4:
-	                throw new EncryptedDocumentException(
-	                        "HSSF does not currently support CryptoAPI encryption");
-	            default:
-	                throw new RecordFormatException("Unknown encryption info " + _encryptionInfo);
-	        }
-	        _minorVersionNo = in.readUShort();
-	        if (_minorVersionNo!=1) {
-	            throw new RecordFormatException("Unexpected VersionInfo number for RC4Header " + _minorVersionNo);
-	        }
-	        _salt = FilePassRecord.read(in, 16);
-	        _encryptedVerifier = FilePassRecord.read(in, 16);
-	        _encryptedVerifierHash = FilePassRecord.read(in, 16);
-	    }
-	    
-	    public void serialize(LittleEndianOutput out) {
-            out.writeShort(_encryptionInfo);
-            out.writeShort(_minorVersionNo);
-            out.write(_salt);
-            out.write(_encryptedVerifier);
-            out.write(_encryptedVerifierHash);
-	    }
-	    
-	    public int getDataSize() {
-	        return 54;
-	    }
-
-        public byte[] getSalt() {
-            return _salt.clone();
-        }
-
-        public void setSalt(byte[] salt) {
-            this._salt = salt.clone();
-        }
-
-        public byte[] getEncryptedVerifier() {
-            return _encryptedVerifier.clone();
-        }
-
-        public void setEncryptedVerifier(byte[] encryptedVerifier) {
-            this._encryptedVerifier = encryptedVerifier.clone();
-        }
-
-        public byte[] getEncryptedVerifierHash() {
-            return _encryptedVerifierHash.clone();
-        }
-
-        public void setEncryptedVerifierHash(byte[] encryptedVerifierHash) {
-            this._encryptedVerifierHash = encryptedVerifierHash.clone();
-        }
-        
-        public void appendToString(StringBuffer buffer) {
-            buffer.append("    .rc4.info = ").append(HexDump.shortToHex(_encryptionInfo)).append("\n");
-            buffer.append("    .rc4.ver  = ").append(HexDump.shortToHex(_minorVersionNo)).append("\n");
-            buffer.append("    .rc4.salt = ").append(HexDump.toHex(_salt)).append("\n");
-            buffer.append("    .rc4.verifier = ").append(HexDump.toHex(_encryptedVerifier)).append("\n");
-            buffer.append("    .rc4.verifierHash = ").append(HexDump.toHex(_encryptedVerifierHash)).append("\n");
-        }
-        
-        @Override
-        public Rc4KeyData clone() {
-            Rc4KeyData other = new Rc4KeyData();
-            other._salt = this._salt.clone();
-            other._encryptedVerifier = this._encryptedVerifier.clone();
-            other._encryptedVerifierHash = this._encryptedVerifierHash.clone();
-            other._encryptionInfo = this._encryptionInfo;
-            other._minorVersionNo = this._minorVersionNo;
-            return other;
-        }
-	}
-
-	public static final class XorKeyData implements KeyData, Cloneable {
-	    /**
-	     * key (2 bytes): An unsigned integer that specifies the obfuscation key. 
-	     * See [MS-OFFCRYPTO], 2.3.6.2 section, the first step of initializing XOR
-	     * array where it describes the generation of 16-bit XorKey value.
-	     */
-	    private int _key;
-
-	    /**
-	     * verificationBytes (2 bytes): An unsigned integer that specifies
-	     * the password verification identifier.
-	     */
-	    private int _verifier;
-	    
-        public void read(RecordInputStream in) {
-            _key = in.readUShort();
-            _verifier = in.readUShort();
-        }
-
-        public void serialize(LittleEndianOutput out) {
-            out.writeShort(_key);
-            out.writeShort(_verifier);
-        }
-
-        public int getDataSize() {
-            // TODO: Check!
-            return 6;
-        }
-
-        public int getKey() {
-            return _key;
-        }
-        
-        public int getVerifier() {
-            return _verifier;
-        }
-        
-        public void setKey(int key) {
-            this._key = key;
-        }
-        
-        public void setVerifier(int verifier) {
-            this._verifier = verifier;
-        }
-        
-        public void appendToString(StringBuffer buffer) {
-            buffer.append("    .xor.key = ").append(HexDump.intToHex(_key)).append("\n");
-            buffer.append("    .xor.verifier  = ").append(HexDump.intToHex(_verifier)).append("\n");
-        }
-        
-        @Override
-        public XorKeyData clone() {
-            XorKeyData other = new XorKeyData();
-            other._key = this._key;
-            other._verifier = this._verifier;
-            return other;
+	private FilePassRecord(FilePassRecord other) {
+	    encryptionType = other.encryptionType;
+        try {
+            encryptionInfo = other.encryptionInfo.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new EncryptedDocumentException(e);
         }
 	}
 	
-	
-	private FilePassRecord(FilePassRecord other) {
-	    _encryptionType = other._encryptionType;
-	    _keyData = other._keyData.clone();
+	public FilePassRecord(EncryptionMode encryptionMode) {
+	    encryptionType = (encryptionMode == EncryptionMode.xor) ? ENCRYPTION_XOR : ENCRYPTION_OTHER;
+	    encryptionInfo = new EncryptionInfo(encryptionMode);
 	}
 	
 	public FilePassRecord(RecordInputStream in) {
-		_encryptionType = in.readUShort();
-
-		switch (_encryptionType) {
-			case ENCRYPTION_XOR:
-			    _keyData = new XorKeyData();
-			    break;
-			case ENCRYPTION_OTHER:
-			    _keyData = new Rc4KeyData();
-				break;
-			default:
-				throw new RecordFormatException("Unknown encryption type " + _encryptionType);
-		}
-
-		_keyData.read(in);
+		encryptionType = in.readUShort();
+		
+		EncryptionMode preferredMode;
+        switch (encryptionType) {
+            case ENCRYPTION_XOR:
+                preferredMode = EncryptionMode.xor;
+                break;
+            case ENCRYPTION_OTHER:
+                preferredMode = EncryptionMode.cryptoAPI;
+                break;
+            default:
+                throw new EncryptedDocumentException("invalid encryption type");
+        }
+		
+		try {
+            encryptionInfo = new EncryptionInfo(in, preferredMode);
+        } catch (IOException e) {
+            throw new EncryptedDocumentException(e);
+        }
 	}
 
-	private static byte[] read(RecordInputStream in, int size) {
-		byte[] result = new byte[size];
-		in.readFully(result);
-		return result;
-	}
+	@SuppressWarnings("resource")
+    @Override
+    public void serialize(LittleEndianOutput out) {
+        out.writeShort(encryptionType);
+
+        byte data[] = new byte[1024];
+        LittleEndianByteArrayOutputStream bos = new LittleEndianByteArrayOutputStream(data, 0);
+
+        switch (encryptionInfo.getEncryptionMode()) {
+            case xor:
+                ((XOREncryptionHeader)encryptionInfo.getHeader()).write(bos);
+                ((XOREncryptionVerifier)encryptionInfo.getVerifier()).write(bos);
+                break;
+            case binaryRC4:
+                out.writeShort(encryptionInfo.getVersionMajor());
+                out.writeShort(encryptionInfo.getVersionMinor());
+                ((BinaryRC4EncryptionHeader)encryptionInfo.getHeader()).write(bos);
+                ((BinaryRC4EncryptionVerifier)encryptionInfo.getVerifier()).write(bos);
+                break;
+            case cryptoAPI:
+                out.writeShort(encryptionInfo.getVersionMajor());
+                out.writeShort(encryptionInfo.getVersionMinor());
+                out.writeInt(encryptionInfo.getEncryptionFlags());
+                ((CryptoAPIEncryptionHeader)encryptionInfo.getHeader()).write(bos);
+                ((CryptoAPIEncryptionVerifier)encryptionInfo.getVerifier()).write(bos);
+                break;
+            default:
+                throw new RuntimeException("not supported");
+        }
 
-	public void serialize(LittleEndianOutput out) {
-		out.writeShort(_encryptionType);
-		assert(_keyData != null);
-		_keyData.serialize(out);
+        out.write(data, 0, bos.getWriteIndex());
 	}
 
-	protected int getDataSize() {
-	    assert(_keyData != null);
-	    return _keyData.getDataSize();
+	@Override
+    protected int getDataSize() {
+	    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+	    LittleEndianOutputStream leos = new LittleEndianOutputStream(bos);
+        serialize(leos);
+        return bos.size();
 	}
 
-	public Rc4KeyData getRc4KeyData() {
-	    return (_keyData instanceof Rc4KeyData)
-            ? (Rc4KeyData) _keyData
-            : null;
-	}
-	
-    public XorKeyData getXorKeyData() {
-        return (_keyData instanceof XorKeyData)
-            ? (XorKeyData) _keyData
-            : null;
-    }
-    
-    private Rc4KeyData checkRc4() {
-        Rc4KeyData rc4 = getRc4KeyData();
-        if (rc4 == null) {
-            throw new RecordFormatException("file pass record doesn't contain a rc4 key.");
-        }
-        return rc4;
+	public EncryptionInfo getEncryptionInfo() {
+        return encryptionInfo;
     }
 
-	public short getSid() {
+    @Override
+    public short getSid() {
 		return sid;
 	}
 	
@@ -261,12 +138,18 @@ public final class FilePassRecord extend
 		return new FilePassRecord(this);
 	}
 
-	public String toString() {
+	@Override
+    public String toString() {
 		StringBuffer buffer = new StringBuffer();
 
 		buffer.append("[FILEPASS]\n");
-		buffer.append("    .type = ").append(HexDump.shortToHex(_encryptionType)).append("\n");
-		_keyData.appendToString(buffer);
+		buffer.append("    .type = ").append(HexDump.shortToHex(encryptionType)).append("\n");
+        String prefix = "     ."+encryptionInfo.getEncryptionMode();
+        buffer.append(prefix+".info = ").append(HexDump.shortToHex(encryptionInfo.getVersionMajor())).append("\n");
+        buffer.append(prefix+".ver  = ").append(HexDump.shortToHex(encryptionInfo.getVersionMinor())).append("\n");
+        buffer.append(prefix+".salt = ").append(HexDump.toHex(encryptionInfo.getVerifier().getSalt())).append("\n");
+        buffer.append(prefix+".verifier = ").append(HexDump.toHex(encryptionInfo.getVerifier().getEncryptedVerifier())).append("\n");
+        buffer.append(prefix+".verifierHash = ").append(HexDump.toHex(encryptionInfo.getVerifier().getEncryptedVerifierHash())).append("\n");
 		buffer.append("[/FILEPASS]\n");
 		return buffer.toString();
 	}

Modified: poi/trunk/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java Wed Sep 28 23:36:09 2016
@@ -17,18 +17,16 @@
 package org.apache.poi.hssf.record;
 
 import java.io.InputStream;
+import java.security.GeneralSecurityException;
 import java.util.ArrayList;
 import java.util.List;
 
 import org.apache.poi.EncryptedDocumentException;
 import org.apache.poi.hssf.eventusermodel.HSSFEventFactory;
 import org.apache.poi.hssf.eventusermodel.HSSFListener;
-import org.apache.poi.hssf.record.FilePassRecord.Rc4KeyData;
-import org.apache.poi.hssf.record.FilePassRecord.XorKeyData;
 import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
-import org.apache.poi.hssf.record.crypto.Biff8RC4Key;
-import org.apache.poi.hssf.record.crypto.Biff8XORKey;
 import org.apache.poi.poifs.crypt.Decryptor;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
 
 /**
  * A stream based way to get at complete records, with
@@ -80,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 {
@@ -114,31 +108,18 @@ public final class RecordFactoryInputStr
 			    userPassword = Decryptor.DEFAULT_PASSWORD;
 			}
 
-			Biff8EncryptionKey key;
-			if (fpr.getRc4KeyData() != null) {
-			    Rc4KeyData rc4 = fpr.getRc4KeyData();
-			    Biff8RC4Key rc4key = Biff8RC4Key.create(userPassword, rc4.getSalt());
-			    key = rc4key;
-			    if (!rc4key.validate(rc4.getEncryptedVerifier(), rc4.getEncryptedVerifierHash())) {
-	                throw new EncryptedDocumentException(
-                        (Decryptor.DEFAULT_PASSWORD.equals(userPassword) ? "Default" : "Supplied")
-                        + " password is invalid for salt/verifier/verifierHash");
-			    }
-			} else if (fpr.getXorKeyData() != null) {
-			    XorKeyData xor = fpr.getXorKeyData();
-			    Biff8XORKey xorKey = Biff8XORKey.create(userPassword, xor.getKey());
-			    key = xorKey;
-			    
-			    if (!xorKey.validate(userPassword, xor.getVerifier())) {
+			EncryptionInfo info = fpr.getEncryptionInfo();
+            try {
+                if (!info.getDecryptor().verifyPassword(userPassword)) {
                     throw new EncryptedDocumentException(
-		                (Decryptor.DEFAULT_PASSWORD.equals(userPassword) ? "Default" : "Supplied")
-		                + " password is invalid for key/verifier");
-			    }
-			} else {
-			    throw new EncryptedDocumentException("Crypto API not yet supported.");
-			}
+                            (Decryptor.DEFAULT_PASSWORD.equals(userPassword) ? "Default" : "Supplied")
+                            + " password is invalid for salt/verifier/verifierHash");
+                }
+            } catch (GeneralSecurityException e) {
+                throw new EncryptedDocumentException(e);
+            }
 
-			return new RecordInputStream(original, key, _initialRecordsSize);
+			return new RecordInputStream(original, info, _initialRecordsSize);
 		}
 
 		public boolean hasEncryption() {

Modified: poi/trunk/src/java/org/apache/poi/hssf/record/RecordInputStream.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/RecordInputStream.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/RecordInputStream.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/RecordInputStream.java Wed Sep 28 23:36:09 2016
@@ -18,13 +18,14 @@
 package org.apache.poi.hssf.record;
 
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.util.Locale;
 
 import org.apache.poi.hssf.dev.BiffViewer;
 import org.apache.poi.hssf.record.crypto.Biff8DecryptingStream;
-import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.util.Internal;
 import org.apache.poi.util.LittleEndianConsts;
 import org.apache.poi.util.LittleEndianInput;
 import org.apache.poi.util.LittleEndianInputStream;
@@ -32,8 +33,6 @@ import org.apache.poi.util.LittleEndianI
 /**
  * Title:  Record Input Stream<P>
  * Description:  Wraps a stream and provides helper methods for the construction of records.<P>
- *
- * @author Jason Height (jheight @ apache dot org)
  */
 public final class RecordInputStream implements LittleEndianInput {
 	/** Maximum size of a single record (minus the 4 byte header) without a continue*/
@@ -91,6 +90,10 @@ public final class RecordInputStream imp
 	 * index within the data section of the current BIFF record
 	 */
 	private int _currentDataOffset;
+	/**
+	 * index within the data section when mark() was called
+	 */
+	private int _markedDataOffset;
 
 	private static final class SimpleHeaderInput implements BiffHeaderInput {
 
@@ -117,14 +120,14 @@ public final class RecordInputStream imp
 		this (in, null, 0);
 	}
 
-	public RecordInputStream(InputStream in, Biff8EncryptionKey key, int initialOffset) throws RecordFormatException {
+	public RecordInputStream(InputStream in, EncryptionInfo key, int initialOffset) throws RecordFormatException {
 		if (key == null) {
 			_dataInput = getLEI(in);
 			_bhi = new SimpleHeaderInput(in);
 		} else {
 			Biff8DecryptingStream bds = new Biff8DecryptingStream(in, initialOffset, key);
+            _dataInput = bds;
 			_bhi = bds;
-			_dataInput = bds;
 		}
 		_nextSid = readNextSid();
 	}
@@ -305,13 +308,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();
@@ -331,7 +343,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;
@@ -491,4 +507,31 @@ public final class RecordInputStream imp
     public int getNextSid() {
         return _nextSid;
     }
+
+    /**
+     * Mark the stream position - experimental function 
+     *
+     * @param readlimit the read ahead limit
+     * 
+     * @see InputStream#mark(int)
+     */
+    @Internal
+    public void mark(int readlimit) {
+        ((InputStream)_dataInput).mark(readlimit);
+        _markedDataOffset = _currentDataOffset;
+    }
+    
+    /**
+     * Resets the stream position to the previously marked position.
+     * Experimental function - this only works, when nextRecord() wasn't called in the meantime.
+     *
+     * @throws IOException if marking is not supported
+     * 
+     * @see InputStream#reset()
+     */
+    @Internal
+    public void reset() throws IOException {
+        ((InputStream)_dataInput).reset();
+        _currentDataOffset = _markedDataOffset;
+    }
 }

Modified: poi/trunk/src/java/org/apache/poi/hssf/record/cont/ContinuableRecordInput.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/cont/ContinuableRecordInput.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/cont/ContinuableRecordInput.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/cont/ContinuableRecordInput.java Wed Sep 28 23:36:09 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/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8DecryptingStream.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8DecryptingStream.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8DecryptingStream.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8DecryptingStream.java Wed Sep 28 23:36:09 2016
@@ -18,102 +18,194 @@
 package org.apache.poi.hssf.record.crypto;
 
 import java.io.InputStream;
+import java.io.PushbackInputStream;
 
-import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.hssf.record.BOFRecord;
 import org.apache.poi.hssf.record.BiffHeaderInput;
+import org.apache.poi.hssf.record.FilePassRecord;
+import org.apache.poi.hssf.record.InterfaceHdrRecord;
+import org.apache.poi.hssf.record.RecordFormatException;
+import org.apache.poi.poifs.crypt.ChunkedCipherInputStream;
+import org.apache.poi.poifs.crypt.Decryptor;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianConsts;
 import org.apache.poi.util.LittleEndianInput;
-import org.apache.poi.util.LittleEndianInputStream;
 
-/**
- *
- * @author Josh Micich
- */
 public final class Biff8DecryptingStream implements BiffHeaderInput, LittleEndianInput {
 
-	private final LittleEndianInput _le;
-	private final Biff8Cipher _cipher;
+    public static final int RC4_REKEYING_INTERVAL = 1024;
 
-	public Biff8DecryptingStream(InputStream in, int initialOffset, Biff8EncryptionKey key) {
-	    if (key instanceof Biff8RC4Key) {
-	        _cipher = new Biff8RC4(initialOffset, (Biff8RC4Key)key);
-	    } else if (key instanceof Biff8XORKey) {
-	        _cipher = new Biff8XOR(initialOffset, (Biff8XORKey)key);
-	    } else {
-	        throw new EncryptedDocumentException("Crypto API not supported yet.");
-	    }
-
-		if (in instanceof LittleEndianInput) {
-			// accessing directly is an optimisation
-			_le = (LittleEndianInput) in;
-		} else {
-			// less optimal, but should work OK just the same. Often occurs in junit tests.
-			_le = new LittleEndianInputStream(in);
-		}
-	}
-
-	public int available() {
-		return _le.available();
+    private final EncryptionInfo info;
+    private ChunkedCipherInputStream ccis;
+    private final byte buffer[] = new byte[LittleEndianConsts.LONG_SIZE];
+    private boolean shouldSkipEncryptionOnCurrentRecord = false;
+
+	public Biff8DecryptingStream(InputStream in, int initialOffset, EncryptionInfo info) throws RecordFormatException {
+        try {
+    	    byte initialBuf[] = new byte[initialOffset];
+    	    InputStream stream;
+    	    if (initialOffset == 0) {
+    	        stream = in;
+    	    } else {
+    	        stream = new PushbackInputStream(in, initialOffset);
+    	        ((PushbackInputStream)stream).unread(initialBuf);
+    	    }
+    	    
+            this.info = info;
+            Decryptor dec = this.info.getDecryptor();
+            dec.setChunkSize(RC4_REKEYING_INTERVAL);
+            ccis = (ChunkedCipherInputStream)dec.getDataStream(stream, Integer.MAX_VALUE, 0);
+            
+            if (initialOffset > 0) {
+                ccis.readFully(initialBuf);
+            }
+        } catch (Exception e) {
+            throw new RecordFormatException(e);
+        }
+	}
+
+	@Override
+    public int available() {
+		return ccis.available();
 	}
 
 	/**
 	 * Reads an unsigned short value without decrypting
 	 */
-	public int readRecordSID() {
-		int sid = _le.readUShort();
-		_cipher.skipTwoBytes();
-		_cipher.startRecord(sid);
+	@Override
+    public int readRecordSID() {
+	    readPlain(buffer, 0, LittleEndianConsts.SHORT_SIZE);
+		int sid = LittleEndian.getUShort(buffer, 0);
+		shouldSkipEncryptionOnCurrentRecord = isNeverEncryptedRecord(sid);
 		return sid;
 	}
 
 	/**
 	 * Reads an unsigned short value without decrypting
 	 */
-	public int readDataSize() {
-		int dataSize = _le.readUShort();
-		_cipher.skipTwoBytes();
-		_cipher.setNextRecordSize(dataSize);
+	@Override
+    public int readDataSize() {
+        readPlain(buffer, 0, LittleEndianConsts.SHORT_SIZE);
+        int dataSize = LittleEndian.getUShort(buffer, 0);
+        ccis.setNextRecordSize(dataSize);
 		return dataSize;
 	}
 
-	public double readDouble() {
-		long valueLongBits = readLong();
+	@Override
+    public double readDouble() {
+	    long valueLongBits = readLong();
 		double result = Double.longBitsToDouble(valueLongBits);
 		if (Double.isNaN(result)) {
-			throw new RuntimeException("Did not expect to read NaN"); // (Because Excel typically doesn't write NaN
+		    // (Because Excel typically doesn't write NaN
+		    throw new RuntimeException("Did not expect to read NaN");
 		}
 		return result;
 	}
 
-	public void readFully(byte[] buf) {
-		readFully(buf, 0, buf.length);
-	}
-
-	public void readFully(byte[] buf, int off, int len) {
-		_le.readFully(buf, off, len);
-		_cipher.xor(buf, off, len);
-	}
-
-
-	public int readUByte() {
-		return readByte() & 0xFF;
-	}
-	public byte readByte() {
-		return (byte) _cipher.xorByte(_le.readUByte());
-	}
-
-
-	public int readUShort() {
-		return readShort() & 0xFFFF;
-	}
-	public short readShort() {
-		return (short) _cipher.xorShort(_le.readUShort());
+	@Override
+    public void readFully(byte[] buf) {
+	    readFully(buf, 0, buf.length);
+	}
+
+	@Override
+    public void readFully(byte[] buf, int off, int len) {
+        if (shouldSkipEncryptionOnCurrentRecord) {
+            readPlain(buf, off, buf.length);
+        } else {
+            ccis.readFully(buf, off, len);
+        }
+	}
+
+	@Override
+    public int readUByte() {
+	    return readByte() & 0xFF;
+	}
+	
+	@Override
+    public byte readByte() {
+        if (shouldSkipEncryptionOnCurrentRecord) {
+            readPlain(buffer, 0, LittleEndianConsts.BYTE_SIZE);
+            return buffer[0];
+        } else {
+            return ccis.readByte();
+        }
+	}
+
+	@Override
+    public int readUShort() {
+	    return readShort() & 0xFFFF;
+	}
+	
+	@Override
+    public short readShort() {
+        if (shouldSkipEncryptionOnCurrentRecord) {
+            readPlain(buffer, 0, LittleEndianConsts.SHORT_SIZE);
+            return LittleEndian.getShort(buffer);
+        } else {
+            return ccis.readShort();
+        }
+	}
+
+	@Override
+    public int readInt() {
+        if (shouldSkipEncryptionOnCurrentRecord) {
+            readPlain(buffer, 0, LittleEndianConsts.INT_SIZE);
+            return LittleEndian.getInt(buffer);
+        } else {
+            return ccis.readInt();
+        }
+	}
+
+	@Override
+    public long readLong() {
+        if (shouldSkipEncryptionOnCurrentRecord) {
+            readPlain(buffer, 0, LittleEndianConsts.LONG_SIZE);
+            return LittleEndian.getLong(buffer);
+        } else {
+            return ccis.readLong();
+        }
 	}
 
-	public int readInt() {
-		return _cipher.xorInt(_le.readInt());
+	/**
+	 * @return the absolute position in the stream
+	 */
+	public long getPosition() {
+	    return ccis.getPos();
 	}
+	
+    /**
+     * TODO: Additionally, the lbPlyPos (position_of_BOF) field of the BoundSheet8 record MUST NOT be encrypted.
+     *
+     * @return <code>true</code> if record type specified by <tt>sid</tt> is never encrypted
+     */
+    public static boolean isNeverEncryptedRecord(int sid) {
+        switch (sid) {
+            case BOFRecord.sid:
+                // sheet BOFs for sure
+                // TODO - find out about chart BOFs
+
+            case InterfaceHdrRecord.sid:
+                // don't know why this record doesn't seem to get encrypted
+
+            case FilePassRecord.sid:
+                // this only really counts when writing because FILEPASS is read early
+
+            // UsrExcl(0x0194)
+            // FileLock
+            // RRDInfo(0x0196)
+            // RRDHead(0x0138)
+
+                return true;
+
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    public void readPlain(byte b[], int off, int len) {
+        ccis.readPlain(b, off, len);
+    }
 
-	public long readLong() {
-		return _cipher.xorLong(_le.readLong());
-	}
 }

Modified: poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java Wed Sep 28 23:36:09 2016
@@ -16,34 +16,9 @@
 ==================================================================== */
 package org.apache.poi.hssf.record.crypto;
 
-import javax.crypto.SecretKey;
-
-import org.apache.poi.EncryptedDocumentException;
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
-import org.apache.poi.poifs.crypt.Decryptor;
-
-public abstract class Biff8EncryptionKey {
-	protected SecretKey _secretKey;
-
-	/**
-	 * Create using the default password and a specified docId
-	 * @param salt 16 bytes
-	 */
-	public static Biff8EncryptionKey create(byte[] salt) {
-	    return Biff8RC4Key.create(Decryptor.DEFAULT_PASSWORD, salt);
-	}
-	
-	public static Biff8EncryptionKey create(String password, byte[] salt) {
-        return Biff8RC4Key.create(password, salt);
-	}
-
-	/**
-	 * @return <code>true</code> if the keyDigest is compatible with the specified saltData and saltHash
-	 */
-	public boolean validate(byte[] saltData, byte[] saltHash) {
-	    throw new EncryptedDocumentException("validate is not supported (in super-class).");
-	}
 
+public final class Biff8EncryptionKey {
 	/**
 	 * Stores the BIFF8 encryption/decryption password for the current thread.  This has been done
 	 * using a {@link ThreadLocal} in order to avoid further overloading the various public APIs

Modified: poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java Wed Sep 28 23:36:09 2016
@@ -61,8 +61,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 +76,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;
@@ -101,6 +106,8 @@ import org.apache.poi.util.Configurator;
 import org.apache.poi.util.HexDump;
 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;
 
@@ -119,7 +126,6 @@ public final class HSSFWorkbook extends
      * The maximum number of cell styles in a .xls workbook.
      * The 'official' limit is 4,000, but POI allows a slightly larger number.
      * This extra delta takes into account built-in styles that are automatically
-     * created for new workbooks
      *
      * See http://office.microsoft.com/en-us/excel-help/excel-specifications-and-limits-HP005199291.aspx
      */
@@ -1444,7 +1450,7 @@ public final class HSSFWorkbook extends
         if (log.check( POILogger.DEBUG )) {
             log.log(DEBUG, "HSSFWorkbook.getBytes()");
         }
-
+        
         HSSFSheet[] sheets = getSheets();
         int nSheets = sheets.length;
 
@@ -1486,9 +1492,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/trunk/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java (original)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java Wed Sep 28 23:36:09 2016
@@ -16,78 +16,113 @@
 ==================================================================== */
 package org.apache.poi.poifs.crypt;
 
+import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.security.GeneralSecurityException;
 
+import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.ShortBufferException;
 
 import org.apache.poi.EncryptedDocumentException;
 import org.apache.poi.util.Internal;
-import org.apache.poi.util.LittleEndianInput;
 import org.apache.poi.util.LittleEndianInputStream;
 
 @Internal
 public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
-    private final int chunkSize;
-    private final int chunkMask;
-    private final int chunkBits;
-    
-    private int _lastIndex = 0;
-    private long _pos = 0;
-    private long _size;
-    private byte[] _chunk;
-    private Cipher _cipher;
-
-    public ChunkedCipherInputStream(LittleEndianInput stream, long size, int chunkSize)
-        throws GeneralSecurityException {
-        super((InputStream)stream);
+    private final int _chunkSize;
+    private final int _chunkBits;
+
+    private final long _size;
+    private final byte[] _chunk, _plain;
+    private final Cipher _cipher;
+
+    private int _lastIndex;
+    private long _pos;
+    private boolean _chunkIsValid = false;
+
+    public ChunkedCipherInputStream(InputStream stream, long size, int chunkSize)
+    throws GeneralSecurityException {
+        this(stream, size, chunkSize, 0);
+    }
+
+    public ChunkedCipherInputStream(InputStream stream, long size, int chunkSize, int initialPos)
+    throws GeneralSecurityException {
+        super(stream);
         _size = size;
-        this.chunkSize = chunkSize;
-        chunkMask = chunkSize-1;
-        chunkBits = Integer.bitCount(chunkMask);
-        
-        _cipher = initCipherForBlock(null, 0);
+        _pos = initialPos;
+        this._chunkSize = chunkSize;
+        int cs = chunkSize == -1 ? 4096 : chunkSize;
+        _chunk = new byte[cs];
+        _plain = new byte[cs];
+        _chunkBits = Integer.bitCount(_chunk.length-1);
+        _lastIndex = (int)(_pos >> _chunkBits);
+        _cipher = initCipherForBlock(null, _lastIndex);
     }
-    
+
+    public final Cipher initCipherForBlock(int block) throws IOException, GeneralSecurityException {
+        if (_chunkSize != -1) {
+            throw new GeneralSecurityException("the cipher block can only be set for streaming encryption, e.g. CryptoAPI...");
+        }
+
+        _chunkIsValid = false;
+        return initCipherForBlock(_cipher, block);
+    }
+
     protected abstract Cipher initCipherForBlock(Cipher existing, int block)
     throws GeneralSecurityException;
 
+    @Override
     public int read() throws IOException {
         byte[] b = new byte[1];
-        if (read(b) == 1)
+        if (read(b) == 1) {
             return b[0];
+        }
         return -1;
     }
 
     // do not implement! -> recursion
     // public int read(byte[] b) throws IOException;
 
+    @Override
     public int read(byte[] b, int off, int len) throws IOException {
+        return read(b, off, len, false);
+    }
+
+    private int read(byte[] b, int off, int len, boolean readPlain) throws IOException {
         int total = 0;
-        
-        if (available() <= 0) return -1;
 
+        if (available() <= 0) {
+            return -1;
+        }
+
+        final int chunkMask = getChunkMask();
         while (len > 0) {
-            if (_chunk == null) {
+            if (!_chunkIsValid) {
                 try {
-                    _chunk = nextChunk();
+                    nextChunk();
+                    _chunkIsValid = true;
                 } catch (GeneralSecurityException e) {
                     throw new EncryptedDocumentException(e.getMessage(), e);
                 }
             }
-            int count = (int)(chunkSize - (_pos & chunkMask));
+            int count = (int)(_chunk.length - (_pos & chunkMask));
             int avail = available();
             if (avail == 0) {
                 return total;
             }
             count = Math.min(avail, Math.min(count, len));
-            System.arraycopy(_chunk, (int)(_pos & chunkMask), b, off, count);
+
+            System.arraycopy(readPlain ? _plain : _chunk, (int)(_pos & chunkMask), b, off, count);
+
             off += count;
             len -= count;
             _pos += count;
-            if ((_pos & chunkMask) == 0)
-                _chunk = null;
+            if ((_pos & chunkMask) == 0) {
+                _chunkIsValid = false;
+            }
             total += count;
         }
 
@@ -95,21 +130,31 @@ public abstract class ChunkedCipherInput
     }
 
     @Override
-    public long skip(long n) throws IOException {
+    public long skip(final long n) throws IOException {
         long start = _pos;
-        long skip = Math.min(available(), n);
+        long skip = Math.min(remainingBytes(), n);
 
-        if ((((_pos + skip) ^ start) & ~chunkMask) != 0)
-            _chunk = null;
+        if ((((_pos + skip) ^ start) & ~getChunkMask()) != 0) {
+            _chunkIsValid = false;
+        }
         _pos += skip;
         return skip;
     }
 
     @Override
     public int available() {
+        return remainingBytes();
+    }
+
+    /**
+     * Helper method for forbidden available call - we know the size beforehand, so it's ok ...
+     *
+     * @return the remaining byte until EOF
+     */
+    private int remainingBytes() {
         return (int)(_size - _pos);
     }
-    
+
     @Override
     public boolean markSupported() {
         return false;
@@ -119,23 +164,115 @@ public abstract class ChunkedCipherInput
     public synchronized void mark(int readlimit) {
         throw new UnsupportedOperationException();
     }
-    
+
     @Override
     public synchronized void reset() throws IOException {
         throw new UnsupportedOperationException();
     }
 
-    private byte[] nextChunk() throws GeneralSecurityException, IOException {
-        int index = (int)(_pos >> chunkBits);
-        initCipherForBlock(_cipher, index);
-        
-        if (_lastIndex != index) {
-            super.skip((index - _lastIndex) << chunkBits);
+    protected int getChunkMask() {
+        return _chunk.length-1;
+    }
+
+    private void nextChunk() throws GeneralSecurityException, IOException {
+        if (_chunkSize != -1) {
+            int index = (int)(_pos >> _chunkBits);
+            initCipherForBlock(_cipher, index);
+
+            if (_lastIndex != index) {
+                super.skip((index - _lastIndex) << _chunkBits);
+            }
+
+            _lastIndex = index + 1;
+        }
+
+        final int todo = (int)Math.min(_size, _chunk.length);
+        int readBytes = 0, totalBytes = 0;
+        do {
+            readBytes = super.read(_plain, totalBytes, todo-totalBytes);
+            totalBytes += Math.max(0, readBytes);
+        } while (readBytes != -1 && totalBytes < todo);
+
+        if (readBytes == -1 && _pos+totalBytes < _size && _size < Integer.MAX_VALUE) {
+            throw new EOFException("buffer underrun");
         }
 
-        byte[] block = new byte[Math.min(super.available(), chunkSize)];
-        super.read(block, 0, block.length);
-        _lastIndex = index + 1;
-        return _cipher.doFinal(block);
+        System.arraycopy(_plain, 0, _chunk, 0, totalBytes);
+
+        invokeCipher(totalBytes, _chunkSize > -1);
+    }
+
+    /**
+     * Helper function for overriding the cipher invocation, i.e. XOR doesn't use a cipher
+     * and uses it's own implementation
+     *
+     * @return
+     * @throws BadPaddingException
+     * @throws IllegalBlockSizeException
+     * @throws ShortBufferException
+     */
+    protected int invokeCipher(int totalBytes, boolean doFinal) throws GeneralSecurityException {
+        if (doFinal) {
+            return _cipher.doFinal(_chunk, 0, totalBytes, _chunk);
+        } else {
+            return _cipher.update(_chunk, 0, totalBytes, _chunk);
+        }
+    }
+
+    /**
+     * Used when BIFF header fields (sid, size) are being read. The internal
+     * {@link Cipher} instance must step even when unencrypted bytes are read
+     * 
+     */
+    @Override
+    public void readPlain(byte b[], int off, int len) {
+        if (len <= 0) {
+            return;
+        }
+
+        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 decryption
+     *
+     * @param recordSize the size of the next record
+     */
+    public void setNextRecordSize(int recordSize) {
+    }
+
+    /**
+     * @return the chunk bytes
+     */
+    protected byte[] getChunk() {
+        return _chunk;
+    }
+
+    /**
+     * @return the plain bytes
+     */
+    protected byte[] getPlain() {
+        return _plain;
+    }
+
+    /**
+     * @return the absolute position in the stream
+     */
+    public long getPos() {
+        return _pos;
     }
 }

Modified: poi/trunk/src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java (original)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/ChunkedCipherOutputStream.java Wed Sep 28 23:36:09 2016
@@ -25,13 +25,18 @@ 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;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.ShortBufferException;
 
 import org.apache.poi.EncryptedDocumentException;
 import org.apache.poi.poifs.filesystem.DirectoryNode;
 import org.apache.poi.poifs.filesystem.POIFSWriterEvent;
 import org.apache.poi.poifs.filesystem.POIFSWriterListener;
+import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.Internal;
 import org.apache.poi.util.LittleEndian;
 import org.apache.poi.util.LittleEndianConsts;
@@ -41,137 +46,246 @@ import org.apache.poi.util.TempFile;
 
 @Internal
 public abstract class ChunkedCipherOutputStream extends FilterOutputStream {
-    private static final POILogger logger = POILogFactory.getLogger(ChunkedCipherOutputStream.class);
-    
-    protected final int chunkSize;
-    protected final int chunkMask;
-    protected final int chunkBits;
-    
+    private static final POILogger LOG = POILogFactory.getLogger(ChunkedCipherOutputStream.class);
+    private static final int STREAMING = -1;
+
+    private final int _chunkSize;
+    private final int _chunkBits;
+
     private final byte[] _chunk;
-    private final File fileOut;
-    private final DirectoryNode dir;
+    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 {
         super(null);
-        this.chunkSize = chunkSize;
-        chunkMask = chunkSize-1;
-        chunkBits = Integer.bitCount(chunkMask);
-        _chunk = new byte[chunkSize];
-
-        fileOut = TempFile.createTempFile("encrypted_package", "crypt");
-        fileOut.deleteOnExit();
-        this.out = new FileOutputStream(fileOut);
-        this.dir = dir;
+        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();
+        this.out = new FileOutputStream(_fileOut);
+        this._dir = dir;
         _cipher = initCipherForBlock(null, 0, false);
     }
 
+    public ChunkedCipherOutputStream(OutputStream stream, int chunkSize) throws IOException, GeneralSecurityException {
+        super(stream);
+        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;
+        _cipher = initCipherForBlock(null, 0, false);
+    }
+
+    public final Cipher initCipherForBlock(int block, boolean lastChunk) throws IOException, GeneralSecurityException {
+        return initCipherForBlock(_cipher, block, lastChunk);
+    }
+
     protected abstract Cipher initCipherForBlock(Cipher existing, int block, boolean lastChunk)
-    throws GeneralSecurityException;    
-    
-    @SuppressWarnings("hiding")
+    throws IOException, GeneralSecurityException;
+
     protected abstract void calculateChecksum(File fileOut, int oleStreamSize)
     throws GeneralSecurityException, IOException;
-    
-    @SuppressWarnings("hiding")
+
     protected abstract void createEncryptionInfoEntry(DirectoryNode dir, File tmpFile)
     throws IOException, GeneralSecurityException;
 
+    @Override
     public void write(int b) throws IOException {
         write(new byte[]{(byte)b});
     }
 
+    @Override
     public void write(byte[] b) throws IOException {
         write(b, 0, b.length);
     }
 
-    public void write(byte[] b, int off, int len)
-    throws IOException {
-        if (len == 0) return;
-        
+    @Override
+    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;
+        }
+
         if (len < 0 || b.length < off+len) {
             throw new IOException("not enough bytes in your input buffer");
         }
-        
+
+        final int chunkMask = getChunkMask();
         while (len > 0) {
             int posInChunk = (int)(_pos & chunkMask);
-            int nextLen = Math.min(chunkSize-posInChunk, len);
+            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) {
-                try {
-                    writeChunk();
-                } catch (GeneralSecurityException e) {
-                    throw new IOException(e);
-                }
+                writeChunk(len > 0);
             }
         }
     }
 
-    protected void writeChunk() throws IOException, GeneralSecurityException {
-        int posInChunk = (int)(_pos & chunkMask);
+    protected int getChunkMask() {
+        return _chunk.length-1;
+    }
+
+    protected void writeChunk(boolean continued) throws IOException {
+        if (_pos == 0 || _totalPos == _written) {
+            return;
+        }
+
+        int posInChunk = (int)(_pos & getChunkMask());
+
         // normally posInChunk is 0, i.e. on the next chunk (-> index-1)
         // but if called on close(), posInChunk is somewhere within the chunk data
-        int index = (int)(_pos >> chunkBits);
+        int index = (int)(_pos >> _chunkBits);
         boolean lastChunk;
         if (posInChunk==0) {
             index--;
-            posInChunk = chunkSize;
+            posInChunk = _chunk.length;
             lastChunk = false;
         } else {
             // pad the last chunk
             lastChunk = true;
         }
 
-        _cipher = initCipherForBlock(_cipher, index, lastChunk);
+        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;
+                }
+            } else {
+                _cipher = initCipherForBlock(_cipher, index, lastChunk);
+                // restore pos - only streaming chunks will be reset
+                _pos = oldPos;
+            }
+            ciLen = invokeCipher(posInChunk, doFinal);
+        } catch (GeneralSecurityException e) {
+            throw new IOException("can't re-/initialize cipher", e);
+        }
 
-        int ciLen = _cipher.doFinal(_chunk, 0, posInChunk, _chunk);
         out.write(_chunk, 0, ciLen);
+        _plainByteFlags.clear();
+        _written += ciLen;
+    }
+
+    /**
+     * Helper function for overriding the cipher invocation, i.e. XOR doesn't use a cipher
+     * and uses it's own implementation
+     *
+     * @return
+     * @throws BadPaddingException 
+     * @throws IllegalBlockSizeException 
+     * @throws ShortBufferException 
+     */
+    protected int invokeCipher(int posInChunk, boolean doFinal) throws GeneralSecurityException {
+        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
     public void close() throws IOException {
         try {
-            writeChunk();
+            writeChunk(false);
 
             super.close();
-            
-            int oleStreamSize = (int)(fileOut.length()+LittleEndianConsts.LONG_SIZE);
-            calculateChecksum(fileOut, (int)_pos);
-            dir.createDocument(DEFAULT_POIFS_ENTRY, oleStreamSize, new EncryptedPackageWriter());
-            createEncryptionInfoEntry(dir, fileOut);
+
+            if (_fileOut != null) {
+                int oleStreamSize = (int)(_fileOut.length()+LittleEndianConsts.LONG_SIZE);
+                calculateChecksum(_fileOut, (int)_pos);
+                _dir.createDocument(DEFAULT_POIFS_ENTRY, oleStreamSize, new EncryptedPackageWriter());
+                createEncryptionInfoEntry(_dir, _fileOut);
+            }
         } catch (GeneralSecurityException e) {
             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) {
             try {
                 OutputStream os = event.getStream();
-                byte buf[] = new byte[chunkSize];
-    
-                // StreamSize (8 bytes): An unsigned integer that specifies the number of bytes used by data 
-                // encrypted within the EncryptedData field, not including the size of the StreamSize field. 
-                // Note that the actual size of the \EncryptedPackage stream (1) can be larger than this 
+
+                // StreamSize (8 bytes): An unsigned integer that specifies the number of bytes used by data
+                // encrypted within the EncryptedData field, not including the size of the StreamSize field.
+                // Note that the actual size of the \EncryptedPackage stream (1) can be larger than this
                 // value, depending on the block size of the chosen encryption algorithm
+                byte buf[] = new byte[LittleEndianConsts.LONG_SIZE];
                 LittleEndian.putLong(buf, 0, _pos);
-                os.write(buf, 0, LittleEndian.LONG_SIZE);
+                os.write(buf);
 
-                FileInputStream fis = new FileInputStream(fileOut);
-                int readBytes;
-                while ((readBytes = fis.read(buf)) != -1) {
-                    os.write(buf, 0, readBytes);
-                }
+                FileInputStream fis = new FileInputStream(_fileOut);
+                IOUtils.copy(fis, os);
                 fis.close();
 
                 os.close();
-                
-                if (!fileOut.delete()) {
-                    logger.log(POILogger.ERROR, "Can't delete temporary encryption file: "+fileOut);
+
+                if (!_fileOut.delete()) {
+                    LOG.log(POILogger.ERROR, "Can't delete temporary encryption file: "+_fileOut);
                 }
             } catch (IOException e) {
                 throw new EncryptedDocumentException(e);

Modified: poi/trunk/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java (original)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java Wed Sep 28 23:36:09 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/trunk/src/java/org/apache/poi/poifs/crypt/Decryptor.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/Decryptor.java?rev=1762726&r1=1762725&r2=1762726&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/Decryptor.java (original)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/Decryptor.java Wed Sep 28 23:36:09 2016
@@ -20,7 +20,9 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.security.GeneralSecurityException;
 
+import javax.crypto.Cipher;
 import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
 
 import org.apache.poi.EncryptedDocumentException;
 import org.apache.poi.poifs.filesystem.DirectoryNode;
@@ -28,16 +30,15 @@ import org.apache.poi.poifs.filesystem.N
 import org.apache.poi.poifs.filesystem.OPOIFSFileSystem;
 import org.apache.poi.poifs.filesystem.POIFSFileSystem;
 
-public abstract class Decryptor {
+public abstract class Decryptor implements Cloneable {
     public static final String DEFAULT_PASSWORD="VelvetSweatshop";
     public static final String DEFAULT_POIFS_ENTRY="EncryptedPackage";
     
-    protected final EncryptionInfoBuilder builder;
+    protected EncryptionInfo encryptionInfo;
     private SecretKey secretKey;
     private byte[] verifier, integrityHmacKey, integrityHmacValue;
 
-    protected Decryptor(EncryptionInfoBuilder builder) {
-        this.builder = builder;
+    protected Decryptor() {
     }
     
     /**
@@ -54,6 +55,45 @@ public abstract class Decryptor {
     public abstract InputStream getDataStream(DirectoryNode dir)
         throws IOException, GeneralSecurityException;
 
+    /**
+     * Wraps a stream for decryption<p>
+     * 
+     * As we are handling streams and don't know the total length beforehand,
+     * it's the callers duty to care for the length of the entries.
+     *
+     * @param stream the stream to be wrapped
+     * @param initialPos initial/current byte position within the stream
+     * @return decrypted stream
+     */
+    public InputStream getDataStream(InputStream stream, int size, int initialPos)
+        throws IOException, GeneralSecurityException {
+        throw new RuntimeException("this decryptor doesn't support reading from a stream");
+    }
+
+    /**
+     * 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");
+    }
+
+    /**
+     * Initializes a cipher object for a given block index for encryption
+     *
+     * @param cipher may be null, otherwise the given instance is reset to the new block index
+     * @param block the block index, e.g. the persist/slide id (hslf)
+     * @return a new cipher object, if cipher was null, otherwise the reinitialized cipher
+     * @throws GeneralSecurityException
+     */
+    public Cipher initCipherForBlock(Cipher cipher, int block)
+    throws GeneralSecurityException {
+        throw new RuntimeException("this decryptor doesn't support initCipherForBlock");
+    }
+    
     public abstract boolean verifyPassword(String password)
         throws GeneralSecurityException;
 
@@ -85,9 +125,11 @@ public abstract class Decryptor {
     public InputStream getDataStream(NPOIFSFileSystem fs) throws IOException, GeneralSecurityException {
         return getDataStream(fs.getRoot());
     }
+
     public InputStream getDataStream(OPOIFSFileSystem fs) throws IOException, GeneralSecurityException {
         return getDataStream(fs.getRoot());
     }
+
     public InputStream getDataStream(POIFSFileSystem fs) throws IOException, GeneralSecurityException {
         return getDataStream(fs.getRoot());
     }
@@ -126,10 +168,29 @@ public abstract class Decryptor {
     }
 
     protected int getBlockSizeInBytes() {
-        return builder.getHeader().getBlockSize();
+        return encryptionInfo.getHeader().getBlockSize();
     }
     
     protected int getKeySizeInBytes() {
-        return builder.getHeader().getKeySize()/8;
+        return encryptionInfo.getHeader().getKeySize()/8;
+    }
+    
+    public EncryptionInfo getEncryptionInfo() {
+        return encryptionInfo;
+    }
+
+    public void setEncryptionInfo(EncryptionInfo encryptionInfo) {
+        this.encryptionInfo = encryptionInfo;
+    }
+
+    @Override
+    public Decryptor clone() throws CloneNotSupportedException {
+        Decryptor other = (Decryptor)super.clone();
+        other.integrityHmacKey = integrityHmacKey.clone();
+        other.integrityHmacValue = integrityHmacValue.clone();
+        other.verifier = verifier.clone();
+        other.secretKey = new SecretKeySpec(secretKey.getEncoded(), secretKey.getAlgorithm());
+        // encryptionInfo is set from outside
+        return other;
     }
 }
\ No newline at end of file




---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@poi.apache.org
For additional commands, e-mail: commits-help@poi.apache.org