Bugzilla 52690 - added a getter for length of encrypted data in Ecma and Agile decryptors

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1293784 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Yegor Kozlov 2012-02-26 08:49:36 +00:00
parent a6aa1fd99e
commit 582ea1c54c
5 changed files with 81 additions and 14 deletions

View File

@ -34,6 +34,7 @@
<changes> <changes>
<release version="3.8-beta6" date="2012-??-??"> <release version="3.8-beta6" date="2012-??-??">
<action dev="poi-developers" type="fix">52690 - added a getter for length of encrypted data in Ecma and Agile decryptors</action>
<action dev="poi-developers" type="fix">52255 - support adding TIFF,EPS and WPG pictures in OOXML documents </action> <action dev="poi-developers" type="fix">52255 - support adding TIFF,EPS and WPG pictures in OOXML documents </action>
<action dev="poi-developers" type="fix">52078 - avoid OutOfMemoryError when rendering groupped pictures in HSLF </action> <action dev="poi-developers" type="fix">52078 - avoid OutOfMemoryError when rendering groupped pictures in HSLF </action>
<action dev="poi-developers" type="fix">52745 - fixed XSSFRichtextString.append to preserve leading / trailing spaces </action> <action dev="poi-developers" type="fix">52745 - fixed XSSFRichtextString.append to preserve leading / trailing spaces </action>

View File

