You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@poi.apache.org by ni...@apache.org on 2013/11/12 12:37:46 UTC
svn commit: r1541009 - in /poi/trunk/src: java/org/apache/poi/poifs/crypt/
ooxml/testcases/org/apache/poi/xwpf/ testcases/org/apache/poi/poifs/crypt/
Author: nick
Date: Tue Nov 12 11:37:45 2013
New Revision: 1541009
URL: http://svn.apache.org/r1541009
Log:
Patch from Andreas Beeker from bug #53475 - further OOXML Encryption support, covering more ciphers
Modified:
poi/trunk/src/java/org/apache/poi/poifs/crypt/AgileDecryptor.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/Decryptor.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/EcmaDecryptor.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionHeader.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java
poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java
poi/trunk/src/ooxml/testcases/org/apache/poi/xwpf/TestXWPFBugs.java
poi/trunk/src/testcases/org/apache/poi/poifs/crypt/TestEncryptionInfo.java
Modified: poi/trunk/src/java/org/apache/poi/poifs/crypt/AgileDecryptor.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/AgileDecryptor.java?rev=1541009&r1=1541008&r2=1541009&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/AgileDecryptor.java (original)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/AgileDecryptor.java Tue Nov 12 11:37:45 2013
@@ -16,25 +16,25 @@
==================================================================== */
package org.apache.poi.poifs.crypt;
-import java.util.Arrays;
import java.io.IOException;
import java.io.InputStream;
-import java.security.MessageDigest;
import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import org.apache.poi.poifs.filesystem.DirectoryNode;
-import org.apache.poi.EncryptedDocumentException;
+import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.DocumentInputStream;
import org.apache.poi.util.LittleEndian;
/**
- * @author Gary King
+ *
*/
public class AgileDecryptor extends Decryptor {
@@ -60,35 +60,34 @@ public class AgileDecryptor extends Decr
public boolean verifyPassword(String password) throws GeneralSecurityException {
EncryptionVerifier verifier = _info.getVerifier();
- int algorithm = verifier.getAlgorithm();
- int mode = verifier.getCipherMode();
+ byte[] salt = verifier.getSalt();
byte[] pwHash = hashPassword(_info, password);
- byte[] iv = generateIv(algorithm, verifier.getSalt(), null);
+ byte[] iv = generateIv(salt, null);
SecretKey skey;
skey = new SecretKeySpec(generateKey(pwHash, kVerifierInputBlock), "AES");
- Cipher cipher = getCipher(algorithm, mode, skey, iv);
+ Cipher cipher = getCipher(skey, iv);
byte[] verifierHashInput = cipher.doFinal(verifier.getVerifier());
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
- byte[] trimmed = new byte[verifier.getSalt().length];
+ byte[] trimmed = new byte[salt.length];
System.arraycopy(verifierHashInput, 0, trimmed, 0, trimmed.length);
byte[] hashedVerifier = sha1.digest(trimmed);
skey = new SecretKeySpec(generateKey(pwHash, kHashedVerifierBlock), "AES");
- iv = generateIv(algorithm, verifier.getSalt(), null);
- cipher = getCipher(algorithm, mode, skey, iv);
+ iv = generateIv(salt, null);
+ cipher = getCipher(skey, iv);
byte[] verifierHash = cipher.doFinal(verifier.getVerifierHash());
trimmed = new byte[hashedVerifier.length];
System.arraycopy(verifierHash, 0, trimmed, 0, trimmed.length);
if (Arrays.equals(trimmed, hashedVerifier)) {
skey = new SecretKeySpec(generateKey(pwHash, kCryptoKeyBlock), "AES");
- iv = generateIv(algorithm, verifier.getSalt(), null);
- cipher = getCipher(algorithm, mode, skey, iv);
+ iv = generateIv(salt, null);
+ cipher = getCipher(skey, iv);
byte[] inter = cipher.doFinal(verifier.getEncryptedKey());
- byte[] keyspec = new byte[_info.getHeader().getKeySize() / 8];
+ byte[] keyspec = new byte[getKeySizeInBytes()];
System.arraycopy(inter, 0, keyspec, 0, keyspec.length);
_secretKey = new SecretKeySpec(keyspec, "AES");
return true;
@@ -124,9 +123,7 @@ public class AgileDecryptor extends Decr
throws GeneralSecurityException {
_size = size;
_stream = stream;
- _cipher = getCipher(_info.getHeader().getAlgorithm(),
- _info.getHeader().getCipherMode(),
- _secretKey, _info.getHeader().getKeySalt());
+ _cipher = getCipher(_secretKey, _info.getHeader().getKeySalt());
}
public int read() throws IOException {
@@ -183,8 +180,7 @@ public class AgileDecryptor extends Decr
int index = (int)(_pos >> 12);
byte[] blockKey = new byte[4];
LittleEndian.putInt(blockKey, 0, index);
- byte[] iv = generateIv(_info.getHeader().getAlgorithm(),
- _info.getHeader().getKeySalt(), blockKey);
+ byte[] iv = generateIv(_info.getHeader().getKeySalt(), blockKey);
_cipher.init(Cipher.DECRYPT_MODE, _secretKey, new IvParameterSpec(iv));
if (_lastIndex != index)
_stream.skip((index - _lastIndex) << 12);
@@ -196,20 +192,33 @@ public class AgileDecryptor extends Decr
}
}
- private Cipher getCipher(int algorithm, int mode, SecretKey key, byte[] vec)
+ private Cipher getCipher(SecretKey key, byte[] vec)
throws GeneralSecurityException {
String name = null;
String chain = null;
- if (algorithm == EncryptionHeader.ALGORITHM_AES_128 ||
- algorithm == EncryptionHeader.ALGORITHM_AES_192 ||
- algorithm == EncryptionHeader.ALGORITHM_AES_256)
- name = "AES";
-
- if (mode == EncryptionHeader.MODE_CBC)
- chain = "CBC";
- else if (mode == EncryptionHeader.MODE_CFB)
- chain = "CFB";
+ EncryptionVerifier verifier = _info.getVerifier();
+
+ switch (verifier.getAlgorithm()) {
+ case EncryptionHeader.ALGORITHM_AES_128:
+ case EncryptionHeader.ALGORITHM_AES_192:
+ case EncryptionHeader.ALGORITHM_AES_256:
+ name = "AES";
+ break;
+ default:
+ throw new EncryptedDocumentException("Unsupported algorithm");
+ }
+
+ switch (verifier.getCipherMode()) {
+ case EncryptionHeader.MODE_CBC:
+ chain = "CBC";
+ break;
+ case EncryptionHeader.MODE_CFB:
+ chain = "CFB";
+ break;
+ default:
+ throw new EncryptedDocumentException("Unsupported chain mode");
+ }
Cipher cipher = Cipher.getInstance(name + "/" + chain + "/NoPadding");
IvParameterSpec iv = new IvParameterSpec(vec);
@@ -217,8 +226,8 @@ public class AgileDecryptor extends Decr
return cipher;
}
- private byte[] getBlock(int algorithm, byte[] hash) {
- byte[] result = new byte[getBlockSize(algorithm)];
+ private byte[] getBlock(byte[] hash, int size) {
+ byte[] result = new byte[size];
Arrays.fill(result, (byte)0x36);
System.arraycopy(hash, 0, result, 0, Math.min(result.length, hash.length));
return result;
@@ -227,18 +236,27 @@ public class AgileDecryptor extends Decr
private byte[] generateKey(byte[] hash, byte[] blockKey) throws NoSuchAlgorithmException {
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
sha1.update(hash);
- return getBlock(_info.getVerifier().getAlgorithm(), sha1.digest(blockKey));
+ byte[] key = sha1.digest(blockKey);
+ return getBlock(key, getKeySizeInBytes());
}
- protected byte[] generateIv(int algorithm, byte[] salt, byte[] blockKey)
+ protected byte[] generateIv(byte[] salt, byte[] blockKey)
throws NoSuchAlgorithmException {
if (blockKey == null)
- return getBlock(algorithm, salt);
+ return getBlock(salt, getBlockSizeInBytes());
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
sha1.update(salt);
- return getBlock(algorithm, sha1.digest(blockKey));
+ return getBlock(sha1.digest(blockKey), getBlockSizeInBytes());
+ }
+
+ protected int getBlockSizeInBytes() {
+ return _info.getHeader().getBlockSize();
+ }
+
+ protected int getKeySizeInBytes() {
+ return _info.getHeader().getKeySize()/8;
}
-}
\ No newline at end of file
+}
Modified: poi/trunk/src/java/org/apache/poi/poifs/crypt/Decryptor.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/Decryptor.java?rev=1541009&r1=1541008&r2=1541009&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/Decryptor.java (original)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/Decryptor.java Tue Nov 12 11:37:45 2013
@@ -19,6 +19,7 @@ package org.apache.poi.poifs.crypt;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
+import java.security.DigestException;
import java.security.MessageDigest;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
@@ -27,6 +28,7 @@ import org.apache.poi.poifs.filesystem.P
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianConsts;
public abstract class Decryptor {
public static final String DEFAULT_PASSWORD="VelvetSweatshop";
@@ -85,15 +87,6 @@ public abstract class Decryptor {
return getDataStream(fs.getRoot());
}
- protected static int getBlockSize(int algorithm) {
- switch (algorithm) {
- case EncryptionHeader.ALGORITHM_AES_128: return 16;
- case EncryptionHeader.ALGORITHM_AES_192: return 24;
- case EncryptionHeader.ALGORITHM_AES_256: return 32;
- }
- throw new EncryptedDocumentException("Unknown block size");
- }
-
protected byte[] hashPassword(EncryptionInfo info,
String password) throws NoSuchAlgorithmException {
// If no password was given, use the default
@@ -101,25 +94,32 @@ public abstract class Decryptor {
password = DEFAULT_PASSWORD;
}
- MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
- byte[] bytes;
+ byte[] pass;
try {
- bytes = password.getBytes("UTF-16LE");
+ pass = password.getBytes("UTF-16LE");
} catch (UnsupportedEncodingException e) {
throw new EncryptedDocumentException("UTF16 not supported");
}
- sha1.update(info.getVerifier().getSalt());
- byte[] hash = sha1.digest(bytes);
- byte[] iterator = new byte[4];
-
+ byte[] salt = info.getVerifier().getSalt();
+
+ MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
+ sha1.update(salt);
+ byte[] hash = sha1.digest(pass);
+ byte[] iterator = new byte[LittleEndianConsts.INT_SIZE];
+
+ try {
for (int i = 0; i < info.getVerifier().getSpinCount(); i++) {
+ LittleEndian.putInt(iterator, 0, i);
sha1.reset();
- LittleEndian.putInt(iterator, 0, i);
sha1.update(iterator);
- hash = sha1.digest(hash);
+ sha1.update(hash);
+ sha1.digest(hash, 0, hash.length); // don't create hash buffer everytime new
}
-
+ } catch (DigestException e) {
+ throw new EncryptedDocumentException("error in password hashing");
+ }
+
return hash;
}
}
\ No newline at end of file
Modified: poi/trunk/src/java/org/apache/poi/poifs/crypt/EcmaDecryptor.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/EcmaDecryptor.java?rev=1541009&r1=1541008&r2=1541009&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/EcmaDecryptor.java (original)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/EcmaDecryptor.java Tue Nov 12 11:37:45 2013
@@ -33,8 +33,6 @@ import org.apache.poi.poifs.filesystem.D
import org.apache.poi.util.LittleEndian;
/**
- * @author Maxim Valyanskiy
- * @author Gary King
*/
public class EcmaDecryptor extends Decryptor {
private final EncryptionInfo info;
Modified: poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionHeader.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionHeader.java?rev=1541009&r1=1541008&r2=1541009&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionHeader.java (original)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionHeader.java Tue Nov 12 11:37:45 2013
@@ -55,6 +55,7 @@ public class EncryptionHeader {
private final int algorithm;
private final int hashAlgorithm;
private final int keySize;
+ private final int blockSize;
private final int providerType;
private final int cipherMode;
private final byte[] keySalt;
@@ -66,6 +67,7 @@ public class EncryptionHeader {
algorithm = is.readInt();
hashAlgorithm = is.readInt();
keySize = is.readInt();
+ blockSize = keySize;
providerType = is.readInt();
is.readLong(); // skip reserved
@@ -110,20 +112,22 @@ public class EncryptionHeader {
sizeExtra = 0;
cspName = null;
- int blockSize = Integer.parseInt(keyData.getNamedItem("blockSize").
+ blockSize = Integer.parseInt(keyData.getNamedItem("blockSize").
getNodeValue());
String cipher = keyData.getNamedItem("cipherAlgorithm").getNodeValue();
if ("AES".equals(cipher)) {
providerType = PROVIDER_AES;
- if (blockSize == 16)
- algorithm = ALGORITHM_AES_128;
- else if (blockSize == 24)
- algorithm = ALGORITHM_AES_192;
- else if (blockSize == 32)
- algorithm = ALGORITHM_AES_256;
- else
- throw new EncryptedDocumentException("Unsupported key length " + blockSize);
+ switch (keySize) {
+ case 128:
+ algorithm = ALGORITHM_AES_128; break;
+ case 192:
+ algorithm = ALGORITHM_AES_192; break;
+ case 256:
+ algorithm = ALGORITHM_AES_256; break;
+ default:
+ throw new EncryptedDocumentException("Unsupported key length " + keySize);
+ }
} else {
throw new EncryptedDocumentException("Unsupported cipher " + cipher);
}
@@ -138,8 +142,8 @@ public class EncryptionHeader {
throw new EncryptedDocumentException("Unsupported chaining mode " + chaining);
String hashAlg = keyData.getNamedItem("hashAlgorithm").getNodeValue();
- int hashSize = Integer.parseInt(keyData.getNamedItem("hashSize")
- .getNodeValue());
+ int hashSize = Integer.parseInt(
+ keyData.getNamedItem("hashSize").getNodeValue());
if ("SHA1".equals(hashAlg) && hashSize == 20) {
hashAlgorithm = HASH_SHA1;
@@ -190,6 +194,10 @@ public class EncryptionHeader {
return keySize;
}
+ public int getBlockSize() {
+ return blockSize;
+ }
+
public byte[] getKeySalt() {
return keySalt;
}
Modified: poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java?rev=1541009&r1=1541008&r2=1541009&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java (original)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java Tue Nov 12 11:37:45 2013
@@ -24,8 +24,6 @@ import org.apache.poi.poifs.filesystem.P
import java.io.IOException;
/**
- * @author Maxim Valyanskiy
- * @author Gary King
*/
public class EncryptionInfo {
private final int versionMajor;
Modified: poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java?rev=1541009&r1=1541008&r2=1541009&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java (original)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java Tue Nov 12 11:37:45 2013
@@ -18,19 +18,17 @@ package org.apache.poi.poifs.crypt;
import java.io.ByteArrayInputStream;
-import org.apache.commons.codec.binary.Base64;
+import javax.xml.parsers.DocumentBuilderFactory;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.poifs.filesystem.DocumentInputStream;
-
+import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
-import org.w3c.dom.NamedNodeMap;
-import javax.xml.parsers.DocumentBuilderFactory;
-import org.apache.poi.EncryptedDocumentException;
/**
- * @author Maxim Valyanskiy
- * @author Gary King
+ * Used when checking if a key is valid for a document
*/
public class EncryptionVerifier {
private final byte[] salt;
@@ -88,16 +86,21 @@ public class EncryptionVerifier {
.getNodeValue());
String alg = keyData.getNamedItem("cipherAlgorithm").getNodeValue();
+
+ int keyBits = Integer.parseInt(keyData.getNamedItem("keyBits")
+ .getNodeValue());
if ("AES".equals(alg)) {
- if (blockSize == 16)
- algorithm = EncryptionHeader.ALGORITHM_AES_128;
- else if (blockSize == 24)
- algorithm = EncryptionHeader.ALGORITHM_AES_192;
- else if (blockSize == 32)
- algorithm = EncryptionHeader.ALGORITHM_AES_256;
- else
- throw new EncryptedDocumentException("Unsupported block size");
+ switch (keyBits) {
+ case 128:
+ algorithm = EncryptionHeader.ALGORITHM_AES_128; break;
+ case 192:
+ algorithm = EncryptionHeader.ALGORITHM_AES_192; break;
+ case 256:
+ algorithm = EncryptionHeader.ALGORITHM_AES_256; break;
+ default:
+ throw new EncryptedDocumentException("Unsupported key size");
+ }
} else {
throw new EncryptedDocumentException("Unsupported cipher");
}
Modified: poi/trunk/src/ooxml/testcases/org/apache/poi/xwpf/TestXWPFBugs.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/ooxml/testcases/org/apache/poi/xwpf/TestXWPFBugs.java?rev=1541009&r1=1541008&r2=1541009&view=diff
==============================================================================
--- poi/trunk/src/ooxml/testcases/org/apache/poi/xwpf/TestXWPFBugs.java (original)
+++ poi/trunk/src/ooxml/testcases/org/apache/poi/xwpf/TestXWPFBugs.java Tue Nov 12 11:37:45 2013
@@ -20,7 +20,7 @@ public class TestXWPFBugs extends TestCa
* A word document that's encrypted with non-standard
* Encryption options, and no cspname section. See bug 53475
*/
- public void test53475() throws Exception {
+ public void test53475NoCSPName() throws Exception {
try {
Biff8EncryptionKey.setCurrentUserPassword("solrcell");
File file = POIDataSamples.getDocumentInstance().getFile("bug53475-password-is-solrcell.docx");
@@ -49,4 +49,40 @@ public class TestXWPFBugs extends TestCa
Biff8EncryptionKey.setCurrentUserPassword(null);
}
}
+
+ /**
+ * A word document with aes-256, i.e. aes is always 128 bit (= 128 bit block size),
+ * but the key can be 128/192/256 bits
+ */
+ public void test53475_aes256() throws Exception {
+ try {
+ Biff8EncryptionKey.setCurrentUserPassword("pass");
+ File file = POIDataSamples.getDocumentInstance().getFile("bug53475-password-is-pass.docx");
+ NPOIFSFileSystem filesystem = new NPOIFSFileSystem(file, true);
+
+ // Check the encryption details
+ EncryptionInfo info = new EncryptionInfo(filesystem);
+ assertEquals(16, info.getHeader().getBlockSize());
+ assertEquals(256, info.getHeader().getKeySize());
+ assertEquals(EncryptionHeader.ALGORITHM_AES_256, info.getHeader().getAlgorithm());
+ assertEquals(EncryptionHeader.HASH_SHA1, info.getHeader().getHashAlgorithm());
+
+ // Check it can be decoded
+ Decryptor d = Decryptor.getInstance(info);
+ assertTrue("Unable to process: document is encrypted", d.verifyPassword("pass"));
+
+ // Check we can read the word document in that
+ InputStream dataStream = d.getDataStream(filesystem);
+ OPCPackage opc = OPCPackage.open(dataStream);
+ XWPFDocument doc = new XWPFDocument(opc);
+ XWPFWordExtractor ex = new XWPFWordExtractor(doc);
+ String text = ex.getText();
+ assertNotNull(text);
+ // I know ... a stupid typo, maybe next time ...
+ assertEquals("The is a password protected document.", text.trim());
+ ex.close();
+ } finally {
+ Biff8EncryptionKey.setCurrentUserPassword(null);
+ }
+ }
}
Modified: poi/trunk/src/testcases/org/apache/poi/poifs/crypt/TestEncryptionInfo.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/poifs/crypt/TestEncryptionInfo.java?rev=1541009&r1=1541008&r2=1541009&view=diff
==============================================================================
--- poi/trunk/src/testcases/org/apache/poi/poifs/crypt/TestEncryptionInfo.java (original)
+++ poi/trunk/src/testcases/org/apache/poi/poifs/crypt/TestEncryptionInfo.java Tue Nov 12 11:37:45 2013
@@ -50,7 +50,7 @@ public class TestEncryptionInfo extends
assertEquals(4, info.getVersionMajor());
assertEquals(4, info.getVersionMinor());
- assertEquals(EncryptionHeader.ALGORITHM_AES_128, info.getHeader().getAlgorithm());
+ assertEquals(EncryptionHeader.ALGORITHM_AES_256, info.getHeader().getAlgorithm());
assertEquals(EncryptionHeader.HASH_SHA512, info.getHeader().getHashAlgorithm());
assertEquals(256, info.getHeader().getKeySize());
assertEquals(64, info.getVerifier().getVerifierHash().length);
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@poi.apache.org
For additional commands, e-mail: commits-help@poi.apache.org