Patch from Andreas Beeker from bug #53475 - further OOXML Encryption support, covering more ciphers
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1541009 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
35161a686b
commit
d1118ce0d5
@ -16,25 +16,25 @@
|
|||||||
==================================================================== */
|
==================================================================== */
|
||||||
package org.apache.poi.poifs.crypt;
|
package org.apache.poi.poifs.crypt;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
import java.util.Arrays;
|
||||||
import org.apache.poi.EncryptedDocumentException;
|
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
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.poifs.filesystem.DocumentInputStream;
|
||||||
import org.apache.poi.util.LittleEndian;
|
import org.apache.poi.util.LittleEndian;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Gary King
|
*
|
||||||
*/
|
*/
|
||||||
public class AgileDecryptor extends Decryptor {
|
public class AgileDecryptor extends Decryptor {
|
||||||
|
|
||||||
@ -60,35 +60,34 @@ public class AgileDecryptor extends Decryptor {
|
|||||||
|
|
||||||
public boolean verifyPassword(String password) throws GeneralSecurityException {
|
public boolean verifyPassword(String password) throws GeneralSecurityException {
|
||||||
EncryptionVerifier verifier = _info.getVerifier();
|
EncryptionVerifier verifier = _info.getVerifier();
|
||||||
int algorithm = verifier.getAlgorithm();
|
byte[] salt = verifier.getSalt();
|
||||||
int mode = verifier.getCipherMode();
|
|
||||||
|
|
||||||
byte[] pwHash = hashPassword(_info, password);
|
byte[] pwHash = hashPassword(_info, password);
|
||||||
byte[] iv = generateIv(algorithm, verifier.getSalt(), null);
|
byte[] iv = generateIv(salt, null);
|
||||||
|
|
||||||
SecretKey skey;
|
SecretKey skey;
|
||||||
skey = new SecretKeySpec(generateKey(pwHash, kVerifierInputBlock), "AES");
|
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());
|
byte[] verifierHashInput = cipher.doFinal(verifier.getVerifier());
|
||||||
|
|
||||||
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
|
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);
|
System.arraycopy(verifierHashInput, 0, trimmed, 0, trimmed.length);
|
||||||
byte[] hashedVerifier = sha1.digest(trimmed);
|
byte[] hashedVerifier = sha1.digest(trimmed);
|
||||||
|
|
||||||
skey = new SecretKeySpec(generateKey(pwHash, kHashedVerifierBlock), "AES");
|
skey = new SecretKeySpec(generateKey(pwHash, kHashedVerifierBlock), "AES");
|
||||||
iv = generateIv(algorithm, verifier.getSalt(), null);
|
iv = generateIv(salt, null);
|
||||||
cipher = getCipher(algorithm, mode, skey, iv);
|
cipher = getCipher(skey, iv);
|
||||||
byte[] verifierHash = cipher.doFinal(verifier.getVerifierHash());
|
byte[] verifierHash = cipher.doFinal(verifier.getVerifierHash());
|
||||||
trimmed = new byte[hashedVerifier.length];
|
trimmed = new byte[hashedVerifier.length];
|
||||||
System.arraycopy(verifierHash, 0, trimmed, 0, trimmed.length);
|
System.arraycopy(verifierHash, 0, trimmed, 0, trimmed.length);
|
||||||
|
|
||||||
if (Arrays.equals(trimmed, hashedVerifier)) {
|
if (Arrays.equals(trimmed, hashedVerifier)) {
|
||||||
skey = new SecretKeySpec(generateKey(pwHash, kCryptoKeyBlock), "AES");
|
skey = new SecretKeySpec(generateKey(pwHash, kCryptoKeyBlock), "AES");
|
||||||
iv = generateIv(algorithm, verifier.getSalt(), null);
|
iv = generateIv(salt, null);
|
||||||
cipher = getCipher(algorithm, mode, skey, iv);
|
cipher = getCipher(skey, iv);
|
||||||
byte[] inter = cipher.doFinal(verifier.getEncryptedKey());
|
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);
|
System.arraycopy(inter, 0, keyspec, 0, keyspec.length);
|
||||||
_secretKey = new SecretKeySpec(keyspec, "AES");
|
_secretKey = new SecretKeySpec(keyspec, "AES");
|
||||||
return true;
|
return true;
|
||||||
@ -124,9 +123,7 @@ public class AgileDecryptor extends Decryptor {
|
|||||||
throws GeneralSecurityException {
|
throws GeneralSecurityException {
|
||||||
_size = size;
|
_size = size;
|
||||||
_stream = stream;
|
_stream = stream;
|
||||||
_cipher = getCipher(_info.getHeader().getAlgorithm(),
|
_cipher = getCipher(_secretKey, _info.getHeader().getKeySalt());
|
||||||
_info.getHeader().getCipherMode(),
|
|
||||||
_secretKey, _info.getHeader().getKeySalt());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int read() throws IOException {
|
public int read() throws IOException {
|
||||||
@ -183,8 +180,7 @@ public class AgileDecryptor extends Decryptor {
|
|||||||
int index = (int)(_pos >> 12);
|
int index = (int)(_pos >> 12);
|
||||||
byte[] blockKey = new byte[4];
|
byte[] blockKey = new byte[4];
|
||||||
LittleEndian.putInt(blockKey, 0, index);
|
LittleEndian.putInt(blockKey, 0, index);
|
||||||
byte[] iv = generateIv(_info.getHeader().getAlgorithm(),
|
byte[] iv = generateIv(_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));
|
||||||
if (_lastIndex != index)
|
if (_lastIndex != index)
|
||||||
_stream.skip((index - _lastIndex) << 12);
|
_stream.skip((index - _lastIndex) << 12);
|
||||||
@ -196,20 +192,33 @@ public class AgileDecryptor extends Decryptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Cipher getCipher(int algorithm, int mode, SecretKey key, byte[] vec)
|
private Cipher getCipher(SecretKey key, byte[] vec)
|
||||||
throws GeneralSecurityException {
|
throws GeneralSecurityException {
|
||||||
String name = null;
|
String name = null;
|
||||||
String chain = null;
|
String chain = null;
|
||||||
|
|
||||||
if (algorithm == EncryptionHeader.ALGORITHM_AES_128 ||
|
EncryptionVerifier verifier = _info.getVerifier();
|
||||||
algorithm == EncryptionHeader.ALGORITHM_AES_192 ||
|
|
||||||
algorithm == EncryptionHeader.ALGORITHM_AES_256)
|
|
||||||
name = "AES";
|
|
||||||
|
|
||||||
if (mode == EncryptionHeader.MODE_CBC)
|
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";
|
chain = "CBC";
|
||||||
else if (mode == EncryptionHeader.MODE_CFB)
|
break;
|
||||||
|
case EncryptionHeader.MODE_CFB:
|
||||||
chain = "CFB";
|
chain = "CFB";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new EncryptedDocumentException("Unsupported chain mode");
|
||||||
|
}
|
||||||
|
|
||||||
Cipher cipher = Cipher.getInstance(name + "/" + chain + "/NoPadding");
|
Cipher cipher = Cipher.getInstance(name + "/" + chain + "/NoPadding");
|
||||||
IvParameterSpec iv = new IvParameterSpec(vec);
|
IvParameterSpec iv = new IvParameterSpec(vec);
|
||||||
@ -217,8 +226,8 @@ public class AgileDecryptor extends Decryptor {
|
|||||||
return cipher;
|
return cipher;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] getBlock(int algorithm, byte[] hash) {
|
private byte[] getBlock(byte[] hash, int size) {
|
||||||
byte[] result = new byte[getBlockSize(algorithm)];
|
byte[] result = new byte[size];
|
||||||
Arrays.fill(result, (byte)0x36);
|
Arrays.fill(result, (byte)0x36);
|
||||||
System.arraycopy(hash, 0, result, 0, Math.min(result.length, hash.length));
|
System.arraycopy(hash, 0, result, 0, Math.min(result.length, hash.length));
|
||||||
return result;
|
return result;
|
||||||
@ -227,18 +236,27 @@ public class AgileDecryptor extends Decryptor {
|
|||||||
private byte[] generateKey(byte[] hash, byte[] blockKey) throws NoSuchAlgorithmException {
|
private byte[] generateKey(byte[] hash, byte[] blockKey) throws NoSuchAlgorithmException {
|
||||||
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
|
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
|
||||||
sha1.update(hash);
|
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 {
|
throws NoSuchAlgorithmException {
|
||||||
|
|
||||||
|
|
||||||
if (blockKey == null)
|
if (blockKey == null)
|
||||||
return getBlock(algorithm, salt);
|
return getBlock(salt, getBlockSizeInBytes());
|
||||||
|
|
||||||
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
|
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
|
||||||
sha1.update(salt);
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -19,6 +19,7 @@ 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.io.UnsupportedEncodingException;
|
||||||
|
import java.security.DigestException;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
@ -27,6 +28,7 @@ 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 org.apache.poi.util.LittleEndian;
|
import org.apache.poi.util.LittleEndian;
|
||||||
|
import org.apache.poi.util.LittleEndianConsts;
|
||||||
|
|
||||||
public abstract class Decryptor {
|
public abstract class Decryptor {
|
||||||
public static final String DEFAULT_PASSWORD="VelvetSweatshop";
|
public static final String DEFAULT_PASSWORD="VelvetSweatshop";
|
||||||
@ -85,15 +87,6 @@ public abstract class Decryptor {
|
|||||||
return getDataStream(fs.getRoot());
|
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,
|
protected byte[] hashPassword(EncryptionInfo info,
|
||||||
String password) throws NoSuchAlgorithmException {
|
String password) throws NoSuchAlgorithmException {
|
||||||
// If no password was given, use the default
|
// If no password was given, use the default
|
||||||
@ -101,23 +94,30 @@ public abstract class Decryptor {
|
|||||||
password = DEFAULT_PASSWORD;
|
password = DEFAULT_PASSWORD;
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
|
byte[] pass;
|
||||||
byte[] bytes;
|
|
||||||
try {
|
try {
|
||||||
bytes = password.getBytes("UTF-16LE");
|
pass = password.getBytes("UTF-16LE");
|
||||||
} catch (UnsupportedEncodingException e) {
|
} catch (UnsupportedEncodingException e) {
|
||||||
throw new EncryptedDocumentException("UTF16 not supported");
|
throw new EncryptedDocumentException("UTF16 not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
sha1.update(info.getVerifier().getSalt());
|
byte[] salt = info.getVerifier().getSalt();
|
||||||
byte[] hash = sha1.digest(bytes);
|
|
||||||
byte[] iterator = new byte[4];
|
|
||||||
|
|
||||||
|
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++) {
|
for (int i = 0; i < info.getVerifier().getSpinCount(); i++) {
|
||||||
sha1.reset();
|
|
||||||
LittleEndian.putInt(iterator, 0, i);
|
LittleEndian.putInt(iterator, 0, i);
|
||||||
|
sha1.reset();
|
||||||
sha1.update(iterator);
|
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;
|
return hash;
|
||||||
|
@ -33,8 +33,6 @@ import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
|||||||
import org.apache.poi.util.LittleEndian;
|
import org.apache.poi.util.LittleEndian;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Maxim Valyanskiy
|
|
||||||
* @author Gary King
|
|
||||||
*/
|
*/
|
||||||
public class EcmaDecryptor extends Decryptor {
|
public class EcmaDecryptor extends Decryptor {
|
||||||
private final EncryptionInfo info;
|
private final EncryptionInfo info;
|
||||||
|
@ -55,6 +55,7 @@ public class EncryptionHeader {
|
|||||||
private final int algorithm;
|
private final int algorithm;
|
||||||
private final int hashAlgorithm;
|
private final int hashAlgorithm;
|
||||||
private final int keySize;
|
private final int keySize;
|
||||||
|
private final int blockSize;
|
||||||
private final int providerType;
|
private final int providerType;
|
||||||
private final int cipherMode;
|
private final int cipherMode;
|
||||||
private final byte[] keySalt;
|
private final byte[] keySalt;
|
||||||
@ -66,6 +67,7 @@ public class EncryptionHeader {
|
|||||||
algorithm = is.readInt();
|
algorithm = is.readInt();
|
||||||
hashAlgorithm = is.readInt();
|
hashAlgorithm = is.readInt();
|
||||||
keySize = is.readInt();
|
keySize = is.readInt();
|
||||||
|
blockSize = keySize;
|
||||||
providerType = is.readInt();
|
providerType = is.readInt();
|
||||||
|
|
||||||
is.readLong(); // skip reserved
|
is.readLong(); // skip reserved
|
||||||
@ -110,20 +112,22 @@ public class EncryptionHeader {
|
|||||||
sizeExtra = 0;
|
sizeExtra = 0;
|
||||||
cspName = null;
|
cspName = null;
|
||||||
|
|
||||||
int blockSize = Integer.parseInt(keyData.getNamedItem("blockSize").
|
blockSize = Integer.parseInt(keyData.getNamedItem("blockSize").
|
||||||
getNodeValue());
|
getNodeValue());
|
||||||
String cipher = keyData.getNamedItem("cipherAlgorithm").getNodeValue();
|
String cipher = keyData.getNamedItem("cipherAlgorithm").getNodeValue();
|
||||||
|
|
||||||
if ("AES".equals(cipher)) {
|
if ("AES".equals(cipher)) {
|
||||||
providerType = PROVIDER_AES;
|
providerType = PROVIDER_AES;
|
||||||
if (blockSize == 16)
|
switch (keySize) {
|
||||||
algorithm = ALGORITHM_AES_128;
|
case 128:
|
||||||
else if (blockSize == 24)
|
algorithm = ALGORITHM_AES_128; break;
|
||||||
algorithm = ALGORITHM_AES_192;
|
case 192:
|
||||||
else if (blockSize == 32)
|
algorithm = ALGORITHM_AES_192; break;
|
||||||
algorithm = ALGORITHM_AES_256;
|
case 256:
|
||||||
else
|
algorithm = ALGORITHM_AES_256; break;
|
||||||
throw new EncryptedDocumentException("Unsupported key length " + blockSize);
|
default:
|
||||||
|
throw new EncryptedDocumentException("Unsupported key length " + keySize);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new EncryptedDocumentException("Unsupported cipher " + cipher);
|
throw new EncryptedDocumentException("Unsupported cipher " + cipher);
|
||||||
}
|
}
|
||||||
@ -138,8 +142,8 @@ public class EncryptionHeader {
|
|||||||
throw new EncryptedDocumentException("Unsupported chaining mode " + chaining);
|
throw new EncryptedDocumentException("Unsupported chaining mode " + chaining);
|
||||||
|
|
||||||
String hashAlg = keyData.getNamedItem("hashAlgorithm").getNodeValue();
|
String hashAlg = keyData.getNamedItem("hashAlgorithm").getNodeValue();
|
||||||
int hashSize = Integer.parseInt(keyData.getNamedItem("hashSize")
|
int hashSize = Integer.parseInt(
|
||||||
.getNodeValue());
|
keyData.getNamedItem("hashSize").getNodeValue());
|
||||||
|
|
||||||
if ("SHA1".equals(hashAlg) && hashSize == 20) {
|
if ("SHA1".equals(hashAlg) && hashSize == 20) {
|
||||||
hashAlgorithm = HASH_SHA1;
|
hashAlgorithm = HASH_SHA1;
|
||||||
@ -190,6 +194,10 @@ public class EncryptionHeader {
|
|||||||
return keySize;
|
return keySize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getBlockSize() {
|
||||||
|
return blockSize;
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] getKeySalt() {
|
public byte[] getKeySalt() {
|
||||||
return keySalt;
|
return keySalt;
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,6 @@ import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Maxim Valyanskiy
|
|
||||||
* @author Gary King
|
|
||||||
*/
|
*/
|
||||||
public class EncryptionInfo {
|
public class EncryptionInfo {
|
||||||
private final int versionMajor;
|
private final int versionMajor;
|
||||||
|
@ -18,19 +18,17 @@ package org.apache.poi.poifs.crypt;
|
|||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
|
||||||
import org.apache.commons.codec.binary.Base64;
|
import org.apache.commons.codec.binary.Base64;
|
||||||
|
import org.apache.poi.EncryptedDocumentException;
|
||||||
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
||||||
|
import org.w3c.dom.NamedNodeMap;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
import org.w3c.dom.NamedNodeMap;
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
import org.apache.poi.EncryptedDocumentException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Maxim Valyanskiy
|
* Used when checking if a key is valid for a document
|
||||||
* @author Gary King
|
|
||||||
*/
|
*/
|
||||||
public class EncryptionVerifier {
|
public class EncryptionVerifier {
|
||||||
private final byte[] salt;
|
private final byte[] salt;
|
||||||
@ -89,15 +87,20 @@ public class EncryptionVerifier {
|
|||||||
|
|
||||||
String alg = keyData.getNamedItem("cipherAlgorithm").getNodeValue();
|
String alg = keyData.getNamedItem("cipherAlgorithm").getNodeValue();
|
||||||
|
|
||||||
|
int keyBits = Integer.parseInt(keyData.getNamedItem("keyBits")
|
||||||
|
.getNodeValue());
|
||||||
|
|
||||||
if ("AES".equals(alg)) {
|
if ("AES".equals(alg)) {
|
||||||
if (blockSize == 16)
|
switch (keyBits) {
|
||||||
algorithm = EncryptionHeader.ALGORITHM_AES_128;
|
case 128:
|
||||||
else if (blockSize == 24)
|
algorithm = EncryptionHeader.ALGORITHM_AES_128; break;
|
||||||
algorithm = EncryptionHeader.ALGORITHM_AES_192;
|
case 192:
|
||||||
else if (blockSize == 32)
|
algorithm = EncryptionHeader.ALGORITHM_AES_192; break;
|
||||||
algorithm = EncryptionHeader.ALGORITHM_AES_256;
|
case 256:
|
||||||
else
|
algorithm = EncryptionHeader.ALGORITHM_AES_256; break;
|
||||||
throw new EncryptedDocumentException("Unsupported block size");
|
default:
|
||||||
|
throw new EncryptedDocumentException("Unsupported key size");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new EncryptedDocumentException("Unsupported cipher");
|
throw new EncryptedDocumentException("Unsupported cipher");
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ public class TestXWPFBugs extends TestCase {
|
|||||||
* A word document that's encrypted with non-standard
|
* A word document that's encrypted with non-standard
|
||||||
* Encryption options, and no cspname section. See bug 53475
|
* Encryption options, and no cspname section. See bug 53475
|
||||||
*/
|
*/
|
||||||
public void test53475() throws Exception {
|
public void test53475NoCSPName() throws Exception {
|
||||||
try {
|
try {
|
||||||
Biff8EncryptionKey.setCurrentUserPassword("solrcell");
|
Biff8EncryptionKey.setCurrentUserPassword("solrcell");
|
||||||
File file = POIDataSamples.getDocumentInstance().getFile("bug53475-password-is-solrcell.docx");
|
File file = POIDataSamples.getDocumentInstance().getFile("bug53475-password-is-solrcell.docx");
|
||||||
@ -49,4 +49,40 @@ public class TestXWPFBugs extends TestCase {
|
|||||||
Biff8EncryptionKey.setCurrentUserPassword(null);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ public class TestEncryptionInfo extends TestCase {
|
|||||||
assertEquals(4, info.getVersionMajor());
|
assertEquals(4, info.getVersionMajor());
|
||||||
assertEquals(4, info.getVersionMinor());
|
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(EncryptionHeader.HASH_SHA512, info.getHeader().getHashAlgorithm());
|
||||||
assertEquals(256, info.getHeader().getKeySize());
|
assertEquals(256, info.getHeader().getKeySize());
|
||||||
assertEquals(64, info.getVerifier().getVerifierHash().length);
|
assertEquals(64, info.getVerifier().getVerifierHash().length);
|
||||||
|
Loading…
Reference in New Issue
Block a user