@ -19,24 +19,18 @@ package org.apache.poi.poifs.crypt;
import java.util.Arrays; import java.util.Arrays;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.FilterInputStream;
import java.io.ByteArrayInputStream;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.EncryptedDocumentException; import org.apache.poi.EncryptedDocumentException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.IvParameterSpec;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.DocumentInputStream; import org.apache.poi.poifs.filesystem.DocumentInputStream;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndian;
/** /**
@ -46,6 +40,7 @@ public class AgileDecryptor extends Decryptor {
private final EncryptionInfo _info; private final EncryptionInfo _info;
private SecretKey _secretKey; private SecretKey _secretKey;
private long _length = -1;
private static final byte[] kVerifierInputBlock; private static final byte[] kVerifierInputBlock;
private static final byte[] kHashedVerifierBlock; private static final byte[] kHashedVerifierBlock;
@ -104,8 +99,13 @@ public class AgileDecryptor extends Decryptor {
public InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException { public InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException {
DocumentInputStream dis = dir.createDocumentInputStream("EncryptedPackage"); DocumentInputStream dis = dir.createDocumentInputStream("EncryptedPackage");
long size = dis.readLong(); _length = dis.readLong();
return new ChunkedCipherInputStream(dis, size); return new ChunkedCipherInputStream(dis, _length);
}
public long getLength(){
if(_length == -1) throw new IllegalStateException("EcmaDecryptor.getDataStream() was not called");
return _length;
} }
protected AgileDecryptor(EncryptionInfo info) { protected AgileDecryptor(EncryptionInfo info) {
@ -182,7 +182,7 @@ public class AgileDecryptor extends Decryptor {
private byte[] nextChunk() throws GeneralSecurityException, IOException { private byte[] nextChunk() throws GeneralSecurityException, IOException {
int index = (int)(_pos >> 12); int index = (int)(_pos >> 12);
byte[] blockKey = new byte[4]; byte[] blockKey = new byte[4];
LittleEndian.putInt(blockKey, index); LittleEndian.putInt(blockKey, 0, index);
byte[] iv = generateIv(_info.getHeader().getAlgorithm(), byte[] iv = generateIv(_info.getHeader().getAlgorithm(),
_info.getHeader().getKeySalt(), blockKey); _info.getHeader().getKeySalt(), blockKey);
_cipher.init(Cipher.DECRYPT_MODE, _secretKey, new IvParameterSpec(iv)); _cipher.init(Cipher.DECRYPT_MODE, _secretKey, new IvParameterSpec(iv));

View File

@ -31,12 +31,40 @@ import org.apache.poi.util.LittleEndian;
public abstract class Decryptor { public abstract class Decryptor {
public static final String DEFAULT_PASSWORD="VelvetSweatshop"; public static final String DEFAULT_PASSWORD="VelvetSweatshop";
/**
* Return a stream with decrypted data.
* <p>
* Use {@link #getLength()} to get the size of that data that can be safely read from the stream.
* Just reading to the end of the input stream is not sufficient because there are
* normally padding bytes that must be discarded
* </p>
*
* @param dir the node to read from
* @return decrypted stream
*/
public abstract InputStream getDataStream(DirectoryNode dir) public abstract InputStream getDataStream(DirectoryNode dir)
throws IOException, GeneralSecurityException; throws IOException, GeneralSecurityException;
public abstract boolean verifyPassword(String password) public abstract boolean verifyPassword(String password)
throws GeneralSecurityException; throws GeneralSecurityException;
/**
* Returns the length of the encytpted data that can be safely read with
* {@link #getDataStream(org.apache.poi.poifs.filesystem.DirectoryNode)}.
* Just reading to the end of the input stream is not sufficient because there are
* normally padding bytes that must be discarded
*
* <p>
* The length variable is initialized in {@link #getDataStream(org.apache.poi.poifs.filesystem.DirectoryNode)},
* an attempt to call getLength() prior to getDataStream() will result in IllegalStateException.
* </p>
*
* @return length of the encrypted data
* @throws IllegalStateException if {@link #getDataStream(org.apache.poi.poifs.filesystem.DirectoryNode)}
* was not called
*/
public abstract long getLength();
public static Decryptor getInstance(EncryptionInfo info) { public static Decryptor getInstance(EncryptionInfo info) {
int major = info.getVersionMajor(); int major = info.getVersionMajor();
int minor = info.getVersionMinor(); int minor = info.getVersionMinor();
@ -82,7 +110,7 @@ public abstract class Decryptor {
for (int i = 0; i < info.getVerifier().getSpinCount(); i++) { for (int i = 0; i < info.getVerifier().getSpinCount(); i++) {
sha1.reset(); sha1.reset();
LittleEndian.putInt(iterator, i); LittleEndian.putInt(iterator, 0, i);
sha1.update(iterator); sha1.update(iterator);
hash = sha1.digest(hash); hash = sha1.digest(hash);
} }

View File

@ -18,7 +18,6 @@ package org.apache.poi.poifs.crypt;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@ -31,7 +30,6 @@ import javax.crypto.spec.SecretKeySpec;
import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.DocumentInputStream; import org.apache.poi.poifs.filesystem.DocumentInputStream;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndian;
/** /**
@ -41,6 +39,7 @@ import org.apache.poi.util.LittleEndian;
public class EcmaDecryptor extends Decryptor { public class EcmaDecryptor extends Decryptor {
private final EncryptionInfo info; private final EncryptionInfo info;
private byte[] passwordHash; private byte[] passwordHash;
private long _length = -1;
public EcmaDecryptor(EncryptionInfo info) { public EcmaDecryptor(EncryptionInfo info) {
this.info = info; this.info = info;
@ -51,7 +50,7 @@ public class EcmaDecryptor extends Decryptor {
sha1.update(passwordHash); sha1.update(passwordHash);
byte[] blockValue = new byte[4]; byte[] blockValue = new byte[4];
LittleEndian.putInt(blockValue, block); LittleEndian.putInt(blockValue, 0, block);
byte[] finalHash = sha1.digest(blockValue); byte[] finalHash = sha1.digest(blockValue);
int requiredKeyLength = info.getHeader().getKeySize()/8; int requiredKeyLength = info.getHeader().getKeySize()/8;
@ -125,8 +124,13 @@ public class EcmaDecryptor extends Decryptor {
public InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException { public InputStream getDataStream(DirectoryNode dir) throws IOException, GeneralSecurityException {
DocumentInputStream dis = dir.createDocumentInputStream("EncryptedPackage"); DocumentInputStream dis = dir.createDocumentInputStream("EncryptedPackage");
long size = dis.readLong(); _length = dis.readLong();
return new CipherInputStream(dis, getCipher()); return new CipherInputStream(dis, getCipher());
} }
public long getLength(){
if(_length == -1) throw new IllegalStateException("EcmaDecryptor.getDataStream() was not called");
return _length;
}
} }

View File

@ -20,7 +20,9 @@ import junit.framework.TestCase;
import org.apache.poi.POIDataSamples; import org.apache.poi.POIDataSamples;
import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
@ -80,4 +82,36 @@ public class TestDecryptor extends TestCase {
} }
} }
} }
public void testDataLength() throws Exception {
POIFSFileSystem fs = new POIFSFileSystem(POIDataSamples.getPOIFSInstance().openResourceAsStream("protected_agile.docx"));
EncryptionInfo info = new EncryptionInfo(fs);
Decryptor d = Decryptor.getInstance(info);
d.verifyPassword(Decryptor.DEFAULT_PASSWORD);
InputStream is = d.getDataStream(fs);
long len = d.getLength();
assertEquals(12810, len);
byte[] buf = new byte[(int)len];
is.read(buf);
ZipInputStream zin = new ZipInputStream(new ByteArrayInputStream(buf));
while (true) {
ZipEntry entry = zin.getNextEntry();
if (entry==null) {
break;
}
while (zin.available()>0) {
zin.skip(zin.available());
}
}
}
} }