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/08/08 00:10:44 UTC

svn commit: r1755461 [1/2] - in /poi/branches/hssf_cryptoapi/src: java/org/apache/poi/hssf/record/ java/org/apache/poi/hssf/record/crypto/ java/org/apache/poi/poifs/crypt/ java/org/apache/poi/poifs/crypt/binaryrc4/ java/org/apache/poi/poifs/crypt/crypt...

Author: kiwiwings
Date: Mon Aug  8 00:10:44 2016
New Revision: 1755461

URL: http://svn.apache.org/viewvc?rev=1755461&view=rev
Log:
HSSF CryptoAPI decryption support

Added:
    poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/
    poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XORDecryptor.java   (with props)
    poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionHeader.java   (with props)
    poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionInfoBuilder.java   (with props)
    poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionVerifier.java   (with props)
    poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptor.java   (with props)
    poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/usermodel/TestCryptoAPI.java   (with props)
    poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/AllEncryptionTests.java
      - copied, changed from r1753906, poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/record/crypto/AllHSSFEncryptionTests.java
    poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/TestBiff8DecryptingStream.java
      - copied, changed from r1753906, poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/record/crypto/TestBiff8DecryptingStream.java
    poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/TestXorEncryption.java
      - copied, changed from r1753906, poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/record/crypto/TestXorEncryption.java
    poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/binaryrc4/
    poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/binaryrc4/TestBinaryRC4.java   (with props)
Removed:
    poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/crypto/Biff8Cipher.java
    poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/crypto/Biff8RC4.java
    poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/crypto/Biff8RC4Key.java
    poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/crypto/Biff8XOR.java
    poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/crypto/Biff8XORKey.java
    poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/record/crypto/
Modified:
    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/crypto/Biff8DecryptingStream.java
    poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.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/Decryptor.java
    poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java
    poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/EncryptionMode.java
    poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Decryptor.java
    poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDecryptor.java
    poi/branches/hssf_cryptoapi/src/scratchpad/src/org/apache/poi/hslf/record/DocumentEncryptionAtom.java
    poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/record/AllRecordTests.java
    poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/TestCipherAlgorithm.java

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=1755461&r1=1755460&r2=1755461&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 Mon Aug  8 00:10:44 2016
@@ -17,8 +17,19 @@
 
 package org.apache.poi.hssf.record;
 
+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;
 
 /**
@@ -31,228 +42,82 @@ 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
-	} 
-	
-	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 int encryptionType;
+    private EncryptionInfo encryptionInfo;
+    private int dataLength;
 	
 	private FilePassRecord(FilePassRecord other) {
-	    _encryptionType = other._encryptionType;
-	    _keyData = other._keyData.clone();
+	    dataLength = other.dataLength;
+	    encryptionType = other.encryptionType;
+        try {
+            encryptionInfo = other.encryptionInfo.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new EncryptedDocumentException(e);
+        }
 	}
 	
 	public FilePassRecord(RecordInputStream in) {
-		_encryptionType = in.readUShort();
+        dataLength = in.remaining();
+		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);
+        }
+	}
 
-		switch (_encryptionType) {
-			case ENCRYPTION_XOR:
-			    _keyData = new XorKeyData();
-			    break;
-			case ENCRYPTION_OTHER:
-			    _keyData = new Rc4KeyData();
-				break;
-			default:
-				throw new RecordFormatException("Unknown encryption type " + _encryptionType);
-		}
+	public void serialize(LittleEndianOutput out) {
+        out.writeShort(encryptionType);
 
-		_keyData.read(in);
-	}
+        byte data[] = new byte[1024];
+        LittleEndianByteArrayOutputStream bos = new LittleEndianByteArrayOutputStream(data, 0);
 
-	private static byte[] read(RecordInputStream in, int size) {
-		byte[] result = new byte[size];
-		in.readFully(result);
-		return result;
-	}
+        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());
+                ((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();
+	    return dataLength;
 	}
 
-	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() {
+    public short getSid() {
 		return sid;
 	}
 	
@@ -265,8 +130,13 @@ public final class FilePassRecord extend
 		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/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=1755461&r1=1755460&r2=1755461&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 Mon Aug  8 00:10:44 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
@@ -114,31 +112,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/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=1755461&r1=1755460&r2=1755461&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 Mon Aug  8 00:10:44 2016
@@ -25,6 +25,7 @@ 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.poifs.crypt.EncryptionInfo;
 import org.apache.poi.util.Internal;
 import org.apache.poi.util.LittleEndianConsts;
 import org.apache.poi.util.LittleEndianInput;
@@ -33,8 +34,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*/
@@ -122,7 +121,7 @@ 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);

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=1755461&r1=1755460&r2=1755461&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 Mon Aug  8 00:10:44 2016
@@ -17,103 +17,202 @@
 
 package org.apache.poi.hssf.record.crypto;
 
+import java.io.IOException;
 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;
+    private 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
+     */
+    private 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;
+        }
+    }
+
+    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);
+        }
+    }
 
-	public long readLong() {
-		return _cipher.xorLong(_le.readLong());
-	}
 }

Modified: poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java
URL: http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java?rev=1755461&r1=1755460&r2=1755461&view=diff
==============================================================================
--- poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java (original)
+++ poi/branches/hssf_cryptoapi/src/java/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.java Mon Aug  8 00:10:44 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/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=1755461&r1=1755460&r2=1755461&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 Mon Aug  8 00:10:44 2016
@@ -21,56 +21,56 @@ 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 _chunkBits;
-    
+
     private final long _size;
-    private final byte[] _chunk;
+    private final byte[] _chunk, _plain;
     private final Cipher _cipher;
 
     private int _lastIndex;
     private long _pos;
     private boolean _chunkIsValid = false;
 
-    public ChunkedCipherInputStream(LittleEndianInput stream, long size, int chunkSize)
+    public ChunkedCipherInputStream(InputStream stream, long size, int chunkSize)
     throws GeneralSecurityException {
         this(stream, size, chunkSize, 0);
     }
 
-    public ChunkedCipherInputStream(LittleEndianInput stream, long size, int chunkSize, int initialPos)
+    public ChunkedCipherInputStream(InputStream stream, long size, int chunkSize, int initialPos)
     throws GeneralSecurityException {
-        super((InputStream)stream);
+        super(stream);
         _size = size;
         _pos = initialPos;
         this._chunkSize = chunkSize;
-        if (chunkSize == -1) {
-            _chunk = new byte[4096];
-        } else {
-            _chunk = new byte[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;
 
@@ -88,8 +88,12 @@ public abstract class ChunkedCipherInput
 
     @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;
         }
@@ -110,7 +114,9 @@ public abstract class ChunkedCipherInput
                 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;
@@ -139,7 +145,7 @@ public abstract class ChunkedCipherInput
     public int available() {
         return remainingBytes();
     }
-    
+
     /**
      * Helper method for forbidden available call - we know the size beforehand, so it's ok ...
      *
@@ -148,7 +154,7 @@ public abstract class ChunkedCipherInput
     private int remainingBytes() {
         return (int)(_size - _pos);
     }
-    
+
     @Override
     public boolean markSupported() {
         return false;
@@ -158,21 +164,21 @@ public abstract class ChunkedCipherInput
     public synchronized void mark(int readlimit) {
         throw new UnsupportedOperationException();
     }
-    
+
     @Override
     public synchronized void reset() throws IOException {
         throw new UnsupportedOperationException();
     }
 
-    private int getChunkMask() {
+    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);
             }
@@ -183,18 +189,81 @@ public abstract class ChunkedCipherInput
         final int todo = (int)Math.min(_size, _chunk.length);
         int readBytes = 0, totalBytes = 0;
         do {
-            readBytes = super.read(_chunk, totalBytes, todo-totalBytes);
+            readBytes = super.read(_plain, totalBytes, todo-totalBytes);
             totalBytes += Math.max(0, readBytes);
         } while (readBytes != -1 && totalBytes < todo);
 
-        if (readBytes == -1 && _pos+totalBytes < _size) {
+        if (readBytes == -1 && _pos+totalBytes < _size && _size < Integer.MAX_VALUE) {
             throw new EOFException("buffer underrun");
         }
 
-        if (_chunkSize == -1) {
-            _cipher.update(_chunk, 0, totalBytes, _chunk);
+        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 {
-            _cipher.doFinal(_chunk, 0, totalBytes, _chunk);
+            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
+     */
+    public int readPlain(byte b[], int off, int len) throws IOException {
+        if (len <= 0) {
+            return len;
         }
+
+        int readBytes, total = 0;
+        do {
+            readBytes = read(b, off, len, true);
+            total += Math.max(0, readBytes);
+        } while (readBytes > -1 && total < len);
+
+        return total;
+    }
+
+    /**
+     * 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
+     */
+    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/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=1755461&r1=1755460&r2=1755461&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 Mon Aug  8 00:10:44 2016
@@ -26,7 +26,10 @@ import java.io.IOException;
 import java.io.OutputStream;
 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.poifs.filesystem.DirectoryNode;
@@ -153,19 +156,17 @@ public abstract class ChunkedCipherOutpu
 
         int ciLen;
         try {
+            boolean doFinal = true;
             if (_chunkSize == STREAMING) {
                 if (continued) {
-                    ciLen = _cipher.update(_chunk, 0, posInChunk, _chunk);
-                } else {
-                    ciLen = _cipher.doFinal(_chunk, 0, posInChunk, _chunk);
+                    doFinal = false;
                 }
-
                 // reset stream (not only) in case we were interrupted by plain stream parts
                 _pos = 0;
             } else {
                 _cipher = initCipherForBlock(_cipher, index, lastChunk);
-                ciLen = _cipher.doFinal(_chunk, 0, posInChunk, _chunk);
             }
+            ciLen = invokeCipher(posInChunk, doFinal);
         } catch (GeneralSecurityException e) {
             throw new IOException("can't re-/initialize cipher", e);
         }
@@ -173,6 +174,23 @@ public abstract class ChunkedCipherOutpu
         out.write(_chunk, 0, 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 {
+        if (doFinal) {
+            return _cipher.doFinal(_chunk, 0, posInChunk, _chunk);
+        } else {
+            return _cipher.update(_chunk, 0, posInChunk, _chunk);
+        }
+    }
+    
     @Override
     public void close() throws IOException {
         try {

Modified: poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/Decryptor.java
URL: http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/Decryptor.java?rev=1755461&r1=1755460&r2=1755461&view=diff
==============================================================================
--- poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/Decryptor.java (original)
+++ poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/Decryptor.java Mon Aug  8 00:10:44 2016
@@ -29,7 +29,6 @@ import org.apache.poi.poifs.filesystem.D
 import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
 import org.apache.poi.poifs.filesystem.OPOIFSFileSystem;
 import org.apache.poi.poifs.filesystem.POIFSFileSystem;
-import org.apache.poi.util.LittleEndianInput;
 
 public abstract class Decryptor implements Cloneable {
     public static final String DEFAULT_PASSWORD="VelvetSweatshop";
@@ -66,7 +65,7 @@ public abstract class Decryptor implemen
      * @param initialPos initial/current byte position within the stream
      * @return decrypted stream
      */
-    public InputStream getDataStream(LittleEndianInput stream, int size, int initialPos)
+    public InputStream getDataStream(InputStream stream, int size, int initialPos)
         throws IOException, GeneralSecurityException {
         throw new RuntimeException("this decryptor doesn't support reading from a stream");
     }

Modified: poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java
URL: http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java?rev=1755461&r1=1755460&r2=1755461&view=diff
==============================================================================
--- poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java (original)
+++ poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java Mon Aug  8 00:10:44 2016
@@ -20,6 +20,7 @@ import static org.apache.poi.poifs.crypt
 import static org.apache.poi.poifs.crypt.EncryptionMode.binaryRC4;
 import static org.apache.poi.poifs.crypt.EncryptionMode.cryptoAPI;
 import static org.apache.poi.poifs.crypt.EncryptionMode.standard;
+import static org.apache.poi.poifs.crypt.EncryptionMode.xor;
 
 import java.io.IOException;
 
@@ -35,6 +36,7 @@ import org.apache.poi.util.LittleEndianI
 /**
  */
 public class EncryptionInfo implements Cloneable {
+    private final EncryptionMode encryptionMode;
     private final int versionMajor;
     private final int versionMinor;
     private final int encryptionFlags;
@@ -75,49 +77,55 @@ public class EncryptionInfo implements C
     public EncryptionInfo(POIFSFileSystem fs) throws IOException {
        this(fs.getRoot());
     }
+    
     /**
      * Opens for decryption
      */
     public EncryptionInfo(OPOIFSFileSystem fs) throws IOException {
        this(fs.getRoot());
     }
+    
     /**
      * Opens for decryption
      */
     public EncryptionInfo(NPOIFSFileSystem fs) throws IOException {
        this(fs.getRoot());
     }
+    
     /**
      * Opens for decryption
      */
     public EncryptionInfo(DirectoryNode dir) throws IOException {
-        this(dir.createDocumentInputStream("EncryptionInfo"), false);
+        this(dir.createDocumentInputStream("EncryptionInfo"), null);
     }
 
-    public EncryptionInfo(LittleEndianInput dis, boolean isCryptoAPI) throws IOException {
-        final EncryptionMode encryptionMode;
-        versionMajor = dis.readUShort();
-        versionMinor = dis.readUShort();
+    public EncryptionInfo(LittleEndianInput dis, EncryptionMode preferredEncryptionMode) throws IOException {
+        if (preferredEncryptionMode == xor) {
+            versionMajor = xor.versionMajor;
+            versionMinor = xor.versionMinor;
+        } else {
+            versionMajor = dis.readUShort();
+            versionMinor = dis.readUShort();
+        }
 
-        if (   versionMajor == binaryRC4.versionMajor
+        if (   versionMajor == xor.versionMajor
+            && versionMinor == xor.versionMinor) {
+            encryptionMode = xor;
+            encryptionFlags = -1;
+        } else if (   versionMajor == binaryRC4.versionMajor
             && versionMinor == binaryRC4.versionMinor) {
             encryptionMode = binaryRC4;
             encryptionFlags = -1;
-        } else if (!isCryptoAPI
-            && versionMajor == agile.versionMajor
+        } else if (
+               2 <= versionMajor && versionMajor <= 4
+            && versionMinor == 2) {
+            encryptionMode = (preferredEncryptionMode == cryptoAPI) ? cryptoAPI : standard;
+            encryptionFlags = dis.readInt();
+        } else if (
+               versionMajor == agile.versionMajor
             && versionMinor == agile.versionMinor){
             encryptionMode = agile;
             encryptionFlags = dis.readInt();
-        } else if (!isCryptoAPI
-            && 2 <= versionMajor && versionMajor <= 4
-            && versionMinor == standard.versionMinor) {
-            encryptionMode = standard;
-            encryptionFlags = dis.readInt();
-        } else if (isCryptoAPI
-            && 2 <= versionMajor && versionMajor <= 4
-            && versionMinor == cryptoAPI.versionMinor) {
-            encryptionMode = cryptoAPI;
-            encryptionFlags = dis.readInt();
         } else {
             encryptionFlags = dis.readInt();
             throw new EncryptedDocumentException(
@@ -170,6 +178,7 @@ public class EncryptionInfo implements C
           , int blockSize
           , ChainingMode chainingMode
       ) {
+        this.encryptionMode = encryptionMode; 
         versionMajor = encryptionMode.versionMajor;
         versionMinor = encryptionMode.versionMinor;
         encryptionFlags = encryptionMode.encryptionFlags;
@@ -236,6 +245,10 @@ public class EncryptionInfo implements C
         this.encryptor = encryptor;
     }
 
+    public EncryptionMode getEncryptionMode() {
+        return encryptionMode;
+    }
+    
     @Override
     public EncryptionInfo clone() throws CloneNotSupportedException {
         EncryptionInfo other = (EncryptionInfo)super.clone();

Modified: poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/EncryptionMode.java
URL: http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/EncryptionMode.java?rev=1755461&r1=1755460&r2=1755461&view=diff
==============================================================================
--- poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/EncryptionMode.java (original)
+++ poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/EncryptionMode.java Mon Aug  8 00:10:44 2016
@@ -33,7 +33,9 @@ public enum EncryptionMode {
     /* @see <a href="http://msdn.microsoft.com/en-us/library/dd906097(v=office.12).aspx">2.3.4.5 \EncryptionInfo Stream (Standard Encryption)</a> */
     standard("org.apache.poi.poifs.crypt.standard.StandardEncryptionInfoBuilder", 4, 2, 0x24),
     /* @see <a href="http://msdn.microsoft.com/en-us/library/dd925810(v=office.12).aspx">2.3.4.10 \EncryptionInfo Stream (Agile Encryption)</a> */
-    agile("org.apache.poi.poifs.crypt.agile.AgileEncryptionInfoBuilder", 4, 4, 0x40)
+    agile("org.apache.poi.poifs.crypt.agile.AgileEncryptionInfoBuilder", 4, 4, 0x40),
+    /* @see <a href="https://msdn.microsoft.com/en-us/library/dd907599(v=office.12).aspx">XOR Obfuscation</a> */
+    xor("org.apache.poi.poifs.crypt.xor.XOREncryptionInfoBuilder", 0, 0, 0)
     ;
     
     public final String builder;

Modified: poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Decryptor.java
URL: http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Decryptor.java?rev=1755461&r1=1755460&r2=1755461&view=diff
==============================================================================
--- poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Decryptor.java (original)
+++ poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/binaryrc4/BinaryRC4Decryptor.java Mon Aug  8 00:10:44 2016
@@ -29,11 +29,9 @@ import javax.crypto.spec.SecretKeySpec;
 
 import org.apache.poi.EncryptedDocumentException;
 import org.apache.poi.poifs.crypt.*;
-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.util.LittleEndian;
-import org.apache.poi.util.LittleEndianInput;
 import org.apache.poi.util.StringUtil;
 
 public class BinaryRC4Decryptor extends Decryptor implements Cloneable {
@@ -53,7 +51,7 @@ public class BinaryRC4Decryptor extends
             super(stream, size, _chunkSize);
         }
 
-        public BinaryRC4CipherInputStream(LittleEndianInput stream)
+        public BinaryRC4CipherInputStream(InputStream stream)
                 throws GeneralSecurityException {
             super(stream, Integer.MAX_VALUE, _chunkSize);
         }    
@@ -140,7 +138,8 @@ public class BinaryRC4Decryptor extends
         return new BinaryRC4CipherInputStream(dis, _length);
     }
     
-    public InputStream getDataStream(LittleEndianInput stream)
+    @Override
+    public InputStream getDataStream(InputStream stream, int size, int initialPos)
             throws IOException, GeneralSecurityException {
         return new BinaryRC4CipherInputStream(stream);
     }

Modified: poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDecryptor.java
URL: http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDecryptor.java?rev=1755461&r1=1755460&r2=1755461&view=diff
==============================================================================
--- poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDecryptor.java (original)
+++ poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDecryptor.java Mon Aug  8 00:10:44 2016
@@ -45,7 +45,6 @@ import org.apache.poi.util.BitFieldFacto
 import org.apache.poi.util.BoundedInputStream;
 import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.LittleEndian;
-import org.apache.poi.util.LittleEndianInput;
 import org.apache.poi.util.LittleEndianInputStream;
 import org.apache.poi.util.StringUtil;
 
@@ -146,7 +145,7 @@ public class CryptoAPIDecryptor extends
     }
 
     @Override
-    public ChunkedCipherInputStream getDataStream(LittleEndianInput stream, int size, int initialPos)
+    public ChunkedCipherInputStream getDataStream(InputStream stream, int size, int initialPos)
             throws IOException, GeneralSecurityException {
         return new CryptoAPICipherInputStream(stream, size, initialPos);
     }
@@ -233,7 +232,7 @@ public class CryptoAPIDecryptor extends
             return CryptoAPIDecryptor.this.initCipherForBlock(existing, block);
         }
 
-        public CryptoAPICipherInputStream(LittleEndianInput stream, long size, int initialPos)
+        public CryptoAPICipherInputStream(InputStream stream, long size, int initialPos)
                 throws GeneralSecurityException {
             super(stream, size, _chunkSize, initialPos);
         }    

Added: 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=1755461&view=auto
==============================================================================
--- poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XORDecryptor.java (added)
+++ poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XORDecryptor.java Mon Aug  8 00:10:44 2016
@@ -0,0 +1,174 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.poifs.crypt.xor;
+
+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.poifs.crypt.ChunkedCipherInputStream;
+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.filesystem.DirectoryNode;
+import org.apache.poi.util.LittleEndian;
+
+public class XORDecryptor extends Decryptor implements Cloneable {
+    private long _length = -1L;
+    private int _chunkSize = 512;
+
+    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);
+            _initialOffset = initialPos;
+        }
+        
+        @Override
+        protected int invokeCipher(int totalBytes, boolean doFinal) {
+            final int pos = (int)getPos();
+            final byte xorArray[] = getEncryptionInfo().getDecryptor().getSecretKey().getEncoded();
+            final byte chunk[] = getChunk();
+            final byte plain[] = getPlain();
+            final int posInChunk = pos & getChunkMask();
+            
+            /*
+             * 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. 
+             */
+            final int xorArrayIndex = _initialOffset+_recordEnd+(pos-_recordStart);
+            
+            for (int i=0; pos+i < _recordEnd && i < totalBytes; i++) {
+                // The following is taken from the Libre Office implementation
+                // It seems that the encrypt and decrypt method is mixed up
+                // in the MS-OFFCRYPTO docs
+                byte value = plain[posInChunk+i];
+                value = rotateLeft(value, 3);
+                value ^= xorArray[(xorArrayIndex+i) & 0x0F];
+                chunk[posInChunk+i] = value;
+            }
+
+            // the other bytes will be encoded, when setNextRecordSize is called the next time
+            return totalBytes;
+        }
+        
+        private byte rotateLeft(byte bits, int shift) {
+            return (byte)(((bits & 0xff) << shift) | ((bits & 0xff) >>> (8 - shift)));
+        }
+        
+        
+        /**
+         * Decrypts a xor obfuscated byte array.
+         * The data is decrypted in-place
+         * 
+         * @see <a href="http://msdn.microsoft.com/en-us/library/dd908506.aspx">2.3.7.3 Binary Document XOR Data Transformation Method 1</a>
+         */
+        @Override
+        public void setNextRecordSize(int recordSize) {
+            _recordStart = (int)getPos();
+            _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();
+    }
+}

Propchange: poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XORDecryptor.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 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=1755461&view=auto
==============================================================================
--- poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionHeader.java (added)
+++ poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionHeader.java Mon Aug  8 00:10:44 2016
@@ -0,0 +1,37 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.poifs.crypt.xor;
+
+import org.apache.poi.poifs.crypt.EncryptionHeader;
+import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+
+public class XOREncryptionHeader extends EncryptionHeader implements EncryptionRecord, Cloneable {
+
+    protected XOREncryptionHeader() {
+    }
+
+    @Override
+    public void write(LittleEndianByteArrayOutputStream littleendianbytearrayoutputstream) {
+    }
+
+    @Override
+    public XOREncryptionHeader clone() throws CloneNotSupportedException {
+        return (XOREncryptionHeader)super.clone();
+    }
+}

Propchange: poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionHeader.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionInfoBuilder.java
URL: http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionInfoBuilder.java?rev=1755461&view=auto
==============================================================================
--- poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionInfoBuilder.java (added)
+++ poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionInfoBuilder.java Mon Aug  8 00:10:44 2016
@@ -0,0 +1,62 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.poifs.crypt.xor;
+
+import java.io.IOException;
+
+import org.apache.poi.poifs.crypt.ChainingMode;
+import org.apache.poi.poifs.crypt.CipherAlgorithm;
+import org.apache.poi.poifs.crypt.Decryptor;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.poifs.crypt.EncryptionInfoBuilder;
+import org.apache.poi.poifs.crypt.Encryptor;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.poi.util.LittleEndianInput;
+
+public class XOREncryptionInfoBuilder implements EncryptionInfoBuilder {
+
+    public XOREncryptionInfoBuilder() {
+    }
+
+    @Override
+    public void initialize(EncryptionInfo info, LittleEndianInput dis)
+    throws IOException {
+        info.setHeader(new XOREncryptionHeader());
+        info.setVerifier(new XOREncryptionVerifier(dis));
+        Decryptor dec = new XORDecryptor();
+        dec.setEncryptionInfo(info);
+        info.setDecryptor(dec);
+        Encryptor enc = new XOREncryptor();
+        enc.setEncryptionInfo(info);
+        info.setEncryptor(enc);
+    }
+
+    @Override
+    public void initialize(EncryptionInfo info,
+        CipherAlgorithm cipherAlgorithm, HashAlgorithm hashAlgorithm,
+        int keyBits, int blockSize, ChainingMode chainingMode) {
+        info.setHeader(new XOREncryptionHeader());
+        info.setVerifier(new XOREncryptionVerifier());
+        Decryptor dec = new XORDecryptor();
+        dec.setEncryptionInfo(info);
+        info.setDecryptor(dec);
+        Encryptor enc = new XOREncryptor();
+        enc.setEncryptionInfo(info);
+        info.setEncryptor(enc);
+    }
+}

Propchange: poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionInfoBuilder.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 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=1755461&view=auto
==============================================================================
--- poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionVerifier.java (added)
+++ poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionVerifier.java Mon Aug  8 00:10:44 2016
@@ -0,0 +1,61 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.poifs.crypt.xor;
+
+import org.apache.poi.poifs.crypt.EncryptionVerifier;
+import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+import org.apache.poi.util.LittleEndianInput;
+
+public class XOREncryptionVerifier extends EncryptionVerifier implements EncryptionRecord, Cloneable {
+
+    protected XOREncryptionVerifier() {
+        setEncryptedKey(new byte[2]);
+        setEncryptedVerifier(new byte[2]);
+    }
+
+    protected XOREncryptionVerifier(LittleEndianInput is) {
+        /**
+         * 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.
+         */
+        byte key[] = new byte[2];
+        is.readFully(key);
+        setEncryptedKey(key);
+        
+        /**
+         * verificationBytes (2 bytes): An unsigned integer that specifies
+         * the password verification identifier.
+         */
+        byte verifier[] = new byte[2];
+        is.readFully(verifier);
+        setEncryptedVerifier(verifier);
+    }
+    
+    @Override
+    public void write(LittleEndianByteArrayOutputStream bos) {
+        bos.write(getEncryptedKey());
+        bos.write(getEncryptedVerifier());
+    }
+
+    @Override
+    public XOREncryptionVerifier clone() throws CloneNotSupportedException {
+        return (XOREncryptionVerifier)super.clone();
+    }
+}

Propchange: poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptionVerifier.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 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=1755461&view=auto
==============================================================================
--- poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptor.java (added)
+++ poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptor.java Mon Aug  8 00:10:44 2016
@@ -0,0 +1,99 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.poifs.crypt.xor;
+
+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 javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+
+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;
+
+public class XOREncryptor extends Encryptor implements Cloneable {
+
+    protected XOREncryptor() {
+    }
+
+    @Override
+    public void confirmPassword(String password) {
+    }
+
+    @Override
+    public void confirmPassword(String password, byte keySpec[],
+            byte keySalt[], byte verifier[], byte verifierSalt[],
+            byte integritySalt[]) {
+    }
+
+    @Override
+    public OutputStream getDataStream(DirectoryNode dir)
+    throws IOException, GeneralSecurityException {
+        OutputStream countStream = new XORCipherOutputStream(dir);
+        return countStream;
+    }
+
+    protected int getKeySizeInBytes() {
+        return -1;
+    }
+
+    protected void createEncryptionInfoEntry(DirectoryNode dir) throws IOException {
+    }
+
+    @Override
+    public XOREncryptor clone() throws CloneNotSupportedException {
+        return (XOREncryptor)super.clone();
+    }
+
+    protected class XORCipherOutputStream extends ChunkedCipherOutputStream {
+
+        @Override
+        protected Cipher initCipherForBlock(Cipher cipher, int block, boolean lastChunk)
+        throws GeneralSecurityException {
+            return XORDecryptor.initCipherForBlock(cipher, block, getEncryptionInfo(), getSecretKey(), Cipher.ENCRYPT_MODE);
+        }
+
+        @Override
+        protected void calculateChecksum(File file, int i) {
+        }
+
+        @Override
+        protected void createEncryptionInfoEntry(DirectoryNode dir, File tmpFile)
+        throws IOException, GeneralSecurityException {
+            XOREncryptor.this.createEncryptionInfoEntry(dir);
+        }
+
+        public XORCipherOutputStream(DirectoryNode dir)
+        throws IOException, GeneralSecurityException {
+            super(dir, 512);
+        }
+    }
+}

Propchange: poi/branches/hssf_cryptoapi/src/java/org/apache/poi/poifs/crypt/xor/XOREncryptor.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: poi/branches/hssf_cryptoapi/src/scratchpad/src/org/apache/poi/hslf/record/DocumentEncryptionAtom.java
URL: http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/scratchpad/src/org/apache/poi/hslf/record/DocumentEncryptionAtom.java?rev=1755461&r1=1755460&r2=1755461&view=diff
==============================================================================
--- poi/branches/hssf_cryptoapi/src/scratchpad/src/org/apache/poi/hslf/record/DocumentEncryptionAtom.java (original)
+++ poi/branches/hssf_cryptoapi/src/scratchpad/src/org/apache/poi/hslf/record/DocumentEncryptionAtom.java Mon Aug  8 00:10:44 2016
@@ -53,7 +53,8 @@ public final class DocumentEncryptionAto
 
 		ByteArrayInputStream bis = new ByteArrayInputStream(source, start+8, len-8);
 		LittleEndianInputStream leis = new LittleEndianInputStream(bis);
-		ei = new EncryptionInfo(leis, true);
+		ei = new EncryptionInfo(leis, EncryptionMode.cryptoAPI);
+		leis.close();
 	}
 
 	public DocumentEncryptionAtom() {
@@ -121,6 +122,7 @@ public final class DocumentEncryptionAto
 		LittleEndian.putInt(_header, 4, bos.getWriteIndex());
         out.write(_header);
 		out.write(data, 0, bos.getWriteIndex());
+		bos.close();
 	}
 
     @Override

Modified: poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/record/AllRecordTests.java
URL: http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/record/AllRecordTests.java?rev=1755461&r1=1755460&r2=1755461&view=diff
==============================================================================
--- poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/record/AllRecordTests.java (original)
+++ poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/record/AllRecordTests.java Mon Aug  8 00:10:44 2016
@@ -21,8 +21,8 @@ import org.apache.poi.hssf.record.aggreg
 import org.apache.poi.hssf.record.cf.TestCellRange;
 import org.apache.poi.hssf.record.chart.AllChartRecordTests;
 import org.apache.poi.hssf.record.common.TestUnicodeString;
-import org.apache.poi.hssf.record.crypto.AllHSSFEncryptionTests;
 import org.apache.poi.hssf.record.pivot.AllPivotRecordTests;
+import org.apache.poi.poifs.crypt.AllEncryptionTests;
 import org.apache.poi.ss.formula.constant.TestConstantValueParser;
 import org.apache.poi.ss.formula.ptg.AllFormulaTests;
 import org.junit.runner.RunWith;
@@ -34,7 +34,7 @@ import org.junit.runners.Suite;
 @RunWith(Suite.class)
 @Suite.SuiteClasses({
     AllChartRecordTests.class,
-    AllHSSFEncryptionTests.class,
+    AllEncryptionTests.class,
     AllFormulaTests.class,
     AllPivotRecordTests.class,
     AllRecordAggregateTests.class,

Added: 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=1755461&view=auto
==============================================================================
--- poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/usermodel/TestCryptoAPI.java (added)
+++ poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/usermodel/TestCryptoAPI.java Mon Aug  8 00:10:44 2016
@@ -0,0 +1,62 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+
+import org.apache.poi.hssf.HSSFITestDataProvider;
+import org.apache.poi.hssf.extractor.ExcelExtractor;
+import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
+import org.junit.AfterClass;
+import org.junit.Test;
+
+public class TestCryptoAPI {
+    final HSSFITestDataProvider ssTests = HSSFITestDataProvider.instance;
+
+    @AfterClass
+    public static void resetPW() {
+        Biff8EncryptionKey.setCurrentUserPassword(null);
+    }
+
+    @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();
+    }
+}

Propchange: poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/usermodel/TestCryptoAPI.java
------------------------------------------------------------------------------
    svn:eol-style = native

Copied: poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/AllEncryptionTests.java (from r1753906, poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/record/crypto/AllHSSFEncryptionTests.java)
URL: http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/AllEncryptionTests.java?p2=poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/AllEncryptionTests.java&p1=poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/record/crypto/AllHSSFEncryptionTests.java&r1=1753906&r2=1755461&rev=1755461&view=diff
==============================================================================
--- poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/record/crypto/AllHSSFEncryptionTests.java (original)
+++ poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/AllEncryptionTests.java Mon Aug  8 00:10:44 2016
@@ -15,20 +15,19 @@
    limitations under the License.
 ==================================================================== */
 
-package org.apache.poi.hssf.record.crypto;
+package org.apache.poi.poifs.crypt;
 
 import org.junit.runner.RunWith;
 import org.junit.runners.Suite;
 
 /**
- * Collects all tests for package <tt>org.apache.poi.hssf.record.crypto</tt>.
- *
- * @author Josh Micich
+ * Collects all tests for package <tt>org.apache.poi.poifs.crypt</tt>.
  */
 @RunWith(Suite.class)
 @Suite.SuiteClasses({
     TestBiff8DecryptingStream.class,
-    TestBiff8EncryptionKey.class
+    TestCipherAlgorithm.class,
+    TestXorEncryption.class
 })
-public final class AllHSSFEncryptionTests {
+public final class AllEncryptionTests {
 }

Copied: poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/TestBiff8DecryptingStream.java (from r1753906, poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/record/crypto/TestBiff8DecryptingStream.java)
URL: http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/TestBiff8DecryptingStream.java?p2=poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/TestBiff8DecryptingStream.java&p1=poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/record/crypto/TestBiff8DecryptingStream.java&r1=1753906&r2=1755461&rev=1755461&view=diff
==============================================================================
--- poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/hssf/record/crypto/TestBiff8DecryptingStream.java (original)
+++ poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/TestBiff8DecryptingStream.java Mon Aug  8 00:10:44 2016
@@ -15,7 +15,7 @@
    limitations under the License.
 ==================================================================== */
 
-package org.apache.poi.hssf.record.crypto;
+package org.apache.poi.poifs.crypt;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -23,17 +23,18 @@ import static org.junit.Assert.assertFal
 import java.io.InputStream;
 import java.util.Arrays;
 
-import junit.framework.AssertionFailedError;
-import junit.framework.ComparisonFailure;
+import javax.crypto.spec.SecretKeySpec;
 
+import org.apache.poi.hssf.record.crypto.Biff8DecryptingStream;
 import org.apache.poi.util.HexDump;
 import org.apache.poi.util.HexRead;
 import org.junit.Test;
 
+import junit.framework.AssertionFailedError;
+import junit.framework.ComparisonFailure;
+
 /**
  * Tests for {@link Biff8DecryptingStream}
- *
- * @author Josh Micich
  */
 public final class TestBiff8DecryptingStream {
 
@@ -49,12 +50,10 @@ public final class TestBiff8DecryptingSt
 		public MockStream(int initialValue) {
 			_initialValue = initialValue;
 		}
+
 		public int read() {
 			return (_initialValue+_position++) & 0xFF;
 		}
-		public int getPosition() {
-			return _position;
-		}
 	}
 
 	private static final class StreamTester {
@@ -70,7 +69,11 @@ public final class TestBiff8DecryptingSt
 		public StreamTester(MockStream ms, String keyDigestHex, int expectedFirstInt) {
 			_ms = ms;
 			byte[] keyDigest = HexRead.readFromString(keyDigestHex);
-			_bds = new Biff8DecryptingStream(_ms, 0, new Biff8RC4Key(keyDigest));
+			EncryptionInfo ei = new EncryptionInfo(EncryptionMode.binaryRC4);
+			Decryptor dec = ei.getDecryptor();
+			dec.setSecretKey(new SecretKeySpec(keyDigest, "RC4"));
+			
+			_bds = new Biff8DecryptingStream(_ms, 0, ei);
 			assertEquals(expectedFirstInt, _bds.readInt());
 			_errorsOccurred = false;
 		}
@@ -84,11 +87,11 @@ public final class TestBiff8DecryptingSt
 		 * Also confirms that read position of the underlying stream is aligned.
 		 */
 		public void rollForward(int fromPosition, int toPosition) {
-			assertEquals(fromPosition, _ms.getPosition());
+			assertEquals(fromPosition, _bds.getPosition());
 			for (int i = fromPosition; i < toPosition; i++) {
 				_bds.readByte();
 			}
-			assertEquals(toPosition, _ms.getPosition());
+			assertEquals(toPosition, _bds.getPosition());
 		}
 
 		public void confirmByte(int expVal) {

Modified: poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/TestCipherAlgorithm.java
URL: http://svn.apache.org/viewvc/poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/TestCipherAlgorithm.java?rev=1755461&r1=1755460&r2=1755461&view=diff
==============================================================================
--- poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/TestCipherAlgorithm.java (original)
+++ poi/branches/hssf_cryptoapi/src/testcases/org/apache/poi/poifs/crypt/TestCipherAlgorithm.java Mon Aug  8 00:10:44 2016
@@ -17,14 +17,14 @@
 
 package org.apache.poi.poifs.crypt;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
 
 import org.apache.poi.EncryptedDocumentException;
 import org.junit.Test;
 
 public class TestCipherAlgorithm {
     @Test
-    public void test() {
+    public void validInputs() {
         assertEquals(128, CipherAlgorithm.aes128.defaultKeySize);
         
         for(CipherAlgorithm alg : CipherAlgorithm.values()) {
@@ -33,27 +33,20 @@ public class TestCipherAlgorithm {
 
         assertEquals(CipherAlgorithm.aes128, CipherAlgorithm.fromEcmaId(0x660E));
         assertEquals(CipherAlgorithm.aes192, CipherAlgorithm.fromXmlId("AES", 192));
-        
-        try {
-            CipherAlgorithm.fromEcmaId(0);
-            fail("Should throw exception");
-        } catch (EncryptedDocumentException e) {
-            // expected
-        }
-
-        try {
-            CipherAlgorithm.fromXmlId("AES", 1);
-            fail("Should throw exception");
-        } catch (EncryptedDocumentException e) {
-            // expected
-        }
-
-        try {
-            CipherAlgorithm.fromXmlId("RC1", 0x40);
-            fail("Should throw exception");
-        } catch (EncryptedDocumentException e) {
-            // expected
-        }
     }
-
+    
+    @Test(expected=EncryptedDocumentException.class)
+    public void invalidEcmaId() {
+        CipherAlgorithm.fromEcmaId(0);
+    }
+    
+    @Test(expected=EncryptedDocumentException.class)
+    public void invalidXmlId1() {
+        CipherAlgorithm.fromXmlId("AES", 1);
+    }
+    
+    @Test(expected=EncryptedDocumentException.class)
+    public void invalidXmlId2() {
+        CipherAlgorithm.fromXmlId("RC1", 0x40);
+    }
 }




